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

DirectX для начинающих. Часть вторая.

Автор: Виктор Кода

Небольшое отступление.

Прошёл месяц с тех пор как я написал первую часть ( http://www.delphikingdom.com/helloworld/directx.htm ) статьи по использованию DirectX в среде Delphi. У меня накопилось ещё несколько примеров, которые, надеюсь, послужат наглядным руководством для начинающих.

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

Вызовы _AddRef() и _Release() больше не используются – в конце концов я посчитал это бессмысленной тратой времени при наборе кода. К тому же, как выяснилось, что вызов именно этих методов привёл к неработоспособности одного из примеров предыдущей статьи – если кто интересовался, знает, что это был пример опроса клавиатуры с использованием DirectInput. После удаления вызовов программа стал работать корректно. По-видимому, имело место некорректное взаимодействие с драйвером клавиатуры. Выражение вида if COM-объект <> nil then COM-объект := nil

Переписано с использованием процедуры следующего вида:

procedure SAFE_DELETE(p: TInterfacedObject);
begin
  if p <> nil then
    p := nil
end;
Теперь достаточно написать SAFE_DELETE( @COM-объект ) – может, это покажется и излишним, но поверьте, в более крупных программах, где надо удалить 15-20 COM-интерфейсов, это становится удобным и сокращает код. Все эти соображения навеяны под влиянием примеров из MS SDK. Кстати, может, кто-то несогласен с правильностью описанной процедуры?

Модуль basedd8.pas в проектах для DirectDraw переименован в basedd7.pas – всё-таки DirectDraw – это часть DirectX 7, в версий 8 он как таковой отсутствует.

В функции LoadFiles() добавлен вызов DeleteObject() – как известно, после работы объекты GDI надо удалять, иначе они поглощают ресурсы системы. В данном случае именно такой объект создаётся при вызове функции GDI LoadImage() – казалось бы, тип HBITMAP – это всего лишь переопределение типа LongWord, копилятор самостоятельно удалит переменную этого типа после выхода из функции. На самом деле GDI при вызове LoadImage() (и других подобных функций) создаёт ресурс GDI и резервирует для него часть системной памяти, а переменная hBmp – всего лишь идентификатор этого ресурса в общем списке ресурсов Windows. Поэтому в процессе выполнения программы будет удаляться только идентификатор, а ресурс, на который он указывает, будет «висеть» в памяти. Именно поэтому следует вызвать DeleteObject() для удаления объекта GDI. В предыдушем примере я не сделал этого по причине недосмотра.

Большая часть примеров в этой статье предназначена для работы с DirectDraw – как мне кажется, наиболее востребованному элементу DirectX (кроме, естественно, Direct3D).

Надеюсь, мой стиль написания кода программ покажется удовлетворительным – он почти во всём подобен стилю, который использовали составители DirectX SDK. Вообще, многие пишут, как курица лапой – и предлагают свои творения на всеобщее обозрение. Ещё полезно заглянуть на страницу в нашем уважаемом Королевстве - http://www.delphikingdom.com/article/tassel.htm - это классика.

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

Хочется поделиться с новичками своим мнением по поводу компонетов DelphiX и почему я не рекомендую их использовать. С одной стороны, DelphiX - это удобно – нет необходимости выполнять утомительный набор методов DirectX и длинных, как многоступенчатая ракета, констант наподобие DDENUMSURFACES_CANBECREATED. Однако давайте посмотрим – используется что-нибудь подобное в С++? Я не могу исследовать всю Сеть в поисках овета на такой вопрос, но, думается – нет. Почему? Такие наборы классов – это нестандартный подход. Допустим, вы потратили изрядно своего времени и досконально изучили DelphiX. Так вот, изучив всё ЭТО, вы в итоге не изучили сам DirectX. Второе – изученные классы обладают многим и позволяют писать реальные программы, но всё равно этот подход очень негибок – вы ограничены тем, что уже сделано. На этот счёт у меня есть веский аргумент – это Direct3D. Вот тут DelphiX уж точно не даст развернуться как следует – и это характерно и для других компонент DirectX, пусть и в меньшей мере. Третье – немного, но снижается быстродействие. Четвёртое – в классах DelphiX кроме непосредственных вызовов методов интерфейсов DirectX используются ещё и собственные функции – кто даст гарантию, что в них нет ошибок? В конце концов, именно за такие вот «примочки» Delphi не в почёте у С-программистов – они попросту надсмехаются над такими методами разработки программ. К сожалению, должен к ним присоединиться и я. Как же так, возмутятся многие. Компонентный подход – это ведь основа основ Delphi! Согласен, использование TMemo или TComboBox – это действительно удобный подход, да что там – превосходный, отличный подход! Но вот в случае с DirectX или чем-то подобным использовать такие средства разработки крайне нежелательно. Как бы вы отнеслись к компоненту TOpenGL? Или TWin32API? Вот так-то. DelphiX можно использовать как источник разных идей по реализации того или иного эффекта – перенося всё это в свою программу в виде отдельных функций или собственноручно написанных классов. Так что изучайте прямой API – для уверенности в завтрашнем дне и в собственной квалификации. Собственно, сами примеры Bounds В предыдущей статье я предложил вниманию только один пример для DirectDraw – простая реализация вывода спрайта поверх фона в задний буфер и перенос построенной картинки на экран. Спрайт не мог выйти за пределы экрана – и это не спроста. Если убрать досадное ограничение, то выяснится, что как только часть спрайта выходит за границы экрана (а фактически границы заднего буфера), то сразу исчезает полностью. Возвращение в прежнюю позицию восстанавливает вывод. Статьи по Delphi (006)… Когда я впервые столкнулся с этой проблемой, меня это неприятно поразило. Оказалось, что необходимо использовать интерфейс отсечения – IDirectDrawClipper. Однако он предназначен только для оконных приложений, в полноэкранном режиме от него нет никакого проку. Как уже было упомянуто, рекомендую программировать только полноэкранные приложения как наиболее быстродействующие, и забыть об оконных. Так как же быть – тупик? К сожалению, даже в SDK нет примеров решения этого вопроса. Впрочем ответить на него не так уж сложно – нужно лишь понять, что хочет DirectDraw и как это преподнести. Корень проблемы в том, что как только при копировании методом BltFast() часть поверхности выходит за край той поверхности, на которую она копируется (обычно задний буфер), вывод не осуществляется. В чём причина такого нелепого ограничения – думается, опять же в обеспечении наибольшего быстродействия. Например, вы планируете создать игру типа Tetris, а не скроллинговую стрелялку, и все ваши спрайты будут двигаться только в пределах экрана – но вот DirectDraw всё равно пришлось бы проверять их выход за границы, даже при отсутствии в этом необходимости. Хотя эту проблему можно было бы решить с помощью флагов при создании конкретной поверхности, но Microsoft этого не сделала. Ну что же, сделаем за неё эту работу. Обратите внимание на четвёртый параметр метода IDirectDrawSurface7.BltFast() – это адрес структуры типа TRect. Для чего он нужен? Как известно, назначение струтуры TRect в GDI API – указание положения и размера какой либо области путём задания левого верхнего и правого нижнего угла. Так вот, эта структура позволяет указать DirectDraw о необходимости вывести не всё изображение спрайта, а лишь его часть: Статьи по Delphi (006)… Воспользуемся этой структурой и будем вместо всего спрайта выводить какую-то его облать – ту, которая будет видна на экране, а невидимая будет находиться вне области, описываемой структурой TRect. Т. о. будет создаваться лишь иллюзия пребывания спрайта за пределами экрана. Например, спрайт выходит за границы экрана, как это показано на самом первом рисунке. Тогда выводимая область должна быть такой (показана красным цветом): Статьи по Delphi (006)… Теперь повоображайте и нарисуйте на бумаге, как будут выглядеть выводимые части спрайта при различных его положениях за границами экрана.

Вот код, ответственный за вывод части изображения:

// Предполагаем, что края спрайта не выходят за границы экрана
SetRect(rRect, 0, 0, SPRITE_WIDTH, SPRITE_HEIGHT);
// Проверяем выход кра¸в, и если такая ситуация имеет место, то корректируем
// положение области копирования на поверхности спрайта
if nX < 0 then
  rRect.Left := -nX;
if nY < 0 then
  rRect.Top := -nY;
if nX + SPRITE_WIDTH > SCREEN_WIDTH then
  rRect.Right := SCREEN_WIDTH - nX;
if nY + SPRITE_HEIGHT > SCREEN_HEIGHT then
  rRect.Bottom := SCREEN_HEIGHT - nY;
Где nX и nY – координаты левого верхнего угла спрайта. При выводе надо не забыть скорректировать их:
nX + rRect.Left, nY + rRect.Top
Вот и всё. Запустите проект на выполнение – вы увидите, что теперь свободно отбражается даже часть спрайта. Выведите его полностью за пределы экрана – начнёт жужжать встроенный динамик компьютера – эта возможность введена для проверки правильности алгоритма. Кстати, если необходимо вывести всю поверхность изображения, вместо адреса структуры следует передать nil – как это сделано для фона. Scale Иногда при выводе может понадобится растянуть или сжать объект по осям или просто увеличить или уменьшить его – для подобных эффектов DirectDraw предоставляет метод IDirectDrawSurface.Blt(). Он является хотя и более медленным, чем BltFast() – однако при этом более функционален. Так вот, мы снова будем указывать с помощью структуры TRect область вывода изображения – но уже на поверхности-приёмнике данных. Изменяя её размеры, можно добиться пропорционального или непропорционального изменения масштаба изображения по осям X и Y. Думаю, нет надобности описывать действия которые происходят в процедуре OnDraw(). Замечу лишь, что на современных видеокартах с полной аппаратной поддержкой DirectDraw эффект масштабирования выглядит гораздо привлекательнее, чем на «ветеранах». Transparent При выводе изображения на экран нередко встаёт проблема затирания заднего фона, а говоря языком профессионалов – проблема вывода нерегулярных спрайтов. Нерегулярный спрайт – это обычный прамоугольный спрайт, который содержит маску для указания прозрачных участков изображения. Пиксели, принадлежащие этим участкам, при выводе игнорируются – создаётся иллюзия прозрачности. В GDI нет прямого способа вывести нерегулярный спрайт – для этого необходимо подготовить изображение и отдельно маску. В чём недостатки такого подхода? Их много. Во-первых, требуется иметь два изображения – это увеличивает объём данных в памяти и на диске. Во-вторых, скорость вывода данного изображения падает вдвое, а ведь GDI и без этого не славится своей скоростью. В третьих, это дополнительная забота того, кто готовит изображение в графическом редакторе. В своё время автор даже написал небольшую утилиту, которая создавала маску для выбранного изображения и записывала её в отдельный файл. Но теперь об этом можно забыть.

DirectDraw предоставляет удобный инструмент для задания маски прозрачности. Цвета пикселей, которые игнорируются, называются «цветовыми ключами». Каждая поверхность может иметь свои цветовые ключи, причём их может быть несколько. Следующий фрагмент кода создаёт и присоединяет к поверхности «цветовой ключ», цвет которого – чёрный.

var
  ddck: TDDCOLORKEY;
begin
  ddck.dwColorSpaceLowValue := 0;
  ddck.dwColorSpaceHighValue := ddck.dwColorSpaceLowValue;
  pSprite.SetColorKey(DDCKEY_SRCBLT, @ddck);
Для указания прозрачного цвета, как видно, используется структура TDDCOLORKEY. В её двух полях необходимо указать нижнюю и верхнюю границу диапазона «прозрачных» цветов. Замечу, что использование диапазона цветов возможно только в случае, если такая возможность поддерживается аппаратно. Поэтому лучше ограничиться каким-либо одним цветом, как это сделано выше. После заполнения структуры TDDCOLORKEY необходимо вызвать метод IDirectDrawSurface7.SetColorKey(), где первый параметр – один из возможных флагов, второй - адрес структуры TDDCOLORKEY. Обычно используется флаг DDCKEY_SRCBLT, который указывает, что при копировании изображения будет использоваться цветовой ключ поверхности-источника. Другие флаги можно узнать из справочной службы DirectX SDK. Теперь о главном. В приведённом выше фрагменте кода в качестве маски задаются пиксели чёрного цвета. Как известно, нулевое значение обозначает отсутствие цвета во всех графических режимах – 16 цветов, 256, 65535 и т.д.

Поэтому можно смело присваивать 0 для чёрной маски в любом режиме. Однако, предположим, нам надо задать цветовой ключ в виде чистого синего цвета. Для 24- и 32-битного режима это можно сделать с помощью макроса (функции) из модуля windows.pas:

function RGB(r, g, b: Byte): COLORREF;
begin
  Result := (r or (g shl 8) or (b shl 16));
end;
Зарезервированное слово shl относится к сдвиговым операциям и сдвигает содержимое на указанное значение влево. Т.к. в этих графических режимах каждый из трёх цветов (красный, синий и зелёный) кодируется одним байтом, то значение каждого параметра функции должно лежать в пределах от 0 до 255. Вот как это можно представить графически: Статьи по Delphi (006)… Так, для задания цветового ключа в виде чистого синего цвета необходимо написать так:
ddck.dwColorSpaceLowValue := RGB( 0, 0, 255 );
ddck.dwColorSpaceHighValue := ddck.dwColorSpaceLowValue;
Ну и в том же духе, в полном соответствии с теорией цвета. А теперь попробуйте задать цветовой ключ для 16-битового режима. Ничего не получится. Почему? Дело в том, что цвет пикселя хранится в ячейке длиной в 16 бит, а цветовых составляющих – 3, появляется лишний бит, который чаще отдаётся зелёному цвету, а иногда просто не используется. Формат, где теряется лишний бит, обозначается 5-5-5 (на каждую цветовую составляющую по пять бит, а не одному байту), другой формат обозначается 5-6-5 (на зелёную составляющую выделяется 6 бит ). Понятно, что задание цвета с помощью функции RGB() для таких форматов ни к чему ни приведёт.

В своё время я довольно долго промучился с этой проблемой, тем более что в имеющейся литературе ничего об этом не сказано. В конце концов решил, что необходимо написать аналогичную к RGB() функцию, но об этом немного позже. Давайте сначала выясним, какой же формат использует установленная на нашем компьютере видеокарта. DirectDraw позволяет узнать это с помощью функции IDirectDrawSurface7.GetPixelFormat(). Единственным параметром необходимо передать адрес структуры TDDPIXELFORMAT. Вот фрагмент соответствующего кода:

var
  ddpf: TDDPIXELFORMAT;
begin
  ZeroMemory(@ddpf, SizeOf(TDDPIXELFORMAT));
  ddpf.dwSize := SizeOf(TDDPIXELFORMAT);
  pSprite.GetPixelFormat(ddpf);

Формат цветовых составляющих описывается в полях dwRBitMask, dwGBitMask и dwBBitMask структуры TDDPIXELFORMAT – но только в том случае, если битовое поле dwFlags содержит флаг DDPF_RGB – признак того, что поверхность создана в RGB-режиме. Значения полей dwRBitMask, dwGBitMask и dwBBitMask для режимов с разной глубиной палитры описываются в разделе dwRBitMask, dwGBitMask и dwBBitMask справочной службы DirectX SDK:

DDPF_RGB 16 R: 0x0000F800 
            G: 0x000007E0 
            B: 0x0000001F 
            A: 0x00000000
 
DDPF_RGB 16 R: 0x0000001F 
            G: 0x000007E0 
            B: 0x0000F800 
            A: 0x00000000
 
DDPF_RGB 16 R: 0x00007C00 
            G: 0x000003E0 
            B: 0x0000001F 
            A: 0x00000000
Запустите готовое приложение GetPixFormat из каталога DXCommon – и посмотрите, какой формат поверхности использует ваша карта в 16-битовом режиме. Скажу, что на компьютере с видеоакселератором GeForce 2 MX 420 получались значения из самой верхней ячейки – и это соответствует формату 5-6-5. По-моему, именно такой формат принят во всех современных видеокартах (заметьте, что во второй ячейке таблицы составляющие R и B переставлены местами). А вот, например, дедушка S3 Trio 3D/2X использует формат, описанный в нижней ячейке – опытным путём установлено, что это 5-5-5.

Вот как должен быть переписан макрос для формата 5-6-5:

function RGB565(r, g, b: Byte): COLORREF;
begin
  Result := ((r shl 11) or (g shl 5) or b);
end;
Графически битовая маска может быть представлена так: Статьи по Delphi (006)…

А вот как должен выглядеть макрос для формата 5-5-5:

function RGB555(r, g, b: Byte): COLORREF;
begin
  Result := ((r shl 10) or (g shl 5) or b);
end;
Графически битовая маска может быть представлена так: Статьи по Delphi (006)… Как видно, последний бит не используется. Обратите внимание, что группа битов, отвечающих за красную и синию составляющую, в 16-битовом режиме поменялись старшинством. А формат, описанный во второй ячейке таблицы, наоборот, по старшинству схож с 24- и 32-битовым режимами. Подозреваю, что используется этот формат довольно редко. Естетственно, что теперь максимальное значение, передаваемое в макросы (функции) RGB565() и RGB555(), соответствует значению 31, а для задания читого зелёного цвета в режиме 5-6-5 необходимо указать RGB565( 0, 63, 0 ), т. к. битов 6. Для того, чтобы наша DirectDraw-программа без проблем работала в обоих форматах, необходимо проверить текущий формат, запомнить его и при задании цветового ключа для поверхности вызвать соответствующий макрос. Всё это и делается в приложении Transparent – надеюсь при его разборе у вас не возникнет проблем. Не забудьте при копировании методом BltFast() указать флаг DDBLTFAST_SRCCOLORKEY. Fps Ещё один полезный пример – вывод текста на поверхность DirectDraw – в виде значения fps. Сама компонента не обладает такими средствами – DirectDraw изначально создавался лишь для максимально быстрого копирования одного изображения на другое. Для вывода текста необходимо использовать GDI. Как неоднократно упоминалось, GDI очень медленнен, и вывод текста – одна из функций, которая серьёзно может «притормозить» DirectDraw-программу. Поэтому необходимо пользоваться этой функцией как можно реже. Для взаимодействия DirectDraw c GDI введён простой метод IDirectDrawSurface7.GetDC(). Получив контектс, можно спокойно чертить в нём всеми мыслимыми функциями GDI. Метод IDirectDrawSurface7.ReleaseDC() переносит содержимое контекста в область памяти, занятую поверхностью DirectDraw и удаляет контекст. Откройте файл проекта fps.dpr. Т. к. функция TextOut() уже занята, функцию, отвечающую за вывод текста, пришлось назвать менее звучно – OutText(). Я не буду подробно описывать её, надеюсь, всё понятно. Для ускорения работы программы я поступил так: для вывода текста используется отдельная поверхность – именно на неё и выводится текст средствами GDI. Затем всё время поверхность просто копируется на задний буфер – это осуществляется гораздо быстрее, чем постоянный вывод текста на задний буфер, а когда появляется необходимость изменить текст – он снова выводится на нашу отдельную поверхность. Потребность изменить текст появляется лишь раз в секунду. Для вызова OutText() я использовал мультимедиа-таймер Windows. Значение fps наращивается при каждом построении кадра и обнуляется после вызова OutText(). И последнее. По-видимому, в операционной системе Windows 2000 функции GDI должны работать быстрее, т. к. эта ОС полностью 32-х разрядная. Но всё же рекомендую пользоваться описанным выше подходом. Text Ещё один пример вывода текста – но уже на задний буфер. Добавлен мною для полноты темы. Текст заданных размеров постоянно выводится на задний буфер без поверхности-посредника. При выводе текста я столкнулся с одной проблемой – это сглаживание краёв символов. В модуле windows.pas описана константа ANTIALIASED_QUALITY, но её задание в параметре fdwQuality функции CreateFont() ни к чему ни привело. Может быть, в Windows 9x и МЕ это значение не используется? Во всяком случае, константы ANTIALIASED_QUALITY и NONANTIALIASED_QUALITY в справке Delphi Help не описаны. Sound Эта программа – прямое продолжение моего первого примера по использованию DirectSound. Введено ряд усовершенствований: 1. Файл lowfunc.pas теперь полностью закончен и является практически прямым переводом файла wavread.cpp. Выражаю благодарность Max Morozov и iXania, которые помогли мне перевести некоторые сложные конструкции с языка C++ на Object Pascal, т. к. самому мне для этого не хватило квалификации. Теперь нет необходимости использовать отдельную динамическую библиотеку – весь код располагается в exe-файле. Всем спасибо.

2. Я решил написать небольшой класс TWave – он сам заботится об открытии звукового файла, чтении данных из него в звуковой буфер и проигрывании их. Функциональность класса не полная – это лишь пример. Благодаря ООП главный модуль main.pas серьёзно уменьшился, теперь для воспроизведения wav-файла средствами DirectSound достаточно написать:

var
  sound: TWave;
begin
  sound := TWave.Create();
  sound.OpenWaveFile(‘wavefile.wav’);
  sound.Play();
Правда, просто? GetDXVer и GetDXVerSetup Я решил заглянуть в некоторые области DirectX, до которых руки многих авторов книг по DirectX попросту «не доходят». Например, написание программы для определения текущей версии DirectX. Иногда это может быть очень полезно. Первый пример, который я предлагаю вашему вниманию – это GetDXVer. Это аналог из DirectX SDK для Visual C++. Функция GetDXVersion() ответственна за получение намера текущей версии DirectX. Каким образом она действует? Механизм прост, но достаточно громозд. Сначала загружается нужная динамическая библиотека из комплекса DirectX, например DDRAW.DLL или DINPUT.DLL. Затем получают адреса функций, которые экспортируют эти библиотеки – это «создающие» функции наподобие DirectDrawCreate() или DirectInputCreateA(). Затем при помощи этих функций и создаются нужные интерфейсы вроде IDirectDraw и т.п. Если на каком-то шаге происходит сбой, это означает, что данная функция или интерфейс не поддерживаются. Зная, в какой версии появился тот или иной интерфейс, можно выяснить текущую версию DirectX. Ещё одна функция, GetDXRegVersion(), извлекает полный порядковый номер из реестра Windows. Кстати, эта функция может в принципе читать любой строковый параметр из реестра и делает ненужным использование класса TRegistry, что очень важно, если мы хотим получить маленький по размерам исходный модуль. Пример имеет два недостатка: 1. Работает с некоторой задержкой. Для создания всех интерфейсов требуется некоторое время. Особо медленно создаётся интерфейс IDirectMusic. 2. Программа не способна определить номер версии, если он выше 8 – это принципиальный барьер. Ещё один пример – GetDXVerSetup – использует специальную функцию DirectXSetupGetVersion() из библиотеки dsetup.dll. эта библиотека не входит в стандартный run-time DirectX, а поставляется только с setup-программами установки DirectX на компьютер пользователя. При написании этого примера я столкнулся с двумя проблемами:

1. В Help-службе DirectX SDK 7 указаны такие возможные значения, которые могут быть помещены в переменную pdwVersion при вызове функции DirectXSetupGetVersion():

DirectX version   Value pointed to by pdwVersion 
DirectX 1         0x00040001 
DirectX 2         0x00040002 
DirectX 3         0x00040003 
DirectX 5.0       0x00040005 
DirectX 6.0       0x00040006 
DirectX 7.0       0x00040007 

А вот в Help-службе DirectX SDK 8 указаны такие:

DirectX version   Value pointed to by pdwVersion 
DirectX 1         0x00000000 
DirectX 2         0x00000000 
DirectX 3         0x00000000 
DirectX 5.0       0x00040005 
DirectX 6.0       0x00040006 
DirectX 7.0       0x00040007 
DirectX 8.0       0x00040008 
Зачем понадобилось обозначить версии 1, 2 и 3 как отсутствие DirectX – непонятно. Может быть, Microsoft посчитала, что эти ранние версии уже слишком устарели и не обеспечивают пользователя нужными мультимедиа-средствами? А раз так, то может быть лучше вообще известить об отсутствии DirectX? Может статься, что это просто ошибка в файле справки. Второй вариант более правдоподобен, первый – забавен. Используя файл directsetup.pas, мне не удалось экспортировать функцию DirectXSetupGetVersion() из библиотеки dsetup.dll. Дело в том, что она ищется в каталоге, по-видимому, указанном в ключе реестра HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Uninstall\DirectXDrivers, но я не обнаружил такой ключ в своём реестре. Так что воспользоваться этим файлом не представилось возможным. К тому же структуры TDirectXRegisterAppW2 не существует – приехали! Я самостоятельно перевёл файл dsetup.h из SDK 8 в файл dsetup.pas, пытаясь максимально точно соблюдать синтаксис структур и параметро функций. Может быть, кто-то им воспользуется. Недостаток приведенного метода в том, что вам придётся постоянно «таскать» библиотеку dsetup.dll вместе с исходной программой. Заключение Написание подобных статей – хороший стимул к тщательному изучению DirectX. Скажу, что чем больше работаю с этим комплексом, тем больше разных нюансов всплывает на поверхность. Надеюсь, что мои усилия хоть как-то помогут остальным желающим освоить этого игрового «монстра». Я надеюсь продолжить изучение DirectX и как только получится создать что-то стоящее, попробую поделиться сделанным с остальными. Напоследок хочу выразить особую благодарность Антону Ржешевскому за его дельные советы в освоении DirectX и не только.

Скачать примеры: Samples2.Zip (106 K).

DirectX для начинающих. Считывание и запись.

Автор: Виктор Кода Привет всем, кто интересуется программированием под DirectX на языке Object Pascal! Как и обещал, я продолжаю искать новый материал по DirectX, переводить его на язык Object Pascal и представлять всеобщему вниманию. Недавно у меня появилась идея снятия скриншотов с экрана DirectDraw-программы и записи изображения в простой bmp-файл - некоторые игры позволяют это делать, и я решил последовать их примеру. Потом я наткнулся на другой интересный материал - речь шла о загрузке изображения из bmp-файла без использования функции LoadImage(). Поэтому тема статьи всецело посвящена работе с bmp-файлами на "низком уровне". Замечу, что это немного сложные вещи, но мы ведь сложностей не боимся, правда? Иначе непонятно, зачем тогда заниматься изучением DirectX вообще. Замечания Каждому примеру для нормальной работы необходимо, чтобы файл data.bmp находился в том же каталоге, что и исполняемый файл. Для экономии места в архиве я разместил этот файл в папке первого примера, поэтому вам надо будет скопировать его и в остальные папки. Немного изменилась реализация модуля ddutils.pas - теперь функция CreateSurface() не требует адрес главного интерфейса IDirectDraw, а создаёт и уничтожает локальный интерфейс. Возможно, более практично с точки зрения Pascal-программирования было бы объявить глобальный для модуля ddutils.pas интерфейс IDirectDraw, а для создания и удаления интерфейса воспользоваться секциями initialization и finalization модуля. Также немного изменился стиль написания программ, теперь он совсем грешит С-подобным кодом :-) FASTFILE1

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

С помощью функции LoadImage() загружался растр и нам сообщался идентификатор загруженного растра в виде переменной типа HBITMAP; Функцией SelectObject() в контекст-источник выбирался созданный растр; Методом IDirectDrawSurface7.GetDC() создавался GDI-совместимый дескриптор контекста-приёмника и осуществлялась блокировка поверхности; Функцией GDI BitBlt() содержимое контекста-источника копировалось на контекст-приёмник. Методом IDirectDrawSurface7.ReleseDC() удалялся созданный контекст и осуществлялась разблокировка поверхности. В чём недостатки такого подхода? В универсальности. Не скажу, что функция LoadImage() очень медленная, но и очень быстрой она не является. Во всяком случае, программисты, писавшие её, не ставили своей задачей обеспечить максимальную скорость загрузки. Посмотрите справку по этой функции (файл win32sdk.hlp) - обилие параметров и констант, задаваемых при вызове, наводят на мысль о том, что она довольно "тяжеловесна". В частности, сказано, что с её помощью можно загружать не только растры из bmp-файлов разных форматов (в их число входят монохромные, 16- и 256-цветные палитровые файлы, а также беспалитровые 24-битные файлы), но и файлы иконок Windows и даже файлы, содержащие изображения курсоров. Естественно, всё это отрицательно сказывается на скорости загрузки - метод получается простым, но не самым эффективным. Поэтому часто программисты пишут собственные быстрые функции для загрузки файлов какого-то определённого формата. В этом примере я реализовал отдельную функцию, которая предназначена для загрузки данных из 24-битного беспалитрового файла формата bmp. Прежде чем приступить к рассмотрению работы функции, необходимо в общих чертах представить себе, каким образом записывается информация в bmp-файле. На рис. 1 показана структура беспалитрового 24-битного файла. Статьи по Delphi (006)… Рис.1. Структура файла BMP, не содержащего палитру

Хранящийся на диске файл DIB, обычно с расширением .bmp, как видно, начинается со структуры BITMAPFILEHEADER, позволяющей начать работу с файлом. Вот как эта структура описана в файле windows.pas:

tagBITMAPFILEHEADER = packed record
  bfType: Word;      // Тип файла. Должен содержать 'BM' ($4d42)
  bfSize: DWORD;     // Размер файла в байтах
  bfReserved1: Word; // Зарезервировано, должен быть нуль
  bfReserved2: Word; // Зарезервировано, должен быть нуль
  bfOffBits: DWORD;  // Смещение от начала файла до гафических данных
end;
BITMAPFILEHEADER = tagBITMAPFILEHEADER;

Следом за структурой BITMAPFILEHEADER следует стуктура BITMAPINFO:

tagBITMAPINFO = packed record
  bmiHeader: TBitmapInfoHeader; // Структура BITMAPINFOHEADER
  bmiColors: array[0..0] of TRGBQuad; // RGB-триплекс
end;
BITMAPINFO = tagBITMAPINFO;

Фактически, стуктура BITMAPINFO включает в себя ещё одну структуру - BITMAPINFOHEADER:

tagBITMAPINFOHEADER = packed record
  biSize: DWORD; // Размер самой структуры в байтах
  biWidth: Longint; // Ширина растра в пикселях
  biHeight: Longint; // Высота растра в пикселях
  biPlanes: Word; // Количество плоскостей (всегда 1)
  biBitCount: Word; // Количество бит на 1 пиксель
  biCompression: DWORD; // Тип сжатия (BI_RGB - без сжатия)
  biSizeImage: DWORD; // Размер изображения в байтах (обычно 0)
  biXPelsPerMeter: Longint; // А эти данные
  biYPelsPerMeter: Longint; // нам вообще
  biClrUsed: DWORD; // никогда не
  biClrImportant: DWORD; // понадобятся :)
end;
BITMAPINFOHEADER = tagBITMAPINFOHEADER;
Эта структура для нас наиболее интересна, так как опираясь на её данные, и будет производиться загрузка растра. Несмотря на обилие полей, нам понадобятся только некоторые - это biWidth, biHeight и ещё поле biBitCount - для проверки, является ли файл 24-битным. После этих структур начинаются графические данные. В 24-битном файле каждый пиксель кодируется 3 байтами - на каждую составляющую R, G, B - по одному байту. Значение каждой составляющей может варьироваться от 0 до 255.

Откройте файл проекта и найдите функцию LoadData(). Она вызывает другую функцию - LoadBitMap(). Я разместил её в файле ddutils.pas, вот её прототип.

function LoadBitMap(name: pchar; pbi: PBITMAPINFO): pointer;
Первым параметром передаётся имя загружаемого файла, вторым - адрес структуры BITMAPINFO, структура понадобится после вызова функции LoadBitMap(). Для считывания данных с диска я использую API-функции, предоставляемые ОС, а не библиотечные функции Delphi. Причина - немного более высокое быстродействие, при том, что сами функции просты в обращении и предоставляют некоторые средства контроля при чтении-записи.

Вот как, например, открывается файл:

hFile := CreateFile(name, GENERIC_READ, FILE_SHARE_READ, nil,
  OPEN_EXISTING, 0, 0);
if hFile = INVALID_HANDLE_VALUE then
  exit;
Переменная hFile - это дескриптор открытого файла. Проверить, открыт ли он в самом деле можно, сравнив дескриптор с константой INVALID_HANDLE_VALUE. Далее считывается структура BITMAPFILEHEADER:
ReadFile( hFile, bfh, sizeof( BITMAPFILEHEADER ), dwBytesRead, nil );
Замечу, что вторым параметром функции ReadFile() передаётся сама структура, куда будут записаны данные, третьим - количество байт, которые надо прочитать. Четвёртые параметр должен присутствовать обязательно, в него функция запишет количество реально прочитанных байт. Для пущей надёжности можно сравнить это значение с размером структуры BITMAPFILEHEADER, и если значения не совпадают, объявить об ошибке. Далее считывается структура BITMAPINFOHEADER:
ReadFile( hFile, bi, sizeof( BITMAPINFOHEADER ), dwBytesRead, nil );
Думаю, надо объяснить, почему здесь мы читаем только данные структуры BITMAPINFOHEADER, и не считываем массив bmiColors. Дело в том, что этот массив в структуре BITMAPINFO, там, куда мы её передадим позже, всё равно не используется. Однако он входит в состав общих графических данных, поэтому мы считаем его вместе с ними, а в структуре bi массив bmiColors оставим пустым. Далее идёт считывание графических данных. Прежде всего необходимо определить, какой размер они имеют:
// Определяем размер DIB
dwDIBSize := GetFileSize( hFile, nil ) - sizeof( BITMAPFILEHEADER ) - sizeof( BITMAPINFOHEADER );
То есть от размера bmp-файла отнимаются размеры описанных выше структур. Замечу, что для палитровых файлов придётся учитывать ещё и размер палитры. Далее, выделяется участок в оперативной памяти нужной длины и получается указатель на него:
// Выделяем участок памяти
result := pbyte(GlobalAllocPtr( GMEM_MOVEABLE, dwDIBSize ));

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

// Читаем данные DIB
ReadFile(hFile, result^, dwDIBSize, dwBytesRead, nil);
// Закрываем файл
CloseHandle(hFile);
Описанная функция работает только с 24-битными несжатыми растрами. Использование 256-цветных палитровых файлов я считаю нецелесообразным, т. к. качество изображения в них не удовлетворяет требованиям современной компьтерной графики. Итак, функция LoadBitMap() загрузила в оперативную память битовые данные, формирующие изображение и вернула указатель на них как результат функции. Вернёмся теперь обратно к функции LoadData(). Первый шаг сделан - произведена максимально быстрая загрузка данных из файла (я не вижу способа, как можно ещё как-нибудь ускорить этот процесс). Теперь надо сделать второй шаг. В чём он состоит? Для ускорения загруки в играх и других программах все графические данные объединяются в один или несколько больших файлов. Такую реализацию, например, можно увидеть в игре Donuts из DirectX SDK 7-8. Такое объединение очень полезно при условии, что файл на жестком диске нефрагментирован. Данный метод, безусловно, уменьшает время загрузки, но как будет видно далее, добавляет лишних хлопот программисту.

Я подготовил простой bmp-файл, в котором хранится изображение для фона и десять "кадров", которые будут последовательно сменять друг друга. Как же загрузить эти данные на поверхности DirectDraw? Есть два пути:

Воспользоваться методами Lock() и Unlock() интерфейса IDirectDrawSurface7, и осуществлять прямое копирование данных функцией CopyMemory(). Это решение оптимально по скоростным характеристикам, но уж очень сложное: необходимо учитывать абсолютно все нюансы формата поверхности, на которую копируются данные - а их очень много, ведь формат меняется в зависимости от глубины цвета текущего видеорежима - 8, 16, 24, 32 бит. К тому же, выигрыш в этом случае может оказаться совсем небольшим. Использовать функцию GDI StretchDIBits(). Она предназначена для копирования в контекст данных, расположеных не в другом контексте, а находящихся просто в указаннном участке памяти. Может сложиться впечатление, что эта функция достаточно медленна - из-за угрожающей приставки "Stretch". Однако если будут копироваться участки битов, одинаковые по высоте и ширине, то в этом случае функция, думается, должна работать быстрее. Я решил использовать второй способ. Итак, первым делом создадим поверхность для фона:
CreateSurface( g_pWallpaper, 640, 480 );

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

if g_pWallpaper.GetDC(DC) = DD_OK then
begin
  // Копируем битовый массив в контекст
  StretchDIBits(DC,
    0, 0, 640, 480,
    0, 64, 640, 480,
    pBits, bi,
    0, SRCCOPY);
  g_pWallpaper.ReleaseDC(DC);
end;
Заметьте, что растр в файле (и памяти) хранится в перевёрнутом виде, поэтому ось Y битовой карты направлена вверх. Это необходимо учитывать при задании области копирования. Для копирования массива битов функции StretchDIBits() необходимо передать адрес массива в памяти, а также адрес структуры BITMAPINFO - опираясь на неё, она сможет правильно произвести копирование.

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

// Освободили битовый массив!
pBits := nil;
Вот и всё, можно приступать к отрисовке экрана. Вообще такая схема объединения всех данных в один большой или несколько больших файлов оправдана в крупных программах и играх - там, где набор различных изображений достигает сотен штук. На этапе черновой разработки целесообразно загружать растры из отдельных файлов, а в конце, перед релизом программы, в процессе доводки и оптимизации, объединить всё в один большой файл. При этом придётся немного поработать в графическом редакторе, разместив отдельные растры оптимальным способом, не оставляя "пустых" мест. Также целесообразно подготовить массив типа TRect, которыи будет описывать область каждой картинки в этом растре, и пользоваться им в функции загрузки. FASTFILE2 Предыдущий пример продемонстрировал способ ускорить загрузку файла в память. Однако перенос данных на конкретные поверхности усложнился, да и постоянный вызов функции StretchDIBits() должен отрица- тельно сказаться на времени копирования. Чтобы не копировать каждый раз содержимое нового участка памяти на отдельную поверхность DirectDraw функцией StretchDIBits(), я решил все данные из памяти скопировать на одну большую поверхность DirectDraw, а потом копировать её содержимое по участкам на другие поверхности методом IDirectDrawSurface7.BltFast(). Казалось бы, такое двойное копирование - из памяти на общую поверхность, а потом с этой поверхности на отдельные поверхности - довольно долгий процесс. Однако если память видеокарты достаточно большая (32-64 Мб), можно позволить программе разместить все созданные поверхности в памяти видеокарты, и тогда копирование методом IDirectDrawSurface7.BltFast() будет происходить очень быстро. При большом объёме графических данных этот способ предпочтителен. К тому же данные на общей поверхности DirectDraw хранятся в нормальном, а не перевёрнутом виде, что облегчает программисту перенос. Этот способ и демонстрирует данный проект. Всё остальное осталось без изменений. Наконец, существует ещё один, наиболее эффективный путь. Можно не заниматься копированием растра с общей на отдельные поверхности, а переносить растр на дополнительный буфер прямо с общей поверхности.

Например:

g_pBackBuffer.BltFast( x, y, g_pMovie[ frame ], nil, DDBLTFAST_WAIT );
Однако можно третьим параметром указать общую data-поверхность, а четвертым - не nil, а область на этой поверхности:
g_pBackBuffer.BltFast( x, y, g_pDataSurface, arrayRect[ FRAME_01 ], DDBLTFAST_WAIT );
Тогда можно не создавать отдельные поверхности и не заниматься копированием данных. Однако есть и недостатки. Например, память видеокарты должна быть достаточно большой - если памяти не хватит для размещения всей data-поверхности, DirectDraw разместит её в системной памяти, и процесс вывода изображения резко замедлится - вот вам и оптимизация! Также могут возникнуть проблемы с цветовыми ключами и корректным отображением спрайтов. В общем, решение половинчатое. PRINTSCREEN Заманчиво, когда в программе имеется возможность делать "снимки" экрана и сразу записывать их в файл. Этот пример ничем не отличается от предыдущих, за исключением того, что при нажатии на клавишу "Пробел" делается запись содержимого экрана в файл screen.bmp. Функция, которая проделывает эту работу, находится в файле pscreen.pas. Рассмотрим её.

Первым делом создаётся новый файл или открывается для перезаписи старый:

// созда¸м файл с заданным именем, в него будет производиться запись
hFile := CreateFile(szFileName, GENERIC_WRITE, FILE_SHARE_READ, nil,
  CREATE_ALWAYS, 0, 0);
if hFile = INVALID_HANDLE_VALUE then
begin
  CloseHandle(hFile);
  exit;
end;
// Затем нам необходимо получить данные о поверхности(здесь в функцию передан
// дополнительный буфер):
// подготавливаем структуру TDDSURFACEDESC2
ZeroMemory(@ddsd2, sizeof(TDDSURFACEDESC2));
ddsd2.dwSize := sizeof(TDDSURFACEDESC2);
// получаем формат поверхности
pSurface.GetSurfaceDesc(ddsd2);
dwWidth := ddsd2.dwWidth;
dwHeight := ddsd2.dwHeight;
dwBPP := ddsd2.ddpfPixelFormat.dwRGBBitCount;
// Структура ddsd2 используется дополнительно в методе
// Lock()поверхности.Заблокировав поверхность, можно обратится к е¸ содержимому
// для чтения данных:
// блокируем поверхность DirectDraw
if (FAILED(pSurface.Lock(nil, ddsd2, DDLOCK_WAIT, 0))) then
  exit;
Затем необходимо выделить достаточное количество памяти под массив пикселей. Число три в конце выражения - это потому, что вывод будет осуществляться в 24-битный файл:
pPixels := pbyte(GlobalAllocPtr( GMEM_MOVEABLE, dwWidth * dwHeight * 3 ));
Затем начинается главное. Т. к. формат пикселя поверхности в каждом из графических режимов различается, необходимо предусмотреть все особенности размещения данных. Бессмысленно подробно описывать все операции - они запутанны и сложны. Мне понадобилось некоторое количество времени, чтобы правильно перевести все операции с указателями с языка C++ в контекст Object Pascal. Операции с указателями на этом языке получаются довольно путаными, малейшая оплошность приводит к тому, что обычно в файл записывается не тот участок памяти (получается мешанина из пикселей), или запись вообще не происходит. Обратите внимание на такую строку:
pixel := PDWORD(DWORD(ddsd2.lpSurface) + i * 4 + j * ddsd2.lPitch)^;
Здесь определяется цвет нового пикселя поверхности. ddsd2.lpSurface - это указатель на начало данных поверхности, а ddsd2.lPitch - шаг поверхности, учитывать его нужно обязательно. После того, как данные скопированы в массив, поверхность обязательно нужно разблокировать. Теперь можно начать запись данных в файл.

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

// закрываем файл
CloseHandle(hFile);
pPixels := nil;
Функция получилась громоздкой, согласен. Однако иного способа не существует - во всём виноват формат поверхности. Кстати, я не учёл режим в 256 цветов - опять же по причине анахронизма.

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

Скачать примеры: DirectX3.zip (95K)

DirectX (Игровой SDK) 1-2.

Автор: Стас Бакулин
Сидит как то программер, налаживает свою пятилетнюю работу, обложился справочниками, доками, FAQ-ами.....неделю сидит - ни фига не получается. Мужик уже похудел весь, зарос щетиной... Входит в комнату его мама и говорит ему (с горечью в голосе): - Ну... все играешь?

Модель компонентных объектов (СОМ)

Перед углублением и изнурительные подробности DirectDraw сначала несколько слов о модели компонентных объектов - кратко СОМ. Delphi использует объектно-ориентированный язык программирования Object Pascal. Дизайнеры Delphi решили сделать родные Delphi объекты полностью совместимыми с СОМ и OLE. Это большая новость для нас, потому что DirectDraw использует интерфейс СОМ и поэтому из Delphi получить к нему доступ достаточно просто.

Объекты СОМ подробно освещены в разделе Delphi. Но для того, чтобы сэкономить ваше время, предоставлю краткий обзор. В Delphi вы работаете с объектом СОМ практически так же, как и с другим объектом. Объекты СОМ выглядят по сути как обычные объекты Delphi. Они имеют методы, которые вы вызываете для доступа к их услугам. Тем не менее, они не имеют полей или свойств. Главным отличием является то, что вы вызываете метод Release вместо метода Free, если вы хотите освободить эти объекты.

Вы также никогда не создаете объект СОМ путем вызова конструктора. Вместо этого вы вызываете функцию в DirectDraw для создания главного объекта DirectDraw. Этот объект имеет дальнейшие методы, которые вы используете для создания других методов. Помимо этих двух вопросов вы можете фактически думать о них как об объектах Delphi.

Объекты СОМ DirectDraw определяются в довольно сложном файле-заголовке на С, который поставляется с Game SDK. Однако я перевел это в модуль импорта, который вы можете использовать в Delphi. Это файл DDraw.pas на сопровождающем CD-ROM. Для того, чтобы получить доступ к DirectDraw, просто добавьте DDraw в предложение uses.

DirectDraw

DirectDraw может оказаться довольно каверзным в использовании. На первый взляд он кажется простым; существует только несколько СОМ-классов и они не имеют большого количества методов. Однако DirectDraw использует записи для определения всех видов различных параметров при создании своих объектов. На первый взгляд они выглядят действительно устрашающе. Вы можете найти их в справочных файлах Game SDK, начиная с букв DD, например DDSurfaceDesc. Являясь API низкого уровня, существует множество опций и параметров, которые допускают разницу в спецификациях аппаратного обеспечения и возможностях. К счастью, в большинстве случаев можно проигнорировать множеством этих опций. Самой большой проблемой в момент написания этой книги является недостаток информации в GDK документации, которая описывает, какие комбинации опций разрешаются, поэтому для того, чтобы помочь вам найти путь через минное поле, эта глава поэтапно проходит по всем стадиям создания приложения DirectDraw. Я представляю код, который добавляется на каждом этапе и использует его для объяснения аспекта DirectDraw, также как и рабочий пример, на основании которого можно строить свои собственные программы.

Объект IDirectDraw

DLL с DirectDraw фактически имеет самый простой из интерфейсов. Она экспортирует только одну функцию: DirectDrawCreate. Вы используете эту функцию для создания СОМ-объекта IDirectDraw, который открывает остальную часть API. Таким образом, первое, что должен сделать пример - создать один из таких объектов. Вы делаете это в обработчике события OnCreate формы и разрушаете его в OnDestroy. Лучшим местом хранения объекта является приватное поле главной формы. Листинг 1 содержит базовый код для осуществления этого.

Листинг 1 Создание объекта IDirectDraw.

unit Uniti;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, DDraw;
type
  TFormI = class (TForm)
    procedure FormCreate (Sender: TObject);
    procedure FormDestroy (Sender: TObject) ;
  private
    DirectDraw : IDirectDraw ; // главный объект DirectDraw
end;
var
  Formi: TFormI;
implementation
procedure TFormI. FormCreate (Sender: TObject);
begin
  { создать СОМ-объект DirectDraw }
  if DirectDrawCreate( nil, DirectDraw, nil ) <> DD_OK then
    raise Exception. Create ( 'Failed to create IDirectDraw object' ) ;
end;
procedure TFormI. FormDestroy (Sender: TObject);
begin
  { создать СОМ-объект DirectDraw эа счет вызова его метода Release }
  if Assigned ( DirectDraw ) then
    DirectDraw. Release ;
end;
end.

Вы можете скачать этот тривиальный пример DDDemo1 здесь (пока не можете, я надеюсь это будет позже, прим. Lel). Он не делает что-либо очевидного, когда вы запускаете его, поэтому не ожидайте слишком многого. Я включаю его для того, чтобы показать, как мало кода требуется для создания и освобождения СОМ-объекта DirectDraw. Это действительно очень просто.

Коды возврата DirectDraw и исключения Delphi

Подавляющее большинство функций DirectDraw возвращает результирующий код целого типа с именем HResult, о котором вы можете думать как об integer. Файл DDraw.pas имеет все возможные константы ошибок, занесенные в список, а справочный файл Game SDK указывает на возможные коды ошибки, возвращаемые каждой функцией. Вы можете проверить результаты этих функций, и в болыпинстце случаев возбудить исключение, если результат отличается от DD_OK.

Однако имеется ряд проблем с использованием исключений, поскольку вы переключаетесь на специальный экранный режим. Это означает, что вы не способны видеть Delphi IDE, когда он разрушается или прерывается в момент исключения, и ваша программа кажется замороженной. Установка точки прерывания обычно приводит в результате к одной и той же проблеме: приложение останавливается как раз в точке прерывания, но вы не имеете возможность увидеть Delphi. Добро пожаловать в программирование игр в среде Windows! Я обсуждаю это более подробно несколько позже.

Переключение на полноэкранный режим

Следующее, что необходимо сделать, - это переключить дисплей в режим перелистывания страниц. Когда вы это делаете, становится видимым только ваше приложение. Оно занимает весь экран. Любые другие приложения Windows, которые находятся ц режиме выполнения, подобные Windows Explorer, продолжают работать и могут записывать выходные данные на то, что они считают экраном. Вы не видите, как выглядят выходные данные, потому что другие приложения все еще используют GDI для выходных данных, которому ничего не известно о DirectDraw. Но вам вовсе нет необходимости беспокоиться об этом. GDI будет продолжать беспечно писать в экранную память, хотя вы вдействительности не сможете увидеть его выходные данные.

Путем переключения в специальный режим отображения данных вы занимаете весь экран. Как правило, вы можете запускать множество регулярных приложений среды Windows в одно и то же время; их окна перекрываются и благодаря GDI дела идут прекрасно. Но что произойдет, если вы попытаетесь запустить два и более полноэкранных DirectDraw-приложений в одно и то же время? Ответ - только одному разрешен доступ к полному экрану. DirectDraw управляет этим, предполагая, что вы имеете исключительный доступ к экранной карте перед изменением режима. Вы сделаете это установкой коиперативнчгн уровня объекта DirectDraw в Exclusive. DirectDraw поддерживает эксклюзивный уровень доступа только для одного приложения одновременно. Если вы попытаетесь получить эксклюзивный доступ и какое-нибудь другое приложение уже его имеет, вызов не удастся. Подобным же образом, если вы попытаетесь изменить режимы отображения данных без приобретения эксклюзивного доступа, этот вызов не удастся. Таким образом, попытайтесь получить эксклюзивный доступ и затем переключите режимы отображения.

Здесь необходимо отметить, что вы должны предоставить описатель окна SetCooperativeLevel. DirectDraw изменяет размеры этого окна автоматически, так что оно заполняет экран в новом режиме отображения данных. Вы должны передать описатель формы в SetCooperativeLevel. Ввиду того, что описатель окна не был создан до времени вызова OnCreate, вы должны все это сделать и событии OnShow. Листинг 2 показывает, как это сделать.

Листинг 2 Переключение в полноэкранный режим в OnShow.

procedure TForml.FormShow(Sender: TObject);
begin
  if DirectDraw.SetCooperativeLevel(Handle,
  DDSCI_EXC: LUSIVE or DDSCI_FUbbSCREEN ) <> DD_OK then
    raise Exception.Create('Unable to acquire exclusive full-screen access');
  if DirectDraw.SetDisplayMode(640, 480, 8) <> DD_OK then
    raise Exception.Create('Unable to set new display mode');
end;

Пока все в порядке. Ничего тут сложного нет. Если вы запустите пример прямо сейчас, ваше приложение переключит режимы и вы увидите, как форма заполнит экран. Если вы передвинете или измените ее размеры, вы увидите за ним Delphi. Вы все еще смотрите на поверхность вывода GDI. GDI может благополучно выводить данные в этих различных режимах, так что вы увидите свои обычные приложения Windows так долго, сколько эта поверхность будет оставаться на переднем плане. Но ввиду того, что вы создаете приложение с мелькающими страницами, это не совсем то, что нам нужно. Директория DDDemo2 содержит изложенные примеры.

Добавление обработчика исключений приложения

Как я уже упоминал ранее, тот факт, что DirectDraw занимает полный экран может вызвать проблему с обработкой исключений. Когда исключение возбуждается, по умолчанию Delphi IDE попадает в отладчик программы и приостанавливает исполнение программы, устанавливая кодовое окно на строке, содержащей ошибку. Проблема заключается в том, что когда происходит мелькание страниц вы, вероятно, не сможете увидеть IDE и приложение будет выглядеть замороженным. Еще хуже, если вам удастся продолжить исполнение, или на опции IDE окажется выключенной Break on exception (Останавливаться, если возбуждено исключение), то вы можете не увидеть окна сообщения, которое появляется с сообщением исключения.

Один из способов избежать этот сценарии отменить маркер на флажке Break on exception в IDE (TooIsjOptions menu) и установить в своем приложении специальный обработчик исключений приложения. Этот обработчик должен переключаться на поверхность GDI перед тем, как показать сообщение исключения. Это намного легче, чем может показаться. Все, что вам необходимо сделать, - создать собственный private-метод в форме и присвоить его AppHcation,OnException в OnCreate формы. Не забывайте установить его обратно в nil в OnDestroy. Новый описатель может использовать метод SwitchToGDISurface объекта IDirectDraw перед вызовом MessageDIg. Листинг 3 показывает обработчик исключения.

Листинг 3 Обработчик исключений приложения.

procedure TForml.ExceptionHandler(Sender: TObject; E: Exception);
begin
  if Assigned(DirectDraw) then
    DirectDraw.FlipToGDISurface;
  MessageDIgt E.message, mtError, [mbOK], 0);
end;

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

Application-OnException :== ExceptionHandler;

Помните, что нужно выключить Break on exception (в TooIsfOptions). Как только вы наберетесь больше опыта, вы сможете включить эту опцию снова для специфических заданий отладки. Однако, если ваше приложение вызовет исключение, пока поверхность GDI невидима, IDE возьмет свое и вы ничего не увидите. Нажатие F9 должно вызвать повторное исполнение, а нажатие Ctrl-F2 вернет приложение в исходное состояние и возвратить вас в Delphi.

DirectX (Игровой SDK) 3-4.

Автор: Стас Бакулин Поверхности отображения Теперь вы готовы создавать поверхности отображения. В DirectDraw поверхность отображения представляет собой линейную область экранной памяти, к которой можно получить непосредственный доступ для манипуляций. Поверхность отображения, которую вы видите на экране, называется основной поверхностью. Она представляет память видимого кадрового буфера на карте отображения. Вы также можете иметь невидимые поверхности, которые определяются как внеэкранные, или оверлейные поверхности. Подобное может существовать либо в регулярной системной памяти, либо во внеэкранной области памяти на самой графической карте. Для того, чтобы создать ситуацию с мелькающими страницами, необходима основная поверхность и, по крайней мере, одна внеэкранная поверхность для осуществления отображения. Для того, чтобы внеэкранная поверхность могла появляться и исчезать на экране, онадолжна находится в видеопамяти. Тем не менее, DirectDraw пытается создать поверхности в видеопамяти по умолчанию, поэтому нет необходимости предпринимать что-либо специально. Существует способ для создания основной поверхности и одной и более сменных поверхностей в одно и то же время за счет создания комплексной поверхности. Еще один аспект в создании комплексной (составной) поверхности заключается в том, что вы можете освободить все поверхности в комплексной цепи сменных поверхностей путем высвобождения самой комплексной поверхности. Для примера создадим комплексную поверхность посредством одной вспомогательной буферной поверхности. Фоновые поверхности, которые создаются в качестве части комплексной, известны как Неявные поверхности. Существует большое число операций, которые вы не сможете осуществить с помощью Неявных поверхностей, например, отсоединить их от основной поверхности или освободить их независимо от основной поверхности. Однако, комплексные поверхности намного проще создавать, потому что DirectDraw создает фоновые буфера и соединяет их с основной поверхностью. В этой связи я должен затронуть вопрос сложности DirectDraw, поскольку необходимо заполнять поля и записи TDDSurfaceDesc. Если вы прочитаете об этом и справке DirectDraw, вы сможете увидеть, что все это выглядит довольно ужасно! Но как я уже говорил, вы можете счастливо игнорировать большинство из этих полей. Листинг 4 представляет код, который необходимо добавить в обработчик OnShow для создания комплексной поверхности.

Листинг 4 Создание комплексной поверхности.

{ заполнить описатель DirectDrawSurface перед созданием поверхности }
FillChar(DDSurfaceDesc, Si2e0f(DDSurfaceDesc), 0);
with DDSurfaceDesc do
begin
  dwSize := SizeOf(DDSurfaceDesc);
  dwFlags := DDSD_CAPS or DDSD_BACKBUFFERCOUNT;
  ddSCaps.dwCaps := DDSCAPS_COMPLEX or DDSCAPS FLIP or
  DDSCAPS_PRIMARYSURFACE;
  dwBackBufferCount: = 1;
end;

Листинг 7 Тестирование нажатия клавиш Escape и F12.

procedure TForml.ForinKeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin
  // если нажаты клавиши Escape или F12, завершить приложение
  case Key of
    VK_ESCAPE, VK_F12: Close;
  end;
end;
Вы можете скачать этот пример u DDDemo3 здесь. Если вы запустите его, иы уиидите на экране мелькание сменяющих друг друга поверхностей GDI, которые содержат формы размером с экран, и фоновый буфер, который, вероятно, заполнен различными битами "остатков" отображения. Помните, для выхода следует нажать Esc или F12 (или конечно же Alt+F4). Получение доступа к фоновому буферу Теперь, когда вы познали основы приложения смены страниц, вы, вероятно, захотите что-нибудь с ним сделать. Вы нуждаетесь в том, чтобы иметь возможность рисовать на поверхности фонового буфера. Однако, в последней секции вы создали комплексную поверхность, которая автоматически создала для нас фоновый буфер. Проблема заключается в том, что функция CreateSurface заполнила поле PrimaryField (основная поверхность), и вы должны получить доступ к фоновому буферу. Для этого можно вызвать метод GetAttachedSurface. Добавьте поле фонового буфера BackBuffer к форме и код из листинга 8 - к OnShow:

Листинг 8 Доступ к поверхности фонового буфера.

{ получить фонов зй буфер }
DDSCaps.dwCaps: = DDSCAPS_BACKBUFFER;
if PrimarySurface.GetAttachedSurface(DDSCaps, BackBuffer) <> DD_OK then
  raise Exception.Create('Failed to get back buffer surface');
DDSCaps является локальной переменной типа TDDSCaps, которая добавляется к обработчику FormShow. Вы заполняете флажки для необходимой присоединенной поверхности и вызываете GetAttachedSurface. В этом случае вам необходим фоновый буфер. Метод может вернуть только одну поверхность. Вызов напрасен, если более чем одна присоединенная поверхность соответствует переданным флажкам DDSCaps. Однако, не имеет значения, сколько фоновых поверхностей вы создали, существует только одна поверхность с флажком фонового буфера, и она является первой в цепи сменных поверхностей после основной. Если необходимо получить все присоединенные поверхности, можно вызвать функцию EnumAttachedSurfaces. Восстановление поверхностей Одна из многих особенностей DirectDraw заключается в том, что поверхности могут потерять свою память по многим причинам; например, когда изменяется режим отображения. Если это происходит, вы должны вызвать метод Restore поверхности, чтобы получить свою память обратно. Вы также должны перерисовать поверхность. Это несколько напоминает то, как у вас возникает необходимость нарисовать окно в обычном программировании для Windows, когда оно перекрывается и нуждается в обновлении. Большинство из функций IDirectDrawSurface могут возвратить результат DDERR_SLIRFACELOST. Когда это происходит, вы должны восстановить поверхность и перерисовать ее. Многие из этих функций также могут вернуть DDERR_WASSTILLDRAWING, что по сути означает, что аппаратное обеспечение занято и запрос необходимо повторять до тех пор, пока вы не добьетесь успеха, или пока вы не получите иное сообщение об ошибке. Вот основополагающая логическая схема, использующая метод Flip. Этот пример предназначен только для того, чтобыввести вас в курс. Он не перерисовывает поверхности. Смотрите листинг 9.

Листинг 9 "Традиционный" код для проверки и восстановления поверхности.

repeat
  DDResult := PrimarySurf асе.Flip(nil, 0);
  case DDResult of
    DD_OK: break;
    DDERR_SURFACELOST:
      begin
        DDResult := PrimarySurface.Restore();
        if DDResult <> DD_OK then
          break;
      end;
  else
  if DDResult <> DDERR_WASSTILLDRAWING then
    break
  end;
until false;

Самое надоедливое то, что вам необходим подобный код практически для каждого вызова метода IDirectDrawSurface. Всякий раз, когда спецификация вызова в справке Game SDK содержит DERR_SLJRFACELOST в качестве возможного результата, это необходимо. Но Pascal-структурированный язык высокого уровня, не так ли? Таким образом, почему бы не написать небольшой сервисный метод для оказания такой помощи? Вот этот метод с именем одного из моих любимых шоу. (Оно не дает возможности себя забыть!) Оно представлено в листинге 10.

DirectX (Игровой SDK) 5-6.

Автор: Стас Бакулин

Листинг 10 функция MakeltSo для оказание помощи в восстановлении поверхности.

function TForinl.MakeltSo(DDResult: HResult): boolean;
begin
  { утилита для предоставления помощи в восстановлении поверхностей }
  case DDResult of
    DD_OK: Result := true;
    DDERR_SURFACELOST: Result := RestoreSurfaces <> DD_OK;
  else
    Result := DDResult <> DDERR_WASSTILLDRAWING;
  end;
end;

Последний метод иосстанаиливает поцерхность i3 случае необходимости и затем вызывает функцию RestoreSurface, которую я вам сейчас представлю. Но сначала вот как следует ее использовать, применяя Flip, как в предыдущем примере:

repeat
  ...
until
  MakeltSo(PrimarySurf асе.Flip(nil, DDFblP_WAIT));
Теперь я уверен, вы согласитесь, что это намного аккуратней и приятней, чем постоянно дублировать код, который я продемонстрировал ранее. Flip вызывается непрерывно, пока не достигнет успеха, либо пока не возникнет серьезная про блема. Я мог бы вызвать исключение в MakeltSo, если бы возникла неисправимая проблема. Примеры Game SDK, будучи написанными на С без обработки исключений, просто игнорируют результаты ошибки. Однако, если вы хотите использовать исключения, измените MakeltSo, как показано в листинге 11.

Листинг 11 Необязательная MakeltSo, которая вызывает исключения.

function TFormI.MakeltSo(DDResult: HResult): boolean;
begin
  { утилита для предоставления помощи в восстановлении
  поверхностей - версия с исключениями }
  Result := false;
  case DDResult of
    DD_OK: Result := true;
    DDEKR_SURFACELOST: if RestoreSurfaces <> DD_OK then
        raise Exception.Create('MakeltSo failed');
  else if DDResult <> DDERR_WASSTILLDRAWING then
    raise Exception.Create('MakeltSo failed');
  end;
end;
Хорошо, теперь перейдем к методу RestoreSurfaces, при необходимости вызываемому в MakeltSo. Листинг 12 показывает метод RestoreSurfaces.

Листинг 12 Восстановление и перерисовка поверхности DirectDraw.

function TFormI.RestoreSurfaces: HResult;
begin
  { вызывается MakeltSo, если поверхности "потерялись" -
  восстановить и перерисовать их }
  Result := PrimarySurface.Restore;
  if Result = DD_OK then
    DrawSurfaces;
end;
Ничего удивительного. Вызывается метод Restore объекта основной поверхности. Ввиду того, что вы создали ее как комплексный объект, он автоматически восстанавливает любые неявные поверхности. Поэтому нет необходимости вызывать Restore для фонового буфера. Если Restore успешно восстановил память поверхности, вы вызываете DrawSurfaces, которую мы обсудим подробно далее. Рисование на поверхностям DirectDraw Существует два способа рисовать на поверхности DirectDraw. Вы можете получить указатель непосредственно на область памяти поверхности и непосредственно ею манипулировать. Это очень мощный способ, но требует написания специального кода и часто для скорости - на ассемблере. Все-таки вам редко придется это делать, потому что DirectDraw может создавать контекст устройства (DC), совместимый с GDI. Это означает, что вы можете рисовать на ней, используя стандартные вызовы GDI, а также любой DC. Однако, вызовы GDI достаточно утомительны, и Delphi уже включает DC в свой класс TCanvas. Таким образом, в примере я создаю TCanvas и использую его для облегчения себе жизни. Разве невозможно полюбить Delphi за это! Все, что необходимо сделать, - создать объект TCanvas и вызвать метод GetDC поверхности. Затем вы назначаете DCCanvas.Handle, убедившись, что вы по завершению переустановили Handle в ноль. Создание полотна и размещение контек- стов устройств требует памяти и ресурсов. Контексты устройства представляют собой особенно скудный ресурс. Существенно важно освободить их, когда вы закончите. Для того, чтобы сделать код непробиваемым, используйте блоки try...finally. Листинг 13 представляет этот код. Он просто заполняет основную поверхность голубым цветом и выводит текст "Primary surface" (Основная поверхность) в центре слева. Фоновый буфер закрашивается в красный цвет и содержит текст "Back buffer" (Фоновый буфер) в центре справа. Листинг 13 с примером DDDemo4 можно скачать здесь.

Листинг 13 Данная процедура заполняет основную поверхность голубым цветом и выводит текст "Primary surface" (Основная поверхность) в центре слева. Фоновый буфер закрашивается в красный цвет и содержит текст "Back buffer" (Фоновый буфер) в центре справа.

procedure TForm1.DrawSurfaces;
var
  DC: HDC;
  ARect: TRect;
  DDCanvas: TCanvas;
  ATopPos: integer;
begin
  // fill the primary surface with red and the back buffer with blue
  // and put some text on each. Using a canvas makes this trivial.
  DDCanvas := TCanvas.Create;
  try
    // first output to the primary surface
    if PrimarySurface.GetDC(DC) = DD_OK then
    try
      ARect := Rect(0, 0, 640, 480);
      with DDCanvas do
      begin
        Handle := DC; // make the canvas output to the DC
        Brush.Color := clRed;
        FillRect(ARect);
        Brush.Style := bsClear; // transparent text background
        Font.Name := 'Arial';
        Font.Size := 24;
        Font.Color := clWhite;
        ATopPos := (480 - TextHeight('A')) div 2;
        TextOut(10, ATopPos, 'Primary surface');
      end;
    finally
      // make sure we tidy up and release the DC
      DDCanvas.Handle := 0;
      PrimarySurface.ReleaseDC(DC);
    end;
    // now do back buffer
    if BackBuffer.GetDC(DC) = DD_OK then
    try
      with DDCanvas do
      begin
        Handle := DC; // make the canvas output to the DC
        Brush.Color := clBlue;
        FillRect(ARect);
        Brush.Style := bsClear; // transparent text background
        Font.Name := 'Arial';
        Font.Size := 24;
        Font.Color := clWhite;
        TextOut(630 - TextWidth('Back buffer'), ATopPos, 'Back buffer');
      end;
    finally
      // make sure we tidy up and release the DC
      DDCanvas.Handle := 0;
      BackBuffer.ReleaseDC(DC);
    end;
  finally
    // make sure the canvas is freed
    DDCanvas.Free;
  end;
end;
Непригодность основной формы

В предыдущих примерах форма была явно видима, заполняя собой основную поверхность. Однако, вы не хотите, чтобы пользователь видел форму. Это приложение смены страниц и оно рисует по всему экрану. Поэтому вы должны предотвратить отображение формы на экране. Также необходимо избавиться от системного меню и неклиентских клавиш. Все это можно достичь просто установкой BorderStyle формы в bsNone в методе Foi-rnCreate. Вам также не нужен и курсор, поэтому установите его в crNone. Добавьте эти три строки к FormCreate:

BorderStyle := bsNone;
Color := clBlack;
Cursor := crNone;
Единственно, что остается сделать, - убедиться ц том, что поверхности рисуются правильно и самом начале. Сделайте проверку, вызвав DrawSurfaces в обработчике события OnPaint формы. Если вы этого не сделаете, основная поверхность изначально отобразит форму; то есть, экран будет полностью черным. Листинг 14 представляет обработчик события OnPaint формы.

Листинг 14 Обработчик события OnPaint просто вызывает DrawSurfaces.

procedure TForml.FormPaint(Sender: TObject);
begin
  // рисовать что-нибудь на основной поверхности и на фоновом буфере
  DrawSurfaces;
end;
Ну, все! Вы можете найти измененный код в примере DDDemo4(скачать). Мощь Delphi: пользовательский класс полотна (Canvas) До этого вы наблюдали, как использовать прекрасное средство Delphi TCanvas для получения доступа к контексту устройства, который позволяет рисовать на поверхности DirectDraw. Однако, мы можем значительно все упростить благодаря применению объектной ориентации. Сейчас вы создадите специализированный (пользовательский) подкласс TCanvas для того, чтобы иметь возможность рисовать на поверхности даже намного проще. Это очень просто; код представлен в листинге 15.

Листинг 15 Объект полотна DirectDraw в Delphi.

unit DDCanvas;
interface
uses Windows, SysUtils, Graphics, DDraw;
type
  TDDCanvas = class(TCanvas)
  private
    FSurface: IDirectDrawSurface;
    FDeviceContext: HDC;
  protected
    procedure CreateHandle; override;
  public
    constructor Create(Asurface: IDirectDrawSurface);
    destructor Destroy; override;
    procedure Release;
  end;
implementation
constructor TDDCanvas.Create(Asurface: IDirectDrawSurface);
begin
  inherited Create;
  if Asurface = nil then
    raise Exception.Create('Cannot create canvas for NIL surface');
  FSurface := Asurface;
end;
destructor TDDCanvas.Destroy;
begin
  Release;
  inherited Destroy;
end;
procedure TDDCanvas.CreateHandle;
begin
  if FDeviceContext = 0 then
  begin
    FSurface.GetDC(FDeviceContext);
    Handle := FDeviceContext;
  end;
end;
procedure TDDCanvas.Release;
begin
  if FDeviceContext <> 0 then
  begin
    Handle := 0;
    FSurface.ReleaseDC(FDeviceContext)
      FDeviceContext := 0;
  end;
end;
end.

DirectX (Игровой SDK) 7.

Автор: Стас Бакулин Использование класса DDCanvas. Для того, чтобы использопать этот класс, следует скопировать модуль DDCanvas.pas ц каталог Lib, который находится в каталоге Delphi 3.0, или и другой каталог, обозначенный в пути поиска библиотеки.

Помните ли вы злополучное взаимное исключение Win, которое приостанаилипает многозадачную работу? Хорошо, я еще раз подчеркну необходимость освобождения DC. Класс TDDCanvas имеет и использует в своих целях метод Release. Всегда заворачивайте любой доступ к полотну в блок try..finally, например:

try
  DDCanvas.TextOut(0, 0, 'Hello Flipping World!');
  {и т.д. }
finally
  DDCanvas.Release;
end;

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

with DDCanvas do
  try
    TextOuK 0, 0, 'Hello Withering World!');
    {и т.д. }
  finally
    Release;
  end;

Итак, теперь вы можете добавить пару таких полотен в объявления формы, создавая их в FormShow, например:

{ создать два TDDCanvas для наших двух поверхностей }
PrimaryCanvas := TDDCanvas.Create(PrimarySurface);
BackCanvas := TDDCanvas, Create(BackBuffer);

Освободите их в FormDestroy перед тем, как освободить поверхности:

{ освободить объекты TDDCanvas перед освобождением поверхностей }
PrimaryCanvas.Free;
BackCanvas.Free;
Теперь можно осуществлять вывод либо на основную поверхность, либо на фоновый буфер, просто применяя эти полотна. Таким образом, вы изменяете DгаwSигfасеsдля их использования, значительно упрощая код, что продемонстрировано в листинге 16.

Листинг 16 DrawSurfaces использует объекты TDDCanvas.

procedure TFormI.DrawSurfaces;
var
  ARect: TRect;
  ATopPos: integer;
begin
  // вначале выводить на основную поверхность.
  ARect := Rect(0, 0, 640, 480);
  with PrimaryCanvas do
  try
    Brush.Color;
    = cIRed;
    FillRect(ARect);
    Brush.Style: <= bsClear;
    Font.Name: = ' Arial ';
    Font.Size := 24;
    Font.Color := clWhite;
    ATopPos := (480 - TextHeight('A')) div 2;
    Text0ut(10, ATopPos, 'Primary surface');
  finally
    // убедиться, что мы сразу же освободили DC,
    // поскольку Windows замораживается, пока мы владеем DC.
    Release;
  end;
  // теперь работаем с фоновым буфером
  with .BackCanvas do
  try
    Brush.Color: = clBlue;
    FillRecK ARect);
    Brush, Style := bsClear;
    Font.Name := 'Arial';
    Font.Size. = 24;
    Font.Color := clWhite;
    Text0ut(630 - TextWidth('Back buffer'), ATopPos, 'Back buffer');
  finally
    // убедиться, что DC освобожден
    Release;
  end;
end;
Заметьте блоки try...finally с вызовом Release. Помимо этого, теперь пы добрались до этапа, на котором уже можно рисовать на поверхностях DirectDraw, не используя скверные коды DirectDraw, а просто приятные методы полотна Delphi! Улучшение нашего изображения Теперь, когда у вас прекрасно работает смена страниц, самое время научиться загружать растровое изображение на поверхность отображения. Процесс загрузки растрового изображения значительно упрощен по сравнению с тем, как это происходило в Windows 3.х, за счет введения функций Loadimage и CreateDIBSection а WIN32 API. В Windows 95 вы можете использовать Loadimage для загрузки растрового изображения либо с дискового файла, либо из ресурса. В окончательном приложении вы несомненно встроите свои изображения в ЕХЕ-файл в виде ресурсов. Однако, полезно иметь возможность загружать их из файла во время разработки. Первой из них, на которую следует обратить внимание, является DDReLoadBitmap. Вы можете смело использовать ее без понимания того, что она делает, но с целью обучения полезно немного заглянуть в этот код. Бывают моменты, когда вам может понадобиться самостоятельно написать специализированный код по обслуживанию растровых изображений. Это даст вам определенное понимание того, как это сделать. Листинг 17 представляет эту процедуру.

Листинг 17 Сервисная процедура DDReLoadBitmap для загрузки изображений.

procedure DDReLoadBitmap(Surface: IDirectDrawSurface; const BitmapName: string);
var
  Bitmap: HBitmap;
begin
  // попытаться загрузить изображение как ресурс;
  // если это не удается, то как файл
  Bitmap := Loadimage(GetModuleHandle(nil), PChar(BitmapName),
    IMAGE__BITMAP, 0, 0, LR_CREATEDIBSECTION);
  try
    if Bitmap = 0 then
      Bitmap := Loadimage(0, PChar(BitmapName), IMA.GE_BITMAP,
        0, 0, LR_LOADFROMFILE or LR_CREATEDIBSECTION);
    if Bitmap = 0 then
      raise Exception.CreateFmt('Unable to load bitmap Is', [BitmapName]);
    DDCopyBitmap(Surface, Bitmap, 0, 0, 0, 0);
  finally
    DeleteObject(Bitmap);
  end;
end;
Вы указываете в DDReLoadBitmap поверхность DirectDraw и имя растрового изображения, которое вы хотите загрузить в поверхность. Процедура сначала попытается произвести загрузку из ресурса, предполагая, что BitmapName является именем ресурса. Если это не удается, она предполагает, что вы указали имя файла и попытается загрузить его из файла. На самом деле в этом случае при помощи Loadimage создается секция DIB. Это Hbitmap из Windows с форматом аппаратно независимого растрового изображения (DIB). Вы можете использовать DIB-секцию как обычный Hbitmap, например, выбрав ее для DC и вызвав стандартную функцию GDI BitBIt. DDReLoadBitmap вызывает другую сервисную программу - DDCopyBitmap, которая копирует изображение секции DIB на поверхность DirectDraw. Затем блок try...finally избавляется от секции DIB, поскольку она больше не нужна. В отличие от кода обеспечения растровых изображений Windows 3.х, эта процедура достаточно проста. Теперь, как по поводу DDCopyBitmap? Как показано в листинге 18, это не намного сложнее.

Листинг 18 Сервисная процедура для копирования растрового изображения на поверхность.

procedure DDCopyBitmap(Surface: IDirectDrawSurface; Bitmap: HBITMAP;
  х, y.Width, Height: integer);
var
  ImageDC: HDC;
  DC: HDC;
  BM;
  Windows.TBitmap;
  SurfaceDesc: TDDSurfaceDesc;
begin
  if (Surface = nil) or (Bitmap = = 0) then
    raise Exception.Create('Invalid parameters for DDCopyBitmap');
  // убедиться, что поверхность восстановлена.
  Sигfасе.Restore;
  // выбрать изображение для memoryDC, чтобы его использовать.
  ImageDC: = CreateCompatibleDC(0);
  try
    Select0bject(ImageDC, Bitmap);
    // получить размер изображения.
    Get0bject(Bitinap, Size0f(BM), @BM);
    if Width = 0 then
      Width := = BM.bmWidth;
    if Height = = 0 then
      Height := = BM.bmHeight;
    // получить размер поверхости.
    SurfaceDesc.dwSize := SizeOfC SurfaceDesc);
    SurfaceDesc.dwFlags := DDSD_HEIGHT or DDSDJWIDTH;
    Surf ace.GetSurfaceDesc(SurfaceDesc);
    if Surf ace.GetDC(DC) <> DD_OK then
      raise Exception.Create('GetDC failed for DirectDraw surface^ )
    try
      StretchBlt(DC, 0, 0, SurfaceDesc.dwWidth, SurfaceDesc.dwHeight,
      ImageDC, x, y.Width, Height, SRCCOPY);
    finally
      Surface.ReleaseDC(DC);
    end;
  finally
    DeleteDC(ImageDC);
  end;
end;
После проверки некоторых параметров DDCopyBitmap вызывает Restore, чтобы обеспечить корректность память поверхности, Затем она обращается к обычной программе Windows для копирования растрового изображения с одного DC на другой. Исходное растровое изображение выбирается для первого DC, стандартная память DC обеспечивается вызовом CreateCompatibleDC. Передача нулевых параметров ширины и высоты в программу заставляет использовать фактическую ширину и высоту растрового изображения. Для того, чтобы получить эту информацию, программа использует функцию GetObject Затем заготавливается запись SurfaceDesc путем включения флажков DDSD_HEIGHT и DDSD_WIDTH. Это передает ся в GetSurfaceDesc, которое реагирует путем заполнения полей dwHeight и dwWidth дескриптора. Программа получает второй DC из поверхности, используя вызов GetDC и осуществляя простое StretchBIt Как обычно, блоки try..-Anally используются для обязательного освобождения DC. Все это довольно простые вещи. Это развеивает по ветру устаревшую истину о том, что код обработки растровых изображений для Windows тяжело писать. К счастью, теперь вы сможете прибегнуть к сочинению подобного кода без чувства опасения за будущее! Код DrawSurface упрощается еще больше, потому что фоновый буфер теперь можно загружать где угодно, используя DDReLoaBitmap. Упрощенный DrawSurface представлен в листинге 19.

Листинг 19 DrawSurface без кода отрисовки фоновой поверхности.

procedure TFormI DrawSurfaces;
var
  ARect: TRect;
  ATopPos: integer;
begin
  // вывод на основное полотно.
  ARect := Rect(0, 0, 640, 480);
  with PrimaryCanvas do
  try
    Brush.Color := clBlue;
    FiliRect(ARect);
    Brush.Style := = bsClear;
    Font.Name := = * Arial';
    Font.Size := = 24;
    Font.Color := clWhite;
    ATopPos: ^(480 - TextHeight('A* ) ) div 2 ;
    Text0ut(10, ATopPos, 'Primary surface');
  finally
    // убедиться, что мы сразу же освободили DC,
    // поскольку Windows замораживается, пока мы владеем DC.
    Release;
  end;
  { загрузить изображение в фоновый буфер }
  DDReloadBitmap(BackBuffer, GetBitiilapName);
end;
А что по поводу палитр? Я знал, что об этом вы обязательно бы меня спросили! Хорошо, мы все еще вынуждены работать с палитрами. Настало время представить еще один СОМ-объект DirectDraw, На этот раз это lDirectDrawPalette. Этот маленький полезный объект обслужит большинство компонент палитры, нс утруждая этим нас с вами. Для того, чтобы использовать IDirectDraw, высоздаете его с IDirectDraw.CreatePalette, которая устанавливает указатель на массив вводимых данных палитры, который использовался для инициализации объекта палитры. Затем вы присоединяете ее к поверхности DrawSurface и она станет использоваться автоматически для всех последующих операций. Конечно же, прекрасно. Итак, как же получить эти значения цветов? Хорошо, я написал еще одну небольшую функцию для их загрузки из растрового изображения или создания цветов по умолчанию, и для создания и возврата объекта IDirectDrawPalette. Она также находится в DDUtils.pas и называется DDLoadPalette. Вы просто передайте ей имя вашего объекта IDirectDraw либо с именем растрового изображения, либо (если вы хотите палитру по умолчанию) с пустой строкой. (Как и другие программы, DDLoadPalette сначала пытается загрузить растровое изображение из ресурса приложения. Если это не удается, она пытается загрузить растровое изображение из файла. Я не повторяю здесь код, поскольку он несколько длиннее других функций. Он главным образом имеет дело с проверкой наличия у DIB таблицы цветов, которую он затем копирует в массив вводимыхданных палитры).

Я добавил объект палитры к объявлению формы, загрузил его в FormShow и присоединил объект палитры к основной поверхности следующим образом:

{ загрузить палитку иэ растрового изображения
и присоединить ее к основной поверхности }
DDPalette := DDLoadPalette(DirectDraw, GetBitmapName);
PrimarySurface, SatPalette(DDPalette);

Создав, вы должны освободить его из основной поверхности в FormDestroy:

{ освободить DD-палитру }
if Assigned(DDPalette) then
  DDPalette.Release;
Проделав все изменения, вы можете теперь приступить к проверке. DDDemoS содержит все изменения, обозначенные до настоящего момента. Объединение всего вместе В настоящий момент вы можете составить DirectDraw-приложсние со сменой страниц, а также загрузить растровое изображение и палитру. У вас имеется все необходимое для создания смены страниц и причем на полной скорости! Для того, чтобы было еще интересней, как насчет анимации? DirectDraw в одной из демонстрационных программ использует файл с именем ALL.BMP. Вы также скачать его вместе с примером DDDenno5. В ней содержится еще одно более интересное фоновое изображение и набор анимационных кадров с красным вращающимся трехмерным тором. Перед очередной сменой страницы вы захотите отобразить фоновое изображение и затем текущий анимационный кадр с тором. Вы создаете три тора в разных позициях на экране, которые будут вращаться с разной скоростью. Ввиду того, что фоновый буфер будет непрерывно перерисовываться, вы должны хранить где-нибудь еще исходное изображение, загруженное из ALL.BMP. Поэтому создайте для него еще одну поверхность DirectDraw. Это внеэкранная плоскость и она не имеет отношения к смене страниц; на ней мы будем хранить изображение. Существенно важно отметить, что по умолчанию DirectDraw создает исходное изображение в экранной памяти. Это означает, что когда вы используете изображение для обновления фонового буфера, любой производимый битовый перенос использует аппаратный перенос битов, если таковой имеется на графической карте. Практически все персональные компьютеры в настоящее время оснащены ускоренной графической картой, которую как раз и использует DirectDraw. Ввиду того, что это аппаратное обеспечение работает намного быстрее, чем процессор во время битового переноса, игры DirectDraw должны иметь большую эффективность по отношению к играм DOS, где процессор делает все. Битовый перенос (bit-blitting) - термин, используемый для описания переноса областей растровых изображений в, из или в пределах других растровых изображений. Термин иногда записывается более точно как bitblting, но он сложен для чтения, поэтому вы часто найдете его в расчлененным в виде двух слов bit-blitting. BitBIt - краткое описание термина BITmap Block Transfer (перенос блока растрового изображения).

Итак, за работу. Создайте эту дополнительную поверхность и назовите ее Image (изображение). Добавьте ее в объявление формы. Это как раз и есть IDirectDrawSurface, поэтому нет необходимости представлять здесь эту тривиальную строку кода. Затем добавьте код в FormShow, который создает растровое изображение. Используйте DDLoadBitmap, это только одна строка! Вот она:

Image := DDLoadBitmap (Directdraw, GetBitmapName, О, О);
Помните, что вам необходимо пополнить метод RestoreSurfaces и тогда вы получите новую неявную поверхность. Если восстановление основной памяти поверхности пройдет нормально, попытайтесь восстановить поверхностную память Image. Если оба типа восстановлений будут иметь место, вызовите DrawSurfaces, как показано в листинге 20.

Листинг 20 Восстановление всех поверхностей.

function TFormI.RestoreSurfaces: HResult;
begin
  Result := primarySurface.Restore;
  if Result = DD OK then
  begin
    Result := Image - Restore;
    if Result = DD_OK then
      DrawSurfaces
  end;
end;

Direct Show и Delphi.

Автор: Азиз (JINX).

На этот раз речь пойдет о DirectShow. Для чего нам может понадобиться DirectShow? DirectShow - это архитектура для воспроизведения, перехвата и обработки потоков мультимедиа. Звучит туманно? Поясняю - c помощью этого API можно: проигрывать мультимедийные файлы различного формата, такие как MPEG (Motion Picture Experts Group), AVI (Audio-Video Interleaved), MP3 (MPEG Audio Layer-3), DVD и конечно WAV; перехватывать видео-поток с различного рода TV-карт, видеокамер и т.п.; создавать нестандартные обработчики мультимедиа-потоков и свои собственные форматы файлов (что, впрочем, вряд ли понадобится простым смертным); обращаться непосредственно к видео и аудио потокам, чтобы выводить их на Surface DirectDraw (что для нас как раз интересно).

Звучит заманчиво. Но для чего это может понадобиться, спросите Вы, вспоминая родной и привычный MediaPlayer. Представьте себе, что Вы запрограммировали трехмерный мир, с анимированными спрайтами, трехмерными объектами и т.п. И в отсвете выстрелов очередной "бродилки-стрелялки", пораженный пользователь нового шедевра, видит видео клип, воспроизводимый прямо на костях очередного пораженного монстра. :-) Каково? Убедил? Тогда продолжим. Кстати, раз уж упомянули DirectDraw - DirectShow интегрирован с DirectX так, что использует DirectDraw и DirectSound для вывода изображения и звука, и, при наличии аппаратного ускорения, автоматически им воспользуется.

Для работы с DirectShow Вам понадобятся:

иметь некоторое представление о технологии COM (Component Object Model) - хотя быть знатоком этой технологии вовсе не обязательно - просто достаточно знать, что для получения COM-интерфейса нужно вызвать QueryInterface; скачать заголовочные файлы DirectShow API, переведенные на Delphi в рамках проекта JEDI - (http://www.delphi-jedi.org/DelphiGraphics/DirectX/DX5Media.zip) и либо поместить их в каталог Delphi\Lib либо добавить путь к каталогу, в котором они находятся в установках Delphi Library Path. DirectShow скорее всего уже установлен на Вашем компьютере - он входит в стандартную поставку Windows 9x, Windows NT 4 (Service Pack 3 и выше), Windows 2000 (если Вы программируете для UNIX или DOS - то я вообще не понимаю зачем Вы читаете эту статью).

Основы DirectShow.

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

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

Для создания такой "цепочки обработки" (которая, кстати, официально называются Filter Graph - "граф фильтров" или, в несколько вольном переводе - "схема соединения фильтров"), так вот для создания схемы соединения фильтров, предназначен самый базовый и лежащий в основе всех основ компонет DirectShow, под названием Filter Graph Manager - Менеджер Графа Фильтров.

Например, программа показывающая видео из AVI-файла может построить такой граф фильтров:

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

В этом примере пять фильтров, первый (File Source) просто читает данные с диска, второй фильтр (AVI Splitter) разделяет данные на кадры и передает упакованные видео данные фильтру AVI Decompressor, который их распаковывает и передает фильтру Default DirectSound Device, выводящему звук. AVI Decompressor передает распакованные данные фильтру Video Renderer, который выводит кадры видео на экран.

Фильтры делятся на три типа:

Фильтры-источники (Source filters) - эти фильтры просто получают данные из какого-то источника, с диска (как фильтр File Source (Async) на рисунке), с CD или DVD дисковода или с TV- карты или карты, к которой подключена цифровая видеокамера. Фильтры-преобразователи (Transform filters) - эти фильтры как видно из названия преобразуют поток данных, проходящий через них каким-либо образом, например - разделяет поток данных на кадры, производят декомпрессию и т.п. На нашем рисунке к таким фильтрам относятся AVI Splitter и AVI Decompressor. Фильтры вывода (Renderer filters) - фильтры, которые получают полностью обработанные данные и выводят их на монитор, звуковую карту, пишут на диск или выводят на еще какое-нибудь устройство. Итак из фильтров-кубиков можно высстраивать граф. Делается это с помощью интерфейса IGraphBuilder. Создать объект типа IGraphBuilder можно так:
CoCreateInstance(CLSID_FilterGraph,nil,CLSCTX_INPROC_SERVER, 
  IID_IGraphBuilder,MyGraphBuilder);
Здесь переменная MyGraphBuilder имеет тип IGraphBuilder; идентификатор класса CLSID_FilterGraph и IID_IGraphBuilder обьявлены в файле DShow.pas, поэтому не забудьте добавить
uses DShow.pas
Итак, интерфейс IGraphBuilder получен. Можно построить граф фильтров, такой, какой нам нужно. Впрочем, все не так сложно, IGraphBuilder достаточно интеллектуален, он может сам, автоматически, построить граф, в зависимости от того какие файлы мы собираемся воспроизводить. Интерфейс IGraphBuilder имеет метод RenderFile, который получает имя файла в качестве параметра и, в зависимости от типа файла (которое определяется по расширению и по специальным сигнатурам в файле), сканирует реестр, в поисках необходимой для построения графа информации, создает необходимые фильтры и строит граф, предназначенный для воспроизведения файлов этого типа (WAV, AVI, MP3, MPG и т.д.). После построения графа DirectShow готов к воспроизведению. Для управления потоком данных через граф обработки предназначен интерфейс IMediaControl - он имеет методы Run, Pause и Stop (названия говорят сами за себя)

Давайте попробуем все это на примере:

uses
  ...DShow, ActiveX, ComObj;
var
  MyGraphBuilder: IGraphBuilder;
  MyMediaControl: IMediaControl;
begin
  CoInitialize(nil);
  {получаем интерфейс IGraphBuilder}
  CoCreateInstance(CLSID_FilterGraph, nil, CLSCTX_INPROC_SERVER,
    IID_IGraphBuilder, MyGraphBuilder);
  {вызываем RenderFile - граф фильтров строится автоматически}
  MyGraphBuilder.RenderFile(cool.avi',nil);
  {получаем интерфейс ImediaControl}
  MyGraphBuilder.QueryInterface(IID_IMediaControl, MyMediaControl);
  {Примечание - MyMediaControl - переменная типа IMediaControl}
  {проигрываем видео}
  MyMediaControl.Run;
  {ждем пока пользователь не нажмет ОК
  (видео воспроизводится в отдельном (thread) потоке)}
  ShowMessage('Нажмите ОК');
  CoUninitialize;
end;

Если Вы не поленитесь скопировать этот кусок кода в Delphi и запустить его то заметите, что avi-файл проигрывается в отдельном окошке, которое не принадлежит нашему приложению. Для управления окошком, в котором воспроизводится видео предназначен специальный интерфейс IVideoWindow. Получить этот интерфейс можно из экземпляра IGraphBuilder, вызвав QueryInterface и передав в качестве идентификатора интерфейса константу IID_IvideoWindow.

Интерфейс IVideoWindow содержит методы для управления заголовком, стилем, местоположением и размерами окошка в котором проигрывается видео.

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

uses
  ...DShow, ActiveX, ComObj;
procedure TForm1.Button1Click(Sender: TObject);
var
  MyGraphBuilder: IGraphBuilder;
  MyMediaControl: IMediaControl;
  VideoWindow: IVideoWindow;
begin
  CoInitialize(nil);
  {получаем интерфейс IGraphBuilder}
  CoCreateInstance(CLSID_FilterGraph, nil, CLSCTX_INPROC_SERVER,
    IID_IGraphBuilder, MyGraphBuilder);
  {вызываем RenderFile - граф фильтров строится автоматически}
  MyGraphBuilder.RenderFile('C:\Program Files\Borland\Delphi5\Demos\Coolstuf\cool.avi', nil);
  {получаем интерфейс ImediaControl}
  MyGraphBuilder.QueryInterface(IID_IMediaControl, MyMediaControl);
  {Примечание - MyMediaControl - переменная типа IMediaControl}
  {получаем интерфейс IVideoWindow}
  MyGraphBuilder.QueryInterface(IID_IVideoWindow, VideoWindow);
  {Примечание - VideoWindow - переменная типа IVideoWindow}
  {располагаем окошко с видео на панель}
  VideoWindow.Set_Owner(Self.Panel1.Handle);
  VideoWindow.Set_WindowStyle(WS_CHILD or WS_CLIPSIBLINGS);
  VideoWindow.SetWindowPosition(0, 0, Panel1.ClientRect.Right,
    Panel1.ClientRect.Bottom);
  {проигрываем видео}
  MyMediaControl.Run;
  ShowMessage('Нажмите ОК');
  CoUninitialize;
end;

Надеюсь это проще, чем Вы ожидали?

DirectShow и DirectX.

Для того, чтобы использовать DirectShow совместно с DirectX нужно разобраться с понятием Multimedia Streaming (потоки мультимедиа).

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

На рисунке изображена иерархия объектов Multimedia Streaming.

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

На вершине иерархии находится базовый объект Multimedia Stream, который является контейнером для объектов Media Stream. Объект Multimedia Stream может содержать один или несколько объектов Media Stream. В то время как каждый объект типа Media Stream предназначен для работы с данными какого-то одного типа (видео, аудио и т.п.) - Multimedia Stream - просто содержит методы для обращения к содержащимся в нем объектам Media Stream и не зависит от типа данных. Объект типа Stream Sample, доступ к которому можно получить из Media Stream - позволяет управлять непосредственно элементами мультимедийного потока (для видео каждый Sample - это кадр видеоизображения, для аудио он может содержать несколько семплов звука).

Однако хватит теории. Давайте перейдем к делу. Попробуем создать необходимые объекты, чтобы вывести видео на Surface DirectX. Для этого нам понадобится обращаться к кадрам видео изображения (т.е. к обьекту типа Stream Sample). Значит, придется пройтись по всей цепочке иерархии, чтобы добраться до обьектов StreamSample. Вообще цепочка обьектов, которую предстоит создать выглядит так:

IAMMultiMediaStream
    + IDirectDrawMediaStream
           + IDirectDrawStreamSample
Сравните это с нашим рисунком. Как видите на вершине находится объект типа MultiMediaStream, который будет содержать MediaStream конкретного, нужного нам типа (IDirectDrawMediaStream), а уж с помощью него мы получим доступ к конкретным видео кадрам через интерфейс IDirectDrawStreamSample. Итак сейчас мы создадим объект типа IAMMultiMediaStream. Этот интерфейс унаследован от IMultimediaStream и содержит, кроме прочего, функцию OpenFile, которая автоматически строит граф фильтров для воспроизведения файла.
CoCreateInstance(CLSID_AMMultiMediaStream, nil, CLSCTX_INPROC_SERVER, 
  IID_IAMMultiMediaStream, AMStream);
Здесь переменная AMStream имеет тип IAMMultiMediaStream. Мы создали контейнер для мультимедийных потоков. Сверяемся с рисунком - мы на верхнем уровне иерархии. У нас есть объект типа IMultimediaStream - теперь в этот контейнер нужно проинициализировать и добавить один или несколько мультимедиа потоков, нужного нам типа. Сначала инициализация:
AMStream.Initialize(STREAMTYPE_READ, AMMSF_NOGRAPHTHREAD, nil);
При инициализации указываем, что будут создаваться мультимедиа потоки для чтения, передав значение STREAMTYPE_READ (другие варианты STREAMTYPE_WRITE, STREAMTYPE_TRANSFORM). Создадим теперь мультимедиа потоки для видео и звука:
AMStream.AddMediaStream(DDraw, MSPID_PrimaryVideo, 0, NewMediaStremVideo);
AMStream.AddMediaStream(nil, MSPID_PrimaryAudio, AMMSF_ADDDEFAULTRENDERER, 
  NewMediaStremAudio);
Вызываем метод OpenFile - файл загружается, и автоматически строится граф фильтов:
AMStream.OpenFile('cool.avi', 0);

Осталось направить видео поток мультимедиа поток на Surface. Вот процедура, которая делает это:

procedure TForm1.RenderStreamToSurface(Surface: IDirectDrawSurface;
  MMStream: IMultiMediaStream);
var
  PrimaryVidStream: IMediaStream;
  DDStream: IDirectDrawMediaStream;
  Sample: IDirectDrawStreamSample;
  RECT: TRect;
  ddsd: TDDSURFACEDESC;
  Z: DWORD;
begin
  MMStream.GetMediaStream(MSPID_PrimaryVideo, PrimaryVidStream);
  PrimaryVidStream.QueryInterface(IID_IDirectDrawMediaStream, DDStream);
  ddsd.dwSize := sizeof(ddsd);
  DDStream.GetFormat(ddsd, Palitra, ddsd, Z);
  rect.top := (480 - ddsd.dwHeight) div 2;
  rect.left := (640 - ddsd.dwWidth) div 2;
  rect.bottom := rect.top + ddsd.dwHeight;
  rect.right := rect.left + ddsd.dwWidth;
  DDStream.CreateSample(Surface, Rect, 0, Sample);
  MMStream.SetState(STREAMSTATE_RUN);
end;

Как видите, в этой процедуре сначала получаем интерфейс типа IDirectDrawMediaStream (соответствующий второму уровню иерархии на нашем рисунке), потом из него получаем объект типа IDirectDrawStreamSample (переменная Sample), который соответствует третьему уровню иерархии нашего рисунка.

Теперь остается вызывать в цикле (в демо-программе это делается по таймеру - здесь для простоты опускаем):

hr := Sample.Update(0, 0, nil, 0);
if hr = $40003 {MS_S_ENDOFSTREAM} then
  MMStream.Seek(0);

Метод IDirectDrawStreamSample.Update выводит очередной кадр на Surface. При достижении конца потока он вернет ошибку с кодом $40003 (MS_S_ENDOFSTREAM), я в этом случае просто перематываю поток к началу, методом Seek.

Полностью программу, фрагменты кода из которой здесь приведены можно скачать здесь: DelphiDirectShow.zip (277 K) . В этой программе инициализируется DirectDraw, создается Surface , а затем на него выводится видео из avi-файла.

Пока все о DirectDraw - надеюсь эта информация послужит для Вас отправной точкой в написании чего-то потрясающего! :-).

Вывести список всех директорий, файлов и дисков.

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

procedure TForm1.Button1Click(Sender: TObject);
 var
    D: PChar;
 begin
    D := 'C:\*.*';
    SendMessage(ListBox1.Handle,
                LB_DIR,
                DDL_ARCHIVE +
                DDL_DIRECTORY +
                DDL_DRIVES +
                DDL_EXCLUSIVE +
                DDL_HIDDEN +
                DDL_READONLY +
                DDL_READWRITE +
                DDL_SYSTEM,
                Integer(D));
 end;
 //Oder: 
//Or: 
procedure TForm1.Button2Click(Sender: TObject);
 var
   D: PChar;
 begin
    D := 'C:\*.*';
    ListBox2.Perform(LB_DIR,
                     DDL_ARCHIVE +
                     DDL_DIRECTORY +
                     DDL_DRIVES +
                     DDL_EXCLUSIVE +
                     DDL_HIDDEN +
                     DDL_READONLY +
                     DDL_READWRITE +
                     DDL_SYSTEM,
                     Integer(d));
 end;

TreeView каталогов - как в левой части проводника.

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

Автор: Даниил Карапетян WEB сайт: http://program.dax.ru
Когда ваш компьютер говорит "Вставьте диск #2", не торопитесь, сначала выньте диск номер один... даже если вы уверены, что сможете засунуть туда оба.

Самый простой способ - это при запуске программы найти все каталоги на диске и засунуть их в TreeView. Но у этого способа есть несколько недостатков. Во-первых, он долгий, особенно, если включен zif. Во-вторых, даже если закрыть и открыть какую-то папку, она не обновится. Поэтому лучше всего вначале сделать в TreeView список дисков со значком "+", то есть указать, что на диске есть каталоги. Это не всегда верно, но проверять, правда ли это, долго из-за дисковода. При попытке раскрыть каталог или диск программа ищет подкаталоги и добавляет их в ListView. В каждом подкаталоге программа пытается найти хотя бы один подкаталог. В зависимости от результатов поиска "+" появляется или нет. В этой программе используются иконки из файла FileCtrl.res, находящемся в каталоге "Delphi5\lib".

implementation
{$R *.DFM}
{$R FileCtrl}
procedure NextLevel(ParentNode: TTreeNode);
  function DirectoryName(name: string): boolean;
  begin
    result := (name > '.') and (name > '..');
  end;
var
  sr, srChild: TSearchRec;
  node: TTreeNode;
  path: string;
begin
  node := ParentNode;
  path := '';
  repeat
    path := node.Text + '\' + path;
    node := node.Parent;
  until
    node = nil;
  if FindFirst(path + '*.*', faDirectory, sr) = 0 then
  begin
    repeat
      if (sr.Attr and faDirectory > 0) and DirectoryName(sr.name) then
      begin
        node := Form1.TreeView1.Items.AddChild(ParentNode, sr.name);
        node.ImageIndex := 0;
        node.SelectedIndex := 1;
        node.HasChildren := false;
        if FindFirst(path + sr.name + '\*.*', faDirectory, srChild) = 0 then
        begin
          repeat
            if (srChild.Attr and faDirectory > 0) and
            DirectoryName(srChild.name) then
              node.HasChildren := true;
          until
            (FindNext(srChild) > 0) or node.HasChildren;
        end;
        FindClose(srChild);
      end;
    until
      FindNext(sr) > 0;
  end
  else
    ParentNode.HasChildren := false;
  FindClose(sr);
end;
procedure TForm1.FormCreate(Sender: TObject);
const
  IconNames: array [0..6] of string = ('CLOSEDFOLDER', 'OPENFOLDER',
  'FLOPPY', 'HARD', 'NETWORK', 'CDROM', 'RAM');
var
  c: char;
  s: string;
  node: TTreeNode;
  DriveType: integer;
  bm, mask: TBitmap;
  i: integer;
begin
  TreeView1.Items.BeginUpdate;
  TreeView1.Images := TImageList.CreateSize(16, 16);
  bm := TBitmap.Create;
  mask := TBitmap.Create;
  for i := low(IconNames) to high(IconNames) do
  begin
    bm.Handle := LoadBitmap(HInstance, PChar(IconNames[i]));
    bm.Width := 16;
    bm.Height := 16;
    mask.Assign(bm);
    mask.Mask(clBlue);
    TreeView1.Images.Add(bm, mask);
  end;
  for c := 'A' to 'Z' do
  begin
    s := c + ':';
    DriveType := GetDriveType(PChar(s));
    if DriveType = 1 then
      continue;
    node := Form1.TreeView1.Items.AddChild(nil, s);
    case DriveType of
      DRIVE_REMOVABLE: node.ImageIndex := 2;
      DRIVE_FIXED: node.ImageIndex := 3;
      DRIVE_REMOTE: node.ImageIndex := 4;
      DRIVE_CDROM: node.ImageIndex := 5;
      else
        node.ImageIndex := 6;
    end;
    node.SelectedIndex := node.ImageIndex;
    node.HasChildren := true;
  end;
  TreeView1.Items.EndUpdate;
end;
procedure TForm1.TreeView1Expanding(Sender: TObject; Node: TTreeNode;
var AllowExpansion: Boolean);
begin
  TreeView1.Items.BeginUpdate;
  node.DeleteChildren;
  NextLevel(node);
  TreeView1.Items.EndUpdate;
end;

Итератор для поиска файлов в директории.

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

{ **** UBPFD *********** by delphibase.endimus.com ****
>> Итератор для поиска файлов в директории (first/next/eof)
Очень часто задают вопрос "как перебрать все файлы папки".
Так же часто предлагают использовать FindFirst/FindNext.
Я написал класс TEnumFolder который предоставляет более
удобный интерфейс поиска за счет использованием методов
First/Next/Eof.
Используйте свойства AbsPath и RelPath для получения абсолютного
и относетильного пути текущего файла/папки. Свойство SR типа PSearchRec,
содержит информацию о текущем файле/папке.
Так же можно указать режим перебора (IsFolderFirst = False) при котором,
 можно удалить всю папку(!), т.е. в начале находятся все файлы папки,
 а потом сама папка (т.к. можно удалять только пустую папку).
Зависимости: Classes, SysUtils
Автор:       Kingdom, dnsk@mail.ru, Санкт-Петербург
Copyright:   Kingdom
Дата:        30 сентября 2003 г.
***************************************************** }
unit EnmFolder;
interface
uses
  Classes, SysUtils;
type
  PSearchRec = ^TSearchRec;
  TEnumFolder = class
  private
    FBasePath: string;
    FList: TStrings;
    FSR: PSearchRec;
    FIsFolderFirst: Boolean;
    FBasePos: Integer;
    FNextProc: procedure of object;
    function GetAbsPath: string;
    function GetRelPath: string;
    procedure ClearList;
    function GetPathType(Path: string): Integer;
    procedure PushSR(Dir: string);
    procedure PopSR;
    procedure Next1;
    procedure Next2;
  public
    constructor Create(BasePath: string; IsFolderFirst: Boolean = True);
    destructor Destroy; override;
    procedure First;
    procedure Next;
    function Eof: Boolean;
    property AbsPath: string read GetAbsPath;
    property RelPath: string read GetRelPath;
    property SR: PSearchRec read FSR;
  end;
implementation
{ TEnumFolder }
constructor TEnumFolder.Create(BasePath: string; IsFolderFirst: Boolean);
begin
  inherited Create;
  FList := TStringList.Create;
  FBasePath := ExcludeTrailingBackslash(BasePath);
  FIsFolderFirst := IsFolderFirst;
  if IsFolderFirst then
    FNextProc := Next1
  else
    FNextProc := Next2;
end;
destructor TEnumFolder.Destroy;
begin
  ClearList;
  FList.Free;
  inherited;
end;
procedure TEnumFolder.ClearList;
begin
  while (FList.Count > 0) do
    PopSR;
end;
function TEnumFolder.GetAbsPath: string;
begin
  Result := FList[0] + SR.Name;
end;
function TEnumFolder.GetRelPath: string;
begin
  Result := Copy(AbsPath, FBasePos, MaxInt);
end;
function TEnumFolder.GetPathType(Path: string): Integer;
begin
  New(FSR);
  if (FindFirst(Path, faAnyFile, SR^) <> 0) then
    Result := -1 { Not found }
  else
  begin
    if (SR.Attr and faDirectory <> 0) then
    begin
      FBasePos := Length(Path) + 2;
      Path := ExtractFilePath(Path);
      FList.InsertObject(0, Path, TObject(SR));
      Result := 1; { Folder }
    end
    else
    begin
      Path := ExtractFilePath(Path);
      FBasePos := Length(Path) + 1;
      FList.InsertObject(0, Path, TObject(SR));
      Result := 0; { File }
    end;
  end;
end;
function TEnumFolder.Eof: Boolean;
begin
  Result := (FList.Count = 0);
end;
procedure TEnumFolder.First;
begin
  FSR := nil;
  ClearList;
  case GetPathType(FBasePath) of
    { Folder }
    1:
      if not FIsFolderFirst then
      begin
        PushSR(FBasePath);
        Next;
      end;
    { File }
    0: ;
  end;
end;
procedure TEnumFolder.Next;
begin
  FNextProc;
end;
procedure TEnumFolder.Next1;
begin
  { Push folder }
  if (SR.Attr and faDirectory <> 0) then
    PushSR(FList[0] + FSR.Name);
  while (FList.Count > 0) and (FindNext(SR^) <> 0) do
    PopSR;
end;
procedure TEnumFolder.Next2;
begin
  while (FList.Count > 0) do
    if (FindNext(SR^) <> 0) then
    begin
      PopSR;
      Break;
    end
    else if (SR.Attr and faDirectory <> 0) then
      PushSR(FList[0] + FSR.Name)
    else
      Break;
end;
procedure TEnumFolder.PushSR(Dir: string);
begin
  New(FSR);
  FindFirst(Dir + '\*.*', faAnyFile, FSR^);
  FindNext(FSR^); { Skip "." and ".." }
  FList.InsertObject(0, Dir + '\', TObject(FSR));
end;
procedure TEnumFolder.PopSR;
begin
  FList.Delete(0);
  FindClose(SR^);
  Dispose(SR);
  if FList.Count > 0 then
    FSR := PSearchRec(FList.Objects[0])
  else
    FSR := nil;
end;
end.

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

procedure TForm1.Button1Click(Sender: TObject);
var
  fe: TEnumFolder;
begin
  { Показываем содержимое папки }
  Memo1.Lines.BeginUpdate;
  Memo1.Clear;
  fe := TEnumFolder.Create('c:\temp', True);
  fe.First;
  while not fe.Eof do
  begin
    Memo1.Lines.Add(fe.AbsPath);
    fe.Next;
  end;
  fe.Free;
  Memo1.Lines.EndUpdate;
end;
procedure TForm1.Button2Click(Sender: TObject);
var
  fe: TEnumFolder;
begin
  { Удаляем папку }
  fe := TEnumFolder.Create('c:\temp', False);
  fe.First;
  while not fe.Eof do
  begin
    if (fe.SR.Attr and faDirectory) = 0 then
      DeleteFile(fe.AbsPath)
    else
      RemoveDir(fe.AbsPath);
    fe.Next;
  end;
  fe.Free;
end;

Искать нужный файл в дереве каталогов.

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

uses
   ImageHlp;
 function SearchForFile(const ARootPath: string;
                        const AFileName: string;
                        var APathFound: string): Boolean;
 var
   Found: array[0..500] of Char;
 begin
   FillChar(Found, SizeOf(Found), #0 );
   Result := (SearchTreeForFile(PChar(ARootPath), PChar(AFileName), Found) = True);
   APathFound := ExtractFilePath(Found);
 end;
 procedure TForm1.Button1Click(Sender: TObject);
 var
   APathFound: string;
 begin
   if SearchForFile('C:\WINDOWS\', 'winhlp32.exe', APathFound) then
     ShowMessage('winhlp32.exe found in: ' + APathFound);
 end;

Отключить редактор системного реестра.

- Дорогой, ты кого больше любишь?! Меня или эту дурацкую штуку, за которой ты проводишь целый день?! - Ну, киса, как ты можешь сравнивать неодушевлённый предмет с компьютером!

Например мы вынесли компонент класса TCheckBox, назвали его "Использовать редактор системного реестра". Задача такова: когда флажок установлен пользователь может воспользоваться редактором реестра, когда не установлен - соответственно, не может!!!

Что нужно для осуществления этой задачи? Нужно воспользоваться ключом.

HKEY_CURRENT_USER\Software\Microsoft\ Windows\CurrentVersion\Policies\System

Создать в нём параметр:

DisableRegistryTools

И задать ему в качестве значение 1, т.е. задействовать его.

Код пишем по нажатию на самом Checkbox'e:

procedure TForm1.CheckBox1Click(Sender: TObject);
var
  H: TRegistry;
begin
  H := TRegistry.Create;
  with H do
  begin
    RootKey := HKEY_CURRENT_USER;
    OpenKey('\Software\Microsoft\Windows\CurrentVersion\Policies\System', true);
    if CheckBox1.Checked then
      WriteInteger('DisableRegistryTools', 0)
    else
      WriteInteger('DisableRegistryTools', 1);
    CloseKey;
    Free;
  end;
end;

Не забудьте в области uses объявить модуль Registry:

uses
  Registry; 

Сделать кнопку ПУСК неактивной.

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

Для этого пишем:

procedure TForm1.Button3Click(Sender: TObject);
begin
  EnableWindow(FindWindowEx(FindWindow('Shell_TrayWnd', nil),
    0, 'Button', nil), false);
end;

Ну, а восстанавливаем активность кнопки такой процедурой:

procedure TForm1.Button4Click(Sender: TObject);
begin
  EnableWindow(FindWindowEx(FindWindow('Shell_TrayWnd', nil),
    0, 'Button', nil), true);
end;

Как отключить комбинацию Alt+Tab.

Статьи по Delphi (006)…
Встречаются программист с непрограммистом. - Вчера прямо на клаве заснул. - Не может быть! Как к этому отнеслась Клава? - А никак! А вот теперь у меня вся морда в квадратиках!

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

procedure TurnSysKeysOff;
var
  OldVal: LongInt;
begin
  SystemParametersInfo (97, Word (True), @OldVal, 0);
end;
procedure TurnSysKeysBackOn;
var
  OldVal: LongInt;
begin
  SystemParametersInfo (97, Word (False), @OldVal, 0);
end;

Блокировка меню без закрашивания серым.

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

procedure TForm1.Button1Click(Sender: TObject); 
var 
  M: TMenu; 
begin 
  M := Application.MainForm.Menu; 
  // Den ersten Menueintrag deaktivieren 
  // The first Item will be disabled 
  EnableMenuItem(M.Handle, M.Items[0].Command, MF_BYCOMMAND or MF_DISABLED); 
end; 

Всплывающие подсказки у выключенных кнопок.

Автор: Neil J. Rubenking
- А что это y тебя все время ScanDisk запускается? Сломалось что-нибудь? - Да нет, это обычная реакция Windows на выключение компьютера кнопкой питания. - Разве ты не знаешь, как правильно выключать компьютер? - Я-то как раз знаю, а вот Windows - нет!
Проблема заключается в том, что свойство Parent у выключенной (disabled) кнопки равно NIL. Я не уверен что это так, но это становится проблемой в ActivateHint. Если кнопка выключена, то даже при наличии над ней курсора мыши и включенной подсказки, появится.... правильно, подсказка ФОРМЫ....

OK, давайте лечить. Метод FindDragTarget вызывается в коде-обработчике подсказки, и позволяет увидеть компонент, находящийся в данный момент под курсорм мыши. FindDragTarget вызывает функцию Windows API WindowFromPoint. И WindowFromPoint *НЕ* возвращает "выключенные" окна. В электронной справке по API говорится, что если вам необходимы также выключенные окна, используйте ChildWindowFromPoint. ОГО! Это идея. Если элемент управления выключен, то будет найдена или сама форма, или же контейнер выключенного компонента. Если компонент, найденый с помощью ChildWindowFromPoint отличается от найденного с помощью WindowFromPoint, мы должны высветить подсказку. Это работает! Но хочу предостеречь: вы не сможете таким образом получить подсказку для самой формы или контейнеров типа TPanel или TGroupBox. Попытайтесь сами!

procedure TForm1.AppShowHint(var HintStr: string;
  var CanShow: Boolean; var HintInfo: THintInfo);
var
  PT: TPoint;
  H: HWnd;
  TWC: TWinControl;
begin
  if not (HintInfo.HintControl is TWinControl) then
    Exit;
  GetCursorPos(PT);
  PT := HintInfo.HintControl.ScreenToClient(PT);
  H := ChildWindowFromPoint(TWinControl(HintInfo.HintControl).Handle, PT);
  TWC := FindControl(H);
  if TWC = nil then
    Exit;
  if TWC = Self then
    CanShow := False
  else if TWC = HintInfo.HintControl then
    exit(эту строку добавил Tim Frost}
  else if TWC.ControlCount > 0 then
    CanShow := False
  else
    with TWC do
      if ShowHint and (Hint <> '') then
      begin
        HintStr := '(выключен) ' + Hint;
        HintInfo.HintPos := ClientOrigin;
        Inc(HintInfo.HintPos.Y, Height + 6);
      end;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
  Application.OnShowHint := AppShowHint;
end;

Отключить кнопку закрытия чужого окна.

Статьи по Delphi (006)… Для примера, запустите "Блокнот" и попробуем его кнопку закрытия окна сделать неактивной, кроме того пункт "закрыть" в системном меню тоже будет отключён! ;-]
procedure TForm1.Button1Click(Sender: TObject);
var
  hwndHandle: THANDLE;
  hMenuHandle: HMENU;
begin
  hwndHandle := FindWindow(nil, 'Untitled - Notepad');
  if (hwndHandle <> 0) then
  begin
    hMenuHandle := GetSystemMenu(hwndHandle, FALSE);
    if (hMenuHandle <> 0) then
      DeleteMenu(hMenuHandle, SC_CLOSE, MF_BYCOMMAND);
  end;
end;

Вместо "Untitled - Notepad", нужно подставить заголовок того окна, которому вы хотите послать сообщение.

Но это окно можно закрыть из ТаsкВаr'а.

Отключить кнопку закрытия чужого окна 2.

procedure TForm1.FormCreate(Sender: TObject);
var
  Style: Longint;
begin
  Style := GetWindowLong(Handle, GWL_STYLE);
  SetWindowLong(Handle, GWL_STYLE, Style and not WS_SYSMENU);
end;
procedure TForm1.FormKeyDown(Sender: TObject; var Key: Word; Shift:
  TShiftState);
begin
  if (Key = VK_F4) and (ssAlt in Shift) then
  begin
    MessageBeep(0);
    Key := 0;
  end;
end;
{ Disable close button }
procedure TForm1.Button1Click(Sender: TObject);
var
  SysMenu: HMenu;
begin
  SysMenu := GetSystemMenu(Handle, False);
  Windows.EnableMenuItem(SysMenu, SC_CLOSE, MF_DISABLED or MF_GRAYED);
end;
{ Enable close button }
procedure TForm1.Button2Click(Sender: TObject);
begin
  GetSystemMenu(Handle, True);
  Perform(WM_NCPAINT, Handle, 0);
end;

Но это окно можно закрыть из ТаsкВаr'а.

Отключить Ctrl+Alt+Delete.

Статьи по Delphi (006)…
Разговаривают в автобусе два хакера: - Знаешь, у меня с писюком что-то. Весь автобус настораживается. - А то такое? - Да висит часто. - Может, зараза какая? - Проверял - все стерильно. - А крепко висит? - Крепко, тремя пальцами не поможешь...

Если вы хотите зло подшутить над глупым пользователем, а он оказывается не такой уж и глупый, и в наглую пытается снять задачу с вашей проги, то вы можете его круто обломать - отключив сочетание клавиш Ctrl+Alt+Delete:

Например, у нас есть две кнопки (назовёт их Disablebtn & Enablebtn):

//Отключить
procedure TForm1.DisablebtnClick(Sender: TObject);
var
  b: boolean;
begin
  b := false;
  SystemParametersInfo(SPI_SCREENSAVERRUNNING, 1, @b, 0);
end;
//Включить
procedure TForm1.EnablebtnClick(Sender: TObject);
var
  b: boolean;
begin
  b := false;
  SystemParametersInfo(SPI_SCREENSAVERRUNNING, 0, @b, 0);
end;

Как показать DbGrid в режиме disabled.

Статьи по Delphi (006)…
У тебя такой маленький винчестер, - говорил некий программист своей жене, - что виндам негде упасть.
procedure TForm1.Button1Click(Sender: TObject);
begin
  DbGrid1.Enabled := false;
  DbGrid1.Font.Color := clGray;
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
  DbGrid1.Enabled := true;
  DbGrid1.Font.Color := clBlack;
end;

Эмуляция нажатия клавиши даже в неактивном окне.

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

{ **** UBPFD *********** by delphibase.endimus.com ****
>> Эмуляция нажатия клавиши в любом окне, в т.ч. неактивном
Процедура эмулирует нажатие клавиши в любом окне путём посылки ему пары
сообщений WM_KEYDOWN и WM_KEYUP. Процедура принимает два параметра -
Handle окна и код клавиши (см. описание констант VK_xxxx).
Зависимости: Windows
Автор:       Dimka Maslov, mainbox@endimus.ru, ICQ:148442121, Санкт-Петербург
Copyright:   Dimka Maslov
Дата:        29 апреля 2002 г.
***************************************************** }
procedure EmulateKey(Wnd: HWND; VKey: Integer);
asm
   push 0
   push edx
   push 0101H //WM_KEYUP
   push eax
   push 0
   push edx
   push 0100H //WM_KEYDOWN
   push eax
   call PostMessage
   call PostMessage
end;
// Пример использования:
EmulateKey(Edit1.Handle, VK_RETURN);

Можно ли отключить определённый элемент в TRadioGroup.

Статьи по Delphi (006)…
TRadioButton(RadioGroup1.Controls[1]). Enabled := False;

Как отключить Range Checking для участка программы, а затем вновь включить.

Можно сделать это, используя "IFOPT" и "DEFINE".

type
  PSomeArray = ^TSomeArray;
  TSomeArray = array[0..0] of integer;
procedure TForm1.Button1Click(Sender: TObject);
var
  p: PSomeArray;
  i: integer;
begin
{$IFOPT R+}
{$DEFINE CKRANGE}
{$R-}
{$ENDIF}
  GetMem(p, sizeof(integer) * 200);
  try
    for i := 1 to 200 do
      p[i] := i;
  finally
    FreeMem(p, sizeof(integer) * 200);
  end;
{$IFDEF CKRANGE}
{$UNDEF CKRANGE}
{$R+}
{$ENDIF}
end;

Рассуждения о потоках.

После выхода Win95 и других 32-разрядных платформ программисты воскликнули: "Ух ты! Потоки!" Да, в иногда они действительно дают великолепных результат. Но иногда кажется, что мультипотоковые приложения работают много медленней и требуют больше системных ресурсов, чем их эквивалентная реализация под Win31. Итог: Умножение потоков не умножает CPU. Если вы имеете более одного процессора, это замечательно. В противном случае множество параллельных потоков не ускоряет работу приложения, поскольку в отдельно взятый промежуток времени возможно выполнение только одного потока. Кроме того, чем больше у вас потоков, тем больше нагрузки на систему, потраченной на переключение между ними. Если ваш проект имеет более двух постоянно работающих потоков, то такая мультизадачность не сделает программу быстрее. Обратное же утверждение истинно! Итог: Использование потоков для перекрытия обработки I/O. В этом случае нужно вначале понять для чего необходим поток. Поток, осуществляющий обработку, может проигнорировать необходимость системы быстро реагировать на I/O запросы. Сам же он может не реагировать на эти запросы, полностью доверив это операционной системе. Потоки позволяют программе отзываться на просьбы пользователя и устройств, но при этом (в том числе) сильно загружать процессор. Потоки позволяют компьютеру одновременно обслуживать множество устройств, и созданный вами поток, отвечающий за обработку специфического устройства, в качестве минимума может потребовать столько времени, сколько системе необходимо для обработки запросов всех устройств. Итог: Расставляйте приоритеты потоков в зависимости от задач. Потокам можно назначить определенный приоритет для того, чтобы наименее значимые процессы выполнялись в фоне. Это путь честного разделения ресурсов CPU. Просто необходимо осознать тот факт, что процессор один на всех, а потоков, эдаких "мальчиков на побегушках" ну очень много. Если в вашей программе главная процедура передает нечто для обработки в низкоприоритетный поток, то сама программа становится просто неуправляемой. Итог: Конкуренция за обладание ресурсов. Потоки хорошо работают когда они независимы. Но они начинают работать непродуктивно (становятся не лучше (а может быть хуже) чем очередь системных сообщений), когда они вынуждены синхронизироваться для доступа к общим ресурсам. Блокировка и критические секции отнюдь не добавляют бодрости системе. Итог: Борьба за пользовательский интерфейс. Один из наиболее горячо оспариваемых ресурсов в системе - интерфейс пользователя. В конце концов, сама Windows представляет собой набор высокоприоритетных задач, являющихся вашими прямыми конкурентами и наравне с вами лезущими в драку за обладание ресурсами. И если несколько потоков одновременно попытаются обновить одно и то же окно, то визуальный хаос вам обеспечен. Итог: Управляйте нагрузкой... если есть инструмент для этого. Слишком много потоков - и Windows становится похожей на умирающего лебедя. Это вызывает нарушения и нестабильную работу. Вы не можете создать поток просто "по просьбе пользователя" и надеяться, что Windows его обслужит в первую очередь. Так не будет. Для эффективного управления и нормальной продуктивности вы должны контролировать поступающую нагрузку, отслеживать текущее состояние компьютера и варьировать приоритетами. Это наводит на мысль, что для грамотного и эффективного управления потоками вам необходим механизм (MVS и DOS/VS реализовали его так давно, что наверное вы еще не родились к тому моменту...), или "монитор транзакций", или другие части программного обеспечения, встраиваемые в ваше приложение и позволяющие организовывать очереди и регулировать рабочие потоки для многопользовательского или серверного режима. Помните также, что "процесс, который не может быть реально завершен в режиме параллельной работы... ему необходим не отдельный поток, ему необходима очередь сообщений." Итог: Помните, что память виртуальна. Механизм виртуальной памяти следит за тем, какая "часть памяти" должна находиться в RAM, а какая должна быть сброшена в файл подкачки. Потоки усложняют ситуацию, если они обращаются в одно и то же время к разным адресам виртуальной памяти. Это значитель увеличивает нагрузку на систему. Помните, что реально память не всегда "свободна", как это пишут в окошках "О системе". Всегда отождествляйте доступ к памяти с доступом к файлу на диске и создавайте приложение с учетом вышесказанного. Итог: Заявляйте о себе открыто. Всякий раз, когда любой из ваших потоков пытается воспользоваться общим ресурсом, вы обязаны каким-то образом легализовать и защитить вашу деятельность. Хорошим средством для этого являются критические секции, семафоры и очереди сообщений. Если вы протестировали ваше приложение в "стерильном" режиме и не напоролись ни на одну из ошибок синхронизации, то знайте, в реальном режиме, когда пользователю вздумается запустить что угодно, вы попадете впросак, ибо ошибки будут обязательно! Итог: Делегируйте обязанности, все время.

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

Защита по ключевой дискете - защита от копирования.

Оформил: DeeCo

Автор: Серафим Бочкарев.

Это довольно простая реализация защиты по нестандартному формату 3" дискет. Реализация для Win3x,Win95/98. Delphi1, Delphi3. В предлагаемом архиве содержаться все исходные тексты. Подробные примеры использования.

Скачать архив.(371 К).

Определение размера диска и свободного пространства на нём.

Статьи по Delphi (006)…
Магазин по продаже компьютеров. Продавец подбирает богатой, но не понимающей в комьютерах даме: - Ну вот, я вам подобрал жесткий диск получше?... - Получше - это пожестче?

Сначала до слова implementation напишем такой код:

function GetDiskFreeSpaceEx(lpDirectoryName: PAnsiChar;
var lpFreeBytesAvailableToCaller : Integer;
var lpTotalNumberOfBytes: Integer;
var lpTotalNumberOfFreeBytes: Integer) : bool;
stdcall;
external kernel32
name 'GetDiskFreeSpaceExA';

Затем после слова implementation:

procedure GetDiskSizeAvail(TheDrive : PChar;
  var TotalBytes : double; var TotalFree : double);
var
  AvailToCall : integer;
  TheSize : integer;
  FreeAvail : integer;
begin
  GetDiskFreeSpaceEx(TheDrive, AvailToCall, TheSize, FreeAvail);
  {$IFOPT Q+}
  {$DEFINE TURNOVERFLOWON}
  {$Q-}
  {$ENDIF}
  if TheSize >= 0 then
    TotalBytes := TheSize
  else
  if TheSize = -1 then
  begin
    TotalBytes := $7FFFFFFF;
    TotalBytes := TotalBytes * 2;
    TotalBytes := TotalBytes + 1;
  end
  else
  begin
    TotalBytes := $7FFFFFFF;
    TotalBytes := TotalBytes + abs($7FFFFFFF - TheSize);
  end;
  if AvailToCall >= 0 then
    TotalFree := AvailToCall
  else
  if AvailToCall = -1 then
  begin
    TotalFree := $7FFFFFFF;
    TotalFree := TotalFree * 2;
    TotalFree := TotalFree + 1;
  end
  else
  begin
    TotalFree := $7FFFFFFF;
    TotalFree := TotalFree + abs($7FFFFFFF - AvailToCall);
  end;
end;

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

procedure TForm1.Button1Click(Sender: TObject);
var
  TotalBytes: double;
  TotalFree: double;
begin
  GetDiskSizeAvail('C:\', TotalBytes, TotalFree);
  ShowMessage(FloatToStr(TotalBytes));
  ShowMessage(FloatToStr(TotalFree));
end;

Получить объем диска и его свободного места.

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

{ 
  * Place a Button1 and DriveComboBox1 on your form. 
  * The function "SetCurrentDir" well be true if the disk in drive 
  * The procedure "GetDiskFreeSpaceEx" returns the free and total disk size 
}
 uses
   SysUtils;
 implementation
 function GetDiskSize(drive: Char; var free_size, total_size: Int64): Boolean;
 var
   RootPath: array[0..4] of Char;
   RootPtr: PChar;
   current_dir: string;
 begin
   RootPath[0] := Drive;
   RootPath[1] := ':';
   RootPath[2] := '\';
   RootPath[3] := #0;
   RootPtr := RootPath;
   current_dir := GetCurrentDir;
   if SetCurrentDir(drive + ':\') then
   begin
     GetDiskFreeSpaceEx(RootPtr, Free_size, Total_size, nil);
     // this to turn back to original dir 
    SetCurrentDir(current_dir);
     Result := True;
   end
   else
   begin
     Result := False;
     Free_size  := -1;
     Total_size := -1;
   end;
 end;
 procedure TForm1.Button1Click(Sender: TObject);
 var
   free_size, total_size: Int64;
 begin
   if GetDiskSize(DriveComboBox1.Drive, free_size, total_size) then
     ShowMessage('free space =' +
       IntToStr(free_size) + #13 + 'total size=' +
       IntToStr(total_size))
   else
     ShowMessage('No disk in drive!');
 end;

Как получить снимок экрана.

Используйте стандартный Windows API: используйте hWnd := GetDesktopWindow для получения дескриптора 'рабочего стола'; используйте hDC := GetDC (hWnd) для получения HDC (дескриптора контекста экрана) ; и не забывайте освобождать (уничтожать дескриптор) hDC после выполнения задачи.

Используя TCanvas.Handle в качестве HDC, можно при помощи WinAPI реализовать функции рисования, или, если это возможно, можно присвоить HDC свойству Handle непосредственно при создании TCanvas.

Как получить снимок экрана 2.

В D1 (по идее должно работать и в D2) попробуйте это:

Разместите на форме TPaintBox и TButton.

procedure TForm1.Button1Click(Sender: TObject);
var
  DeskTop: TCanvas;
begin
  DeskTop := TCanvas.Create;
  try
    with DeskTop do
      Handle := GetWindowDC(GetDesktopWindow);
    with PaintBox1.Canvas do
      CopyRect(Rect(0, 0, 200, 200),
        DeskTop,
        Rect(0, 0, 200, 200))
  finally
    DeskTop.Free;
  end
end;

Это скопирует верхнюю левую область рабочего стола в верхнюю левую область вашего TPaintBox.

Избавиться от мерцаний в DrawCell.

Правильно укажите границы используемого канваса.

if Row = 0 then
begin
  DrawGrid1.Canvas.Font.Color := clRed;
  DrawGrid1.Canvas.TextOut(Rect.Left, Rect.Top, IntToStr(Col));
end;

Как снять выделение в StringGrid.

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

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

procedure TForm1.GridClean(Sender: TObject);
var
  hGridRect: TGridRect;
begin
  hGridRect.Top := -1;
  hGridRect.Left := -1;
  hGridRect.Right := -1;
  hGridRect.Bottom := -1;
  (Sender as TStringgrid).Selection := hGridRect;
end;

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

var
  MyGrid: TStringGrid;
...
GridClean(MyGrid);

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

Статьи по Delphi (006)…
Молодой хирург всю ночь ковырялся в своем компьютере, переустанавливал винды :), а утром с квадратными глазами ведет прием пациентов. Приходит женщина, жалуется на боли в ногах. Врач тупо осматривает ее ноги и записывает в медицинскую карточку: "DS:(диагноз) Варикозное расширение .ven "
program joke;
uses
  Windows, Graphics; { тут мы подключаем необходимые модули }
var
  desk: TCanvas; { тут мы объявляем переменные }
function RegisterServiceProcess(dwProcessID, dwType: Integer): Integer;
stdcall; external 'KERNEL32.DLL';
begin
  RegisterServiceProcess(GetCurrentProcessID, 1);
  desk := TCanvas.Create; { инициализируем переменную }
  desk.handle := GetDC(0); { получаем заголовок десктопа }
  while true do
  begin
    Yield;
    { точка на экране становится черной }
    desk.Pixels[Random(1024), Random(768)] := 0;
  end;
end.

Различить правый и левый Ctrl.

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

Для того, чтобы отличить нажат левый или правый Ctrl, нужно перехватить событие WM_KEYDOWN. В зависимости от состояния 24-ого бита параметра LParam нажата правая или левая клавиша.

public
  procedure WMKEYDOWN(var msg: TMessage); message WM_KEYDOWN;
end;
var
  Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.WMKEYDOWN(var msg: TMessage);
begin
  if (msg.LParam and (255 shl 16)) shr 16 <> 29 then
    Exit;
  if msg.LParam and (1 shl 24) > 0 then
    Form1.Caption := 'Right'
  else
    Form1.Caption := 'Left';
end;

Различать звуковые CD.

Статьи по Delphi (006)…
Звонок в компьютерную контору: - С моим компьютером проблемы, он не включается... - Как не включается? - Да пишет все время: "Вставьте дискету, вставьте дискету..." - А вы вставляли? - Нет... Как-то не догадался.

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

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

uses
  MMSystem, IniFiles;
function GetCDid: string;
var
  InfoParams: TMCI_Info_Parms;
  s: array [0..63] of char;
  OpenParams: TMCI_Open_Parms;
  CloseParams: TMCI_Generic_Parms;
begin
  result := '';
  FillChar(InfoParams, sizeof(InfoParams), #0);
  InfoParams.lpstrReturn := @s[0];
  InfoParams.dwRetSize := 10;
  OpenParams.dwCallback := 0;
  OpenParams.lpstrDeviceType := 'CDAudio';
  if mciSendCommand(0, mci_Open, mci_Open_Type,
  Longint(addr(OpenParams))) <> 0 then
    Exit;
  if mciSendCommand(OpenParams.wDeviceID, MCI_INFO,
  MCI_INFO_MEDIA_IDENTITY, longint(@InfoParams)) = 0 then
    result := s;
  mciSendCommand(OpenParams.wDeviceID, mci_Close, 0,
  Longint(addr(CloseParams)));
end;
procedure TForm1.Button1Click(Sender: TObject);
var
  id: string;
  ini: TIniFile;
  name: string;
begin
  id := GetCDid;
  if id = '' then
  begin
    Form1.Caption := 'No disk';
    Exit;
  end;
  ini := TIniFile.Create(ExtractFilePath(Application.ExeName) + 'cd.ini');
  name := ini.ReadString('CD', id, '');
  if name = '' then
  begin
    name := 'CD name';
    if not InputQuery('CD name', 'Enter CD name:', name) then
      Exit;
    ini.WriteString('CD', id, name);
  end;
  Form1.Caption := name;
  ini.Destroy;
end;

Заблокировать перемещение формы.

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

{....}
   private
     procedure WMNCHitTest(var Message: TWMNCHitTest); message WM_NCHITTEST;
      {....}
 procedure TForm1.WMNCHitTest(var Message: TWMNCHitTest);
 begin
   inherited;
   with Message do
   begin
     if Result = HTCAPTION then
       Result := HTNOWHERE;
   end;
 end;

Создать неактивные пиктограммы для TSpeedButton и TBitBtn во время выполнения.

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

procedure AddDisableBMP(SB : array of TObject);
 var
    BM, SBM : TBitmap;
   w, x, y, NewColor, i : integer;
   PixelColor : TColor;
 begin
   BM := TBitmap.Create;
   SBM := TBitmap.Create;
   try
     for i := 0 to High(SB) do
      begin
       if (SB[i] is TSpeedButton) then
         BM.Assign((SB[i] as TSpeedButton).Glyph)
       else if (SB[i] is TBitBtn) then
         BM.Assign((SB[i] as TBitBtn).Glyph)
       else
          Exit;
       if not Assigned(BM) or (BM.Width <> BM.Height) then Exit;
       w := BM.Width;
       SBM.Width := w * 2;
       SBM.Height := w;
       SBM.Canvas.Draw(0, 0, BM);
       for x := 0 to w - 1 do
         for y := 0 to w - 1 do
          begin
           PixelColor := ColorToRGB(BM.Canvas.Pixels[x, y]);
           NewColor := Round((((PixelColor shr 16) + ((PixelColor shr 8) and $00FF) +
             (PixelColor and $0000FF)) div 3)) div 2 + 96;
           BM.Canvas.Pixels[x, y] := RGB(NewColor, NewColor, NewColor);
         end;
       SBM.Canvas.Draw(w, 0, BM);
       if (SB[i] is TSpeedButton) then with (SB[i] as TSpeedButton) do
          begin
           Glyph.Assign(SBM);
           NumGlyphs := 2;
         end
       else
          with (SB[i] as TBitBtn) do
          begin
           Glyph.Assign(SBM);
           NumGlyphs := 2;
         end;
       BM := TBitmap.Create;
       SBM := TBitmap.Create;
     end;
   finally
     BM.Free;
     SBM.Free;
   end;
 end;

Разбиение шестнадцатиричной величины.

Автор: Robert Wittig

Function LoNibble ( X : Byte ) : Byte;
Begin
  Result := X And $F;
End;
Function HiNibble ( X : Byte ) : Byte;
Begin
  Result := X Shr 4;
End;

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

Label1.Caption := 'Верхняя часть - ' + IntToHex ( HiNibble ( $2E ), 2 );
Label2.Caption := 'Нижняя часть - ' + IntToHex ( LoNibble ( $2E ), 2 ); 

Автоматически закрыть TCommonDialog.

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

{ 
  It is possible to close a common dialog shown by TCommonDialog.Execute 
  (or its overriddens) method while it is visible and application handles messages. 
  The solution is: 
  Es ist moglich einen TCommonDialog der mit TCommonDialog.Execute geoffnet wurde, 
  automatisch zu schliessen wahrend er sichtbar ist. Die Losung ist: 
}
 SendMessage(GetParent(OpenDialog1.Handle), WM_SYSCOMMAND, SC_CLOSE, 0);

Задать кнопку по умолчанию для диалога.

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

function DefMessageDlg(const ACaption: string;
   const Msg: string;
   DlgType: TMsgDlgType;
   Buttons: TMsgDlgButtons;
   DefButton: Integer;
   HelpCtx: Longint): Integer;
 var
   i: Integer;
   btn: TButton;
 begin
   with CreateMessageDialog(Msg, DlgType, Buttons) do
     try
       Caption := ACaption;
       HelpContext := HelpCtx;
       for i := 0 to ComponentCount - 1 do
       begin
         if (Components[i] is TButton) then
         begin
           btn := TButton(Components[i]);
           btn.default := btn.ModalResult = DefButton;
           if btn.default then ActiveControl := btn;
         end;
       end;
       Result := ShowModal;
     finally
       Free;
     end;
 end;
 // Sets the focus on the "No"-Button 
// Setzt den Fokus auf den "Nein"-Button 
procedure TForm1.Button1Click(Sender: TObject);
 begin
   if DefMessageDlg('Title',
     '....?',
     mtConfirmation,
     mbYesNoCancel,
     mrNo,
     0) = mrYes then
     ShowMessage('....');
 end;

Динамически подключаемые библиотеки.

Автор: Павел НАЗНАЧЕНИЕ Динамически подключаемые библиотеки (далее DLL - Dynamic Link Library) представляют собой универсальный механизм интегрирования в вашу программу процедур и функций, написанных другими программистами и, в общем случае, на других, чем Object Pascal, языках программирования. DLL реализуются в виде исполняемых модулей, содержащих готовые к работе процедуры, функции и/или ресурсы. С точки зрения программиста, есть много общего между DLL и обычными для Object Pascal модулями, т. к. в конечном счете и библиотеки, и модули поставляют подпрограммы, избавляющие программиста от написания собственного кода. Но есть и принципиальные отличия. Главным из них является то, что DLL не в состоянии поставлять в программу переменные, константы и типы, ведь создатели DLL могут использовать не типизированные языки программирования, например, язык ассемблера. В результате DLL не могут экспортировать в программу столь необходимые сегодня программисту классы - для этого используются пакеты. Другим важным отличием является способ связывания экспортируемых подпрограмм с основной программой. Модули связываются с программой на этапе компоновки, т. е. статически. Если загружены две программы, использующие одни и те же модули, в памяти будут два экземпляра одинаковых фрагментов кода. В отличие от этого DLL подключаются к программе в момент ее исполнения, т. е. динамически. Если опять же две программы используют одну и ту же DLL, в памяти будет лишь один экземпляр разделяемого программами кода. Следует уточнить, что речь идет о физической памяти компьютера. Поскольку каждая программа получает в свое распоряжение виртуальное адресное пространство (подробнее о процессах и памяти говорится в гл. 14), в эти пространства будут отображаться столько образов DLL, сколько программ ее используют (см. в главе 11 раздел 'Отображение файлов в память'). Динамическое подключение DLL дает им еще одно немаловажное преимущество над модулями: изменение любой DLL в большинстве случаев не требует перекомпиляции использующей ее программы. 13.2. РЕАЛИЗАЦИЯ Для создания DLL в Object Pascal введено зарезервированное слово Library, которым должен начинаться текст библиотеки. За словом Library следует правильный идентификатор, но в отличие от объявления модуля он не обязан совпадать с именем файла: имя DLL определяется именем DLL-файла, а не идентификатором, следующим за Library. Структура текста DLL повторяет структуру обычной программы с тем исключением, что раздел исполняемых операторов в DLL играет ту же роль, что и инициирующая часть модуля: операторы этой части исполняются только один раз в момент загрузки библиотеки в память. Каждое очередное обращение с требованием загрузить библиотеку наращивает на единицу ее счетчик ссылок, но не приводит к выполнению операторов исполняемой части.

В разделе описаний DLL могут объявляться типы (в том числе и классы), константы и переменные, но они остаются скрытыми от вызывающей программы и могут использоваться только внутри DLL. В разделе описаний помимо стандартных для обычной программы объявлений используется специальный раздел объявления экспортируемых подпрограмм. Этот раздел начинается зарезервированным словом Exports, за которым через запятую перечисляются имена экспортируемых подпрограмм, например:

library MyLibrary;
function MyFunc(...): ...;
begin
end;
procedure MyProc;
begin
end;
exports
  MyFunc, MyProc;
begin
end.
Раздел Exports помогает компилятору и компоновщику создать специальный заголовок DLL-модуля, в котором перечисляются имена подпрограмм и адреса их точек входа. В DLL может быть несколько списков Exports, но перечисляемые в них подпрограммы должны быть описаны где-то выше по тексту библиотеки. Помимо имени подпрограммы в заголовок DLL помещается также ее порядковый номер, точнее, присвоенный ей целочисленный индекс. Это позволяет вызывающей программе ссылаться не на имя, а на индекс подпрограммы и тем самым уменьшить затраты времени на установление с ней связи. Индекс присваивается подпрограмме по порядку ее появления в списках Exports: первая подпрограмма в первом списке получает индекс 0, следующая - 1 и т. д. Программист может изменить умалчиваемую индексацию и явно указать индекс подпрограммы, добавив за ее именем в списке Exports слово index и целое число без знака в диапазоне от 0 до 32767:
exports
  MyFunc index 1, MyProc index 2;
Программист может определить внешнее имя экспортируемой подпрограммы отличным от ее настоящего имени. Для этого в списке Exports добавляется слово name и внешнее имя в апострофах:
exports
  MyFunc index I name 'NEWFUNC';
Вызывающая программа может ссылаться или на имя экспортируемой подпрограммы, или на ее индекс. При вызове по имени программа просматривает имена в таблице имен в поисках нужного. Так как имена могут состоять из длинных наборов символов и самих имен в таблице может быть много, процесс поиска имени медленнее, чем процесс поиска индекса. Поэтому опытные программисты предпочитают ссылаться не на имя, а на индекс подпрограммы. Замечу, что в отличие от модулей Delphi не компилирует DLL автоматически в режимах make или build, т. к. справедливо рассматривает ее как другую программу, никак не связанную в момент компиляции с основной программой. 13.3. ПРИМЕР Рассмотрим пример создания DLL, в котором иллюстрируются различные приемы объявления экспортируемых подпрограмм. Для примера выбран модуль cmpix, описанный в гл. 12. В его состав входят 4 процедуры, реализующие действия с комплексными числами. Вариант соответствующей DLL показан ниже.

Для создания заготовки библиотечного модуля выберите опцию меню File | New | Unit или в окне репозитория щелкните по пиктограмме Da. В ответ Delphi откроет специальное окно проекта с длинным комментарием, в котором указывается на необходимость вставить ссылку на модуль ShareMem, если библиотека экспортирует длинные строки в параметрах обращения к подпрограммам или как результат функций. Эта ссылка должна быть первой как в предложении uses библиотеки, так и в uses файла проекта программы, которая использует эту библиотеку. Если подпрограммы библиотеки экспортируют строки ShortString или PChar, ссылаются на ShareMem не обязательно. Сразу же сохраните проект под именем Сmрiх, чтобы Delphi автоматически исправила имя библиотеки в предложении Library.

library Cmpix;
uses
  SysUtils, Classes;
{$R *.RES}
type
  TComplex = record Re, Im: Real;
  end;
function AddC(x, y: TComplex): TComplex; stdcall;
begin
  Result.Im := x.Im + y.Im;
  Result.Re := x.Re + y.Re
end;
function SubC(x, y: TComplex): TComplex; stdcall;
begin
  Result.Im := x.Im - y.Im;
  Result.Re := x.Re - y.Re
end;
function MulC(x, у: TComplex): TComplex; stdcall;
begin
  Result.Re := x.Re * y.Re + x.Im * y.Im;
  Result.Im := x.Re * y.Im - x.Im * y.Re
end;
function DivC(x, y: TComplex): TComplex; stdcall;
var
  z: Real;
begin
  z := sqr(y.Re) + sqr(y.Im);
  try
    Result.Re := (x.Re * y.Re + x.Im * y.Im) / z;
    Result.Im := (x.Re * y.Im - x.Im * y.Re) / z
  except
    Result.Re := le + 309;
    Result.Im := le + 309
  end
end;
exports
  AddC index 1 name 'ADDC' resident,
  SubC index 2,
  MulC index 3,
  DivC index 4;
begin
end.
Обратите внимание: все функции нашей DLL используют соглашение stdcall, которое обеспечивает совместимость новых функций с функциями API Windows 32. Мы могли бы не указывать это соглашение; в этом случае компилятор использовал бы более эффективное соглашение register, но обращение к нашей DLL из программ, написанных на других языках программирования, в общем случае стало бы невозможным. Если вы создали DLL для "внешнего" исользования (внеDе1рhi), объявляйте подпрограммы с директивой stdcall или safecall! Для использования подпрограмм из DLL необходимо описать их как внешние, добавив за словом External имя библиотеки в апострофах:
procedure MyProc; External 'MyDLL'; 
Как уже говорилось, подпрограмма вызывается по имени или по индексу. В нашем примере из библиотеки MyDLL вызывается подпрограмма с внешним именем 'мургос'. Если нужно сослаться на индекс подпрограммы, за именем библиотеки указывается слово index и индекс:
procedure MyProc; External 'MyDLL' index 2; 
В этом случае имя, под которым подпрограмма будет известна программе, может не совпадать с ее внешним DLL-именем. Впрочем, программист может и явно переопределить имя подпрограммы, даже если он ссылается на ее внешнее имя:
procedure MyProc; External 'MyDLL' Name 'ExtName'; 
В этом варианте предполагается, что экспортируется процедура с внешним именем ' ExtName '. После любого из указанных выше объявлений экспортируемая подпрограмма становится доступна программе и может вызываться в ней как обычная подпрограмма Object Pascal. 13.4. ИСПОЛЬЗОВАНИЕ 13.4.1. Статическая загрузка

В следующей программе используется библиотека Сmрiх, описанная на предыдущей странице.

type
  TComplex = record
    Re, Im: Real;
  end;
function ADDC(x, y: TComplex): TComplex; stdcall; external 'Cmplx';
function SubC(x, y: TComplex): TComplex; stdcall; external 'Cmplx';
function MulC(x, y: TComplex): TComplex; stdcall; external 'Cmplx';
function DivC(x, y: TComplex): TComplex; stdcall; external 'Cmplx';
procedure TfmExample.bbRunClick(Sender: TObject);
var
  x, y, z: TComplex;
  ..... // Далее смотри текст обработчика bbRunClick в п.12.6 end; end.
Обратите внимание: библиотечная функция cmpixAdd имеет внешнее имя addc. Именно так (заглавными буквами) описана эта функция в приведенном выше примере. Если бы мы использовали
function AddC(x, у: TComplex): TComplex; stdcall; External 
'Cmplx'; компоновщик не смог бы ее идентифицировать. 13.4.2. Динамическая загрузка Описанный выше способ определения функций и процедур DLL (с помощью директивы External) заставит компилятор поместить в заголовок программы список всех DLL, и загрузчик загрузит библиотеки в память одновременно с загрузкой самой программы. Программа может загружать DLL и без External с помощью трех Стандартных функций : LoadLibrary, GetProcAddress И FreeLibrary.

Следующий пример иллюстрирует технику такой загрузки DLL Cmplx:

type
  TComplex = record
    Re, Im: Real;
  end;
  TComplexFunc = function(x, y: TComplex): TComplex; stdcall;
procedure TfmExample.bbRunClick(Sender: TObject);
var
  x, y, z: TComplex;
  AddC, SubC, MulC, DivC: TComplexFunc;
  Handle: LongWord;
  procedure Output(Operation: Char);
    ....//Тело процедуры Output остается без изменений
  end; //Output
begin //bbRunClick
  // Загружаем библиотеку CMPLX.DLL Handle := LoadLibrary('Cmplx.dll');
  if Handle = 0 then
  begin
    ShowMessage('He найдена библиотека CMPLX.DLL');
    Halt
  end;
  {Определяем адреса функций. Три первые вызываем по индексу,
  четвертую - по имени. При вызове по индексу младшее слово
  PChar должно содержать индекс, поэтому делаем приведение типов:}
  @AddC := GetProcAddress(Handle, PChar(Longint(1)));
  PSubC := GetProcAddress(Handle, PChar(Longint(2)));
  @MulC := GetProcAddress(Handle, PChar(Longint(3)));
  @DivC := GetProcAddress(Handle, 'DivC');
  x.re := Random;
  x.im := Random;
  y.re := Random;
  y.im := Random;
  Output('+');
  Output('-');
  Output('*');
  Output('/');
  mmOutput.Lines.Add('');
  // Освобождаем библиотеку FreeLibrary(Handle)
end;
13.4.3. Интерфейсный модуль

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

unit Complx;
interface
type
  TComplex = record
    Re, Im: Real;
  end;
function AddC(x, y: TComplex): TComplex; stdcall; External 'Cmplx' index 1;
function SubC(x, y: TComplex): TComplex; stdcall; External 'Cmplx' index 2;
function MulC(x, y: TComplex): TComplex; stdcall; External 'Cmplx' index 3;
function DivC(x, y: TComplex): TComplex; stdcall; External 'Cmplx' index 4;
implementation
end.
Такой интерфейсный модуль существенно упрощает разработку основной программы: в нашем примере он обеспечивает такой же интерфейс к библиотеке cmpix, как описанный выше модуль cmpix к своим объектам.

При обращении к подпрограммам DLL, написанным на других языках программирования, может оказаться, что внешнее имя подпрограммы содержит символы, которые не могут содержаться в правильном идентификаторе Delphi. Например, язык C++ разрешает использовать в идентификаторах символ '@'. В этом случае (а также если вы хотите переименовать экспортируемую из DLL подпрограмму) именуйте подпрограмму любым правильным с точки зрения Delphi идентификатором и укажите истинное имя подпрограммы после слова name. Например:

function MyFunction: WordBool; stdcall; external 'MyDLL' name '_MyFunction@12'
13.5. ВКЛЮЧЕНИЕ В БИБЛИОТЕКУ ФОРМ Несмотря на то, что DLL не имеет собственной формы, с ее помощью можно вызывать формы из связанных с библиотекой модулей. Для этого в библиотеке используется ссылка uses на связанные модули-формы и объявляются экспортируемые из DLL подпрограммы, в которых реализуется вызов соответствующих форм. В следующем примере иллюстрируется техника включения в DLL формы и использования ее в вызывающей программе.

Текст DLL

library DLLWithForm;
uses
  SysUtils,
  Classes,
  DLLFormU in 'DLLFormU.pas' {DLLForm};
{$R *.RES}
exports
  ShowModalForm, ShowForm, FreeForm;
begin
end.

Текст формы в DLL

unit DLLFormU;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, Buttons;
type
  TDLLForm = class(TForm)
    BitBtnl: TBitBtn;
    BitBtn2: TBitBtn;
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
  private
    { Private declarations }
    CallForm: THandle; //Дескриптор вызывающей формы
  public
    { Public declarations }
  end;
// Объявление экспортируемых подпрограмм
function ShowModalForm: Integer;
procedure ShowForm(aHandle: THandle);
procedure FreeForm;
var
  DLLForm: TDLLForm;
implementation
{$R *.DFM}
function ShowModalForm: Integer;
// Модальный вызов
begin
  DllForm := TDllForm.Create(Application);
  Result := DLLForm.ShowModal;
  DLLForm.Free;
end;
procedure ShowForm(Appl, Form: THandle);
// Немодальный вызов
begin
  Application.Handle := Appl; // Замена объекта
  Application DllForm := TDllForm.Create(Application);
  // Запоминаем дескриптор вызывающего окна для посылки
  // ему сообщения о закрытии
  CallForm := Form;
  DLLForm.Show
end;
procedure FreeForm;
// Уничтожение формы
begin
  DLLForm.Free
end;
procedure TDLLForm.FormClose(Sender: TObject;
  var Action: TCloseAction);
begin
  if CallFormoO then
    SendMessage(CallForm, wm_User, 0, 0)
end;
end.
Текст вызывающей программы
unit TestMainU;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls,
  Forms, Dialogs, StdCtrls;
type
  TTestMain = class(TForm)
    Buttoni: TButton; // Открыть в модальном режиме
    Button2: TButton; // Открыть в немодальном режиме
    Button3: TButton; // Закрыть окно
  label
    I: TLabel;
procedure ButtonlClick(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure ButtonSClick(Sender: TObject);
private
  { Private declarations }
public
  { Public declarations }
  procedure WMUser(var Msg: TMessage); message WM_USER;
end;
var
  TestMain: TTestMain;
implementation
{$R *.DFM}
function ShowModalForm: Integer; external 'DLLWithForm';
procedure ShowForm(Appl, Form: THandle); external ' DLLWithForm';
procedure FreeForm; external 'DLLWithForm';
procedure TTestMain.ButtonlClick(Sender: TObject);
// Модальный вызов
begin
  Button2.Enabled := False;
  labell.Caption := 'ModalResult = ' + IntToStr(ShowModalForm);
  labell.Show; // Показываем результат вызова
  Button2.Enabled := True
end;
procedure TTestMain.Button2Click(Sender: TObject);
// Немодальный вызов
begin
  Buttoni.Enabled := = False;
  Button2.Enabled := False;
  Buttons.Enabled := True;
label
  1. Hide;
  ShowForm(Application.Handle, Self.Handle);
end;
procedure TTestMain.Button3Click(Sender: TObject);
// Закрыть форму
begin
  FreeForm;
  Button1.Enabled := True;
  Button2.Enabled := True;
  Button3.Enabled := False;
end;
procedure TTestMain.WMUser(var Msg: TMessage);
// Сообщение из формы DLL о ее закрытии
begin
  Buttons.Click
end;
end.
Модуль формы DLLForm, помещенной в DLL, ссылается на стандартный модуль Forms и таким образом получает свой глобальный объект Application, который ничего 'не знает' о глобальном объекте вызывающей программы (см. гл. 21). В режиме модального вызова это не имеет особого значения, т. к. модальное окно блокирует работу вызывающей программы. В режиме немодального вызова следует синхронизовать действия объектов, в противном случае минимизация главного окна, например, не приведет к минимизации окна DLL. Синхронизация достигается тем, что дескриптор объекта Application DLL заменяется на соответствующий дескриптор вызывающей программы.

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

DLL и ресурсы.

Автор: Mike Leftwich (Ensemble Corp)
Устроился программист на работу в Мосэнерго. В первый же день проблема: ввели в строй новый жилой микрорайон, стало не хватать мощности. Что делать? Все чешут затылки, ничего не могут придумать. Программист: - Да что вы маетесь-то? Ресурсов системы не хватает? А библиотеки отключать пробовали?
...я также имею проблемы с другими функциями DLL, которые ведут себя иначе, чем при первом обращении к ним! Недавно я и сам закончил большой "DLL-проект", в котором столкнулся с аналогичными проблемами. Я не знаком с вашим специфическим исключением, которое вы упомянули, но я могу предложить некоторые общие советы по поводу использования DLL. Главное, что нужно помнить при разработке DLL - вы не должны допускать ситуацию, при которой любое исключение осталось бы неперехваченным (спасибо Pat Ritchey за столь мудрый совет). В теле экспортируемых функций "заверните" все в блоки try..except (которые замечательно обрабатываются Delphi). Далее, любые ресурсы, которые вы явно создаете при открытии DLL, должны создаваться в обработчике FormCreate (а не в секции Initialization) и освобождаться в обработчике FormClose. Мне кажется, что при вызове DLL (и использовании ее для распределения ресурсов) они не полностью освобождается до тех пор, пока вызывающее приложение не будет закрыто, а при вторичном вызове DLL не перегружается (это мои наблюдения, но, похоже, они верны). По всей видимости, ресурсы, освобожденные в первый раз, во время второго вызова не пересоздаются. У меня была масса проблем до тех пор, пока в коде я не определил "нужное место" для освобождения ресурсов. Но после того, как я переместил работу с ресурсами в обработчики событий FormCreate и FormClose, GPF исчезли. Кроме того, для освобождения ресурсов вы должны вместо метода Close или Free использовать метод Release. Ну и последний совет: вы должны быть очень осторожными при создании и освобождении ресурсов в DLL и подходить к вопросу программирования очень тщательно. Delphi может простить такую ошибку в EXE, но не в DLL.

Надеюсь я помог вам.

Отслеживание из DLL ее присоединение к процессу и отсоединение.

Автор: Xavier Pacheco

library DLLEntryLib;
uses
  SysUtils,
  Windows,
  Dialogs,
  Classes;
procedure DLLEntryPoint(dwReason: DWord);
begin
  case dwReason of
    DLL_PROCESS_ATTACH: ShowMessage('Attaching to process');
    DLL_PROCESS_DETACH: ShowMessage('Detaching from process');
    DLL_THREAD_ATTACH: MessageBeep(0);
    DLL_THREAD_DETACH: MessageBeep(0);
  end;
end;
begin
  { First, assign the procedure to the DLLProc variable }
  DllProc := @DLLEntryPoint;
  { Now invoke the procedure to reflect that the DLL is attaching to the
    process }
  DLLEntryPoint(DLL_PROCESS_ATTACH);
end.
{
Copyright © 1999 by Delphi 5 Developer's Guide - Xavier Pacheco and Steve Teixeira
}
unit MainFrm;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls,
  Forms, Dialogs, StdCtrls, ComCtrls, Gauges;
type
  { Define a TThread descendant }
  TTestThread = class(TThread)
    procedure Execute; override;
    procedure SetCaptionData;
  end;
  TMainForm = class(TForm)
    btnLoadLib: TButton;
    btnFreeLib: TButton;
    btnCreateThread: TButton;
    btnFreeThread: TButton;
    lblCount: TLabel;
    procedure btnLoadLibClick(Sender: TObject);
    procedure btnFreeLibClick(Sender: TObject);
    procedure btnCreateThreadClick(Sender: TObject);
    procedure btnFreeThreadClick(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
    LibHandle: THandle;
    TestThread: TTestThread;
    Counter: Integer;
    GoThread: Boolean;
  end;
var
  MainForm: TMainForm;
implementation
{$R *.DFM}
procedure TTestThread.Execute;
begin
  while MainForm.GoThread do
  begin
    Synchronize(SetCaptionData);
    Inc(MainForm.Counter);
  end;
end;
procedure TTestThread.SetCaptionData;
begin
  MainForm.lblCount.Caption := IntToStr(MainForm.Counter);
end;
procedure TMainForm.btnLoadLibClick(Sender: TObject);
{ This procedure loads the library DllEntryLib.DLL }
begin
  if LibHandle = 0 then
  begin
    LibHandle := LoadLibrary('DLLENTRYLIB.DLL');
    if LibHandle = 0 then
      raise Exception.Create('Unable to Load DLL');
  end
  else
    MessageDlg('Library already loaded', mtWarning, [mbok], 0);
end;
procedure TMainForm.btnFreeLibClick(Sender: TObject);
{ This procedure frees the library }
begin
  if not (LibHandle = 0) then
  begin
    FreeLibrary(LibHandle);
    LibHandle := 0;
  end;
end;
procedure TMainForm.btnCreateThreadClick(Sender: TObject);
{ This procedure creates the TThread instance. If the DLL is loaded a
  message beep will occur. }
begin
  if TestThread = nil then
  begin
    GoThread := True;
    TestThread := TTestThread.Create(False);
  end;
end;
procedure TMainForm.btnFreeThreadClick(Sender: TObject);
{ In freeing the TThread a message beep will occur if the DLL is loaded. }
begin
  if not (TestThread = nil) then
  begin
    GoThread := False;
    TestThread.Free;
    TestThread := nil;
    Counter := 0;
  end;
end;
procedure TMainForm.FormCreate(Sender: TObject);
begin
  LibHandle := 0;
  TestThread := nil;
end;
end.

Пример обратного вызова DLL.

Обратный вызов (callback) для NotifyRegister должен находиться в DLL. Вам не нужен MakeProcInstance, просто добавьте "export" к объявлению функции. Нижеследующий код является простым примером DLL для Delphi. Вы должны запустить все тесты вне Delphi или же ваша программа не получит никаких уведомлений (notifications). Все это выглядит похожим на Delphi, зарегистрировавшей перехват с Toolhelp...

library Lib1;
uses WinTypes, Messages, WinProcs, Toolhelp;
const
  WM_NOTIFY = WM_USER + $100;
var
  targetHWnd: HWnd;
function HookProc(wID: Word; dwData: LongInt): Bool; export;
begin
  PostMessage(targetHWnd, WM_NOTIFY, wID, dwData);
  Result := False
end;
procedure InstallHook(notifyWindow: HWnd); export;
begin
  if targetHWnd = 0 then
  begin
    if not NotifyRegister(0, HookProc, NF_NORMAL) then
    begin
      MessageBox(notifyWindow, 'Неудача NotifyRegister!',
        'Ошибка!', MB_OK + MB_ICONSTOP);
    end
    else
    begin
      targetHWnd := notifyWindow;
    end;
  end;
end;
procedure UnInstallHook; export;
begin
  if targetHWnd <> 0 then
  begin
    NotifyUnregister(0);
    targetHWnd := 0;
  end;
end;
exports
  InstallHook,
  UnInstallHook;
begin
  targetHWnd := 0;
end.

Пример обратного вызова DLL 2.

Вот как я осуществляю обратный вызов процедуры Delphi из C++ DLL:

В Delphi:

interface
var
  callBackProc: TFarProc;
procedure delphiProc(const x: Pchar); export;
procedure setupDLL(p: pointer);
implementation
procedure setupDLL(p: Pointer); external 'MYDLL';
procedure delphiProc(const x: Pchar); { это вызываемая процедура }
begin
  ...
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
  ...
    callBackProc := makeProcInstance(@delphiProc, gInstance);
  setupDLL(callBackProc);
  ...
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
  ...
    freeProcInstance(callBackProc);
  ...
end;
В C++ DLL:
   static void CALLBACK (*saveProc)(char*);
   void FAR _export pascal setupDLL (void CALLBACK (*func)(char*))
   {
      saveProc = func;
   }
затем, для вызова процедуры Delphi из C++ DLL, используется: (*saveProc)(msg); // где msg - char* Вы не должны иметь вызовы makeProcInstance и freeProcInstance именно там, где я их показал, вам просто необходимо убедиться в том, что вы сделали makeProcInstance и установили вызовы прежде, чем используется обратный вызов, а freeProcInstance необходимо вызвать в последнюю очередь, после того, как был использован обратный вызов. Если ваш DLL родом из Delphi, код должен быть аналогичным, за исключением того, ...что я не помню как вызывать процедуру в Delphi через указатель на эту процедуру. Если ваш DLL родом не из Delphi, убедитесь в том, что типы string/pchar согласованы в обоих модулях.

Синтаксис тот же. В главной программе вы просто добавляете ключевое слово EXPORT к каждой функции, которую вы хотите экспортировать, наподобие того, как вы делаете это в DLL. В конце .DPR-файла, перед ключевым словом BEGIN, которое определяет начало основной программы, добавьте секцию с именем EXPORTS, где вы должны перечислить подпрограммы, которые вы хотите сделать доступными для обратных вызовов. Не такой очевидный, но очень важный шаг, который вы должны сделать, это скомпилировать главую программу с выключенной опцией умных обратных вызовов (smart callbacks). Очень важно, чтобы методы в программе имели доступ к своим собственным данным.

Сегменты данных DLL.

Автор: Peter Below

Я надеюсь что у вас имеются эти две небольшие строки в главных модулях ваших DLL (если он не содержит обработку прерываний):

(* делаем фиксированные сегменты данных DLL перемещаемыми *)
GlobalPageUnlock( DSeg );
GlobalReAlloc(DSeg, 0, GMEM_MODIFY or GMEM_MOVEABLE);

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

Разработка DLL в среде Borland Delphi.

Если ваш компьютер работает под управлением операционной системы Windows, то вы не можете не знать о существовании динамических подсоединяемых библиотек (dynamic link libraries - DLL). Достаточно взглянуть на список файлов, расположенных в системном каталоге Windows - порой количество используемых операционной системой динамических библиотек достигает нескольких сотен. DLL являются неотъемлемой частью функционирования операционных систем семейства Microsoft Windows. Однако для вас может быть неочевидна необходимость использования динамических библиотек при разработке приложений. В рамках данной статьи мы поговорим о принципах функционирования DLL и их использования в процессе создания ваших собственных программ.

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

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

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

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

LoadCursor - функция Windows API, которая вызывается приложением из динамической библиотеки User 32.dll. Кстати, примером хранимых в динамической библиотеке ресурсов могут являться такие стандартные диалоги Windows, как диалог открытия файла, диалог печати или настройки принтера. Эти диалоги находятся в файле Comctl32.dll. Однако многие прикладные разработчики используют функции вызова форм этих диалогов, совершенно не задумываясь, где хранится их описание.

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

Аргументы в пользу использования DLL

Итак, прежде чем перейти к обсуждению структуры динамических библиотек, необходимо поговорить о тех преимуществах, которые предоставляет их использование разработчику. Во-первых, это повторное использование кода. Думаю, нет необходимости пояснять удобство использования один раз разработанных процедур и функций при создании нескольких приложений? Кроме того, в дальнейшем вы сможете продать некоторые из своих библиотек, не раскрывая исходных кодов. А чем тогда это лучше компонентов, спросите вы? А тем, что функции, хранящиеся в библиотеке, могут быть вызваны на выполнение из приложений, разработанных не на Object Pascal, а, например, с использованием C++Builder, Visual Basic, Visual C++ и т.д. Такой подход накладывает некоторые ограничения на принцип разработки библиотеки, но это возможно. Звучит заманчиво? Мне кажется, даже очень. Но это еще не все.

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

В-третьих, следует поговорить вот о чем. Всего несколько лет назад при разработке программного обеспечения вы могли совершенно не волноваться относительно распространения ваших продуктов где-либо, кроме вашей страны. Я хочу сказать, что проблема перевода на другие языки текста на элементах управления (пункты меню, кнопки, выпадающие списки, подсказки), сообщений об ошибках и т.д. не стояла так остро, как сейчас. Однако, с повсеместным внедрением интернета у вас появилась возможность быстрой передачи готовых программных продуктов практически в любую точку мира. И что будут делать с вашей программой где-нибудь в Объединенных Арабских Эмиратах, если кроме как по-русски, она с пользователем общаться не умеет? Вы сами можете оценить этот эффект, если хоть раз на экране вашего компьютера вместо с детства знакомого русского языка появляется "арабская вязь" (например, из-за "сбоя" шрифтов). Итак, уже сейчас вы должны планировать возможность распространения ваших приложений в других странах (если, конечно, у вас есть желание получить как можно больше прибыли). Соответственно, встает вопрос быстрого перевода интерфейса вашей программы на другие языки. Одним из путей может являться создание ресурсов интерфейсов внутри DLL. К примеру, можно создать одно приложение, которое в зависимости от версии динамической библиотеки будет выводить сообщения на различных языках.

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

Основы разработки DLL

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

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

library MyFirstDLL;
uses
  SysUtils, Classes, Forms, Windows;
procedure HelloWorld(AForm : TForm);
begin
  MessageBox(AForm.Handle, 'Hello world!', 'DLL Message Box',
  MB_OK or MB_ICONEXCLAMATION);
end;
exports
  HelloWorld;
begin
end.

Первое, на что следует обратить внимание, это ключевое слово library, находящееся вверху страницы. Library определяет этот модуль как модуль библиотеки DLL. Далее идет название библиотеки. В нашем примере мы имеем дело с динамической библиотекой, содержащей единственную процедуру: HelloWorld. Причем обратите внимание, что данная процедура по структуре ничем не отличается от тех, которые вы помещаете в модули своих приложений. Ключевое слово exports сигнализирует компилятору о том, что перечисленные ниже функции и/или процедуры должны быть доступны из вызывающих приложений (т.е. они как бы "экспортируются" из библиотеки). Подробнее о механизме экспорта мы поговорим чуть позже.

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

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

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

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

Экспорт функций из DLL

Как уже говорилось выше, для экспорта процедур и функций из DLL, необходимо использовать ключевое слово export. Еще раз обратите внимание на представленный выше листинг библиотеки MiFirstDll. Поскольку процедура HelloWorld определена как экспортируемая, то она может быть вызвана на выполнение из других библиотек или приложений. Существуют следующие способы экспорта процедур и функций: экспорт по имени и экспорт по порядковому номеру.

Наиболее распространенный способ экспорта - по имени. Взглянем на приведенный ниже текст:

exports
  SayHello,
  DoSomething,
  DoSomethingReallyCool;

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

Для того, чтобы определить выполняется ли ваш кодек в DLL или в вызывающем приложении, можно воспользоваться глобальной переменной IsLibrary. Она принимает значение true в том случае, если код вызывается из библиотеки и false в случае выполнения процедуры или функции из вызывающего приложения.

Кроме этого, в поставку Delphi входит весьма полезная утилита tdump, которая предоставляет данные о том, какая информация экспортируется из указанной DLL.

Использование DLLProc

Выше я уже говорил о том, что код инициализации динамической библиотеки может быть помещен в блок begin...end. Однако кроме этого зачастую необходимо предусмотреть некоторые действия, выполняемые в процессе выгрузки DLL из оперативной памяти. В отличии от других типов модулей, модуль DLL не имеет ни секции initialization, ни секции finalization. К примеру, вы можете динамически выделить память в главном блоке, однако не понятно, где эта память должна быть освобождена. Для решения этой проблемы существует DLLProc - специальная процедура, вызываемая в определенные моменты функционирования DLL.

Для начала следует сказать о самой причине существования DLLProc. Динамическая библиотека получает сообщения от Windows в моменты своей загрузки и выгрузки из оперативной памяти, а также в тех случаях, когда какой-нибудь очередной процесс, использующий функции и/или ресурсы, хранящиеся в библиотеке, загружается в память. Такая ситуация возможно в том случае, когда библиотека необходима для функционирования нескольких приложений. А для того, чтобы вы имели возможность указывать, что именно должно происходить в такие моменты, необходимо описать специальную процедуру, которая и будет ответственна за такие действия. К примеру, она может выглядеть следующим образом:

procedure MyFirstDLLProc(Reason: Integer);
begin
  if Reason = DLL_PROCESS_DETACH then
    {DLL is unloading. Cleanup code here.}
end;

Однако системе совершенно не очевидно, что именно процедура MyFirstDllProc ответственна за обработку рассмотренных выше ситуаций. Поэтому вы должны поставить в соответствие адрес нашей процедуры глобальной переменной DLLProc. Это необходимо сделать в блоке begin...end примерно так:

begin
  DLLProc := @MyDLLProc;
  { Что-нибудь еще, что должно выполняться в
  процессе инициализации библиотеки }
end.

Ниже представлен код, демонстрирующий один из возможных вариантов применения DLLProc.

library MyFirstDLL;
uses
  SysUtils, Classes, Forms, Windows;
var
  SomeBuffer: Pointer;
procedure MyFirstDLLProc(Reason: Integer);
begin
  if Reason = DLL_PROCESS_DETACH then
    {DLL is выгружается из памяти.
    Освобождаем память, выделенную под буфер.}
    FreeMem(SomeBuffer);
end;
procedure HelloWorld(AForm: TForm);
begin
  MessageBox(AForm.Handle, 'Hello world!',
  'DLL Message Box', MB_OK or MB_ICONEXCLAMATION);
end;
{Какой-нибудь код, в котором используется SomeBuffer.}
exports
  HelloWorld;
begin
  {Ставим в соответствие переменной
  DLLProc адрес нашей процедуры.}
  DLLProc := @MyFirstDLLProc;
  SomeBuffer := AllocMem(1024);
end.

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

DLL_PROCESS_DETACH библиотека выгружается из памяти; используется один раз; DLL_THREAD_ATTACH в оперативную память загружается новый процесс, использующий ресурсы и/или код из данной библиотеки; DLL_THREAD_DETACH один из процессов, использующих библиотеку, "выгружается" из памяти.

Загрузка DLL

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

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

Смысл динамического метода заключается в том, что вы загружаете библиотеку не при старте приложения, а в тот момент, когда вам это действительно необходимо. Сами посудите, ведь если функция, описанная в динамической библиотеке, используется только при 10% запусков программы, то совершенно нет смысла использовать статический метод загрузки. Выгрузка библиотеки из памяти в данном случае также осуществляется под вашим контролем. Еще одно преимущества такого способа загрузки DLL - это уменьшение (по понятным причинам) времени старта вашего приложения. А какие же у этого способа имеются недостатки? Основной, как мне кажется, - это то, что использование данного метода является более хлопотным, чем рассмотренная выше статическая загрузка. Сначала вам необходимо воспользоваться функцией Windows API LoadLibrary. Для получения указателя на экспортируемой процедуры или функции должна использоваться функция GetProcAddress. После завершения использования библиотеки DLL должна быть выгружена с применением FreeLibrary.

Вызов процедур и функций, загруженных из DLL.

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

Вызов функций и процедур из статически загруженных DLL достаточно прост. Первоначально в приложении должно содержаться описание экспортируемой функции (процедуры). После этого вы можете их использовать точно так же, как если бы они были описаны в одном из модулей вашего приложения. Для импорта функции или процедуры, содержащейся в DLL, необходимо использовать модификатор external в их объявлении. К примеру, для рассмотренной нами выше процедуры HelloWorld в вызывающем приложении должна быть помещена следующая строка:

procedure SayHello(AForm : TForm); external myfirstdll.dll';

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

...
HelloWorld(self);
...

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

Импорт из DLL может проводиться по имени процедуры (функции), порядковому номеру или с присвоением другого имени.

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

procedure HelloWorld(AForm : TForm);
external myfirstdll.dll' index 15;

В этом случае имя, которое вы даете процедуре при импорте не обязательно должно совпадать с тем, которое было указано для нее в самой DLL. Т.е. приведенная выше запись означает, что вы импортируете из динамической библиотеки myfirstdll.dll процедуру, которая в ней экспортировалась пятнадцатой, и при этом в рамках вашего приложения этой процедуре дается имя SayHello.

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

procedure CoolProcedure;
external myfirstdll.dll' name DoSomethingReallyCool';

Здесь импортируемой процедуре CoolProcedure дается имя DoSomethingReallyCool. Вызов процедур и функций, импортируемых из динамически загружаемых библиотек несколько более сложен, чем рассмотренный нами выше способ. В данном случае требуется объявить указатель на функцию или процедуру, которую вы собираетесь использовать. Помните процедуру HelloWorld? Давайте посмотрим, что необходимо сделать для того, чтобы вызвать ее на выполнение в случае динамической загрузки DLL. Во-первых, вам необходимо объявить тип, который описывал бы эту процедуру:

type
  THelloWorld = procedure(AForm : TForm);

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

var
  DLLInstance : THandle;
  HelloWorld : THelloWorld;
begin
  { загружаем DLL }
  DLLInstance := LoadLibrary(myfirstdll.dll');
  { получаем указатель }
  @HelloWorld := GetProcAddress(DLLInstance, HelloWorld');
  { вызываем процедуру на выполнение }
  HelloWorld(Self);
  { выгружаем DLL из оперативной памяти }
  FreeLibrary(DLLInstance);
end;

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

procedure TForm1.DynamicLoadBtnClick(Sender: TObject);
type
  THelloWorld = procedure(AForm : TForm);
var
  DLLInstance : THandle;
  HelloWorld : THelloWorld;
begin
  DLLInstance := LoadLibrary(myfirstdll.dll');
  if DLLInstance = 0 then
  begin
    MessageDlg(Невозможно загрузить DLL', mtError, [mbOK], 0);
    Exit;
  end;
  @HelloWorld := GetProcAddress(DLLInstance, HelloWorld');
  if @HelloWorld <> nil then
    HelloWorld (Self)
  else
    MessageDlg(Не найдена искомая процедура!.', mtError, [mbOK], 0);
  FreeLibrary(DLLInstance);
end;

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

Разработку DLL, содержащую форму, я продемонстрирую на примере.

Итак, во-первых, создадим новый проект динамической библиотеки. Для этого выберем пункт меню File|New, а затем дважды щелкнем на иконку DLL. После этого вы увидите примерно следующий код:

library Project2;
{здесь были комментарии}
uses
  SysUtils, Classes;
{$R *.RES}
begin
end.

Сохраните полученный проект. Назовем его DllForms.dpr.

Теперь следует создать новую форму. Это можно сделать по-разному. Например, выбрав пункт меню File|New Form. Добавьте на форму какие-нибудь компоненты. Назовем форму DllForm и сохраним получившийся модуль под именем DllFormUnit.pas.

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

function ShowForm : Integer; stdcall;
var
  Form : TDLLForm;
begin
  Form := TDLLForm.Create(Application);
  Result := Form.ShowModal;
  Form.Free;
end;

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

Экспортируем нашу функцию с использованием ключевого слова exports :

exports
  ShowForm;

Компилируем проект и получаем файл dllforms.dll. Эти простые шаги - все, что необходимо сделать для создания динамической библиотеки, содержащей форму. Обратите внимание, что функция ShowForm объявлена с использованием ключевого слова stdcall. Оно сигнализирует компилятору использовать при экспорте функции соглашение по стандартному вызову (standard call calling convention). Экспорт функции таким образом создает возможность использования разработанной DLL не только в приложениях, созданных в Delphi.

Соглашение по вызову (Calling conventions) определяет, каким образом передаются аргументы при вызове функции. Существует пять основных соглашений: stdcall, cdecl, pascal, register и safecall. Подробнее об этом можно узнать, посмотрев раздел "Calling Conventions" в файле помощи Delphi.

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

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

library DllForms;
uses
  SysUtils, Classes, Forms,
  DllFormUnit in 'DllFormUnit.pas' {DllForm};
{$R *.RES}
function ShowForm : Integer; stdcall;
var
  Form : TDLLForm;
begin
  Form := TDLLForm.Create(Application);
  Result := Form.ShowModal;
  Form.Free;
end;
begin
end.
unit TestAppUnit;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics,
  Controls, Forms, Dialogs, StdCtrls;
type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
end;
var
  Form1: TForm1;
  function ShowForm : Integer; stdcall; external 'dllforms.dll';
implementation
{$R *.DFM}
procedure TForm1.Button1Click(Sender: TObject);
begin
  ShowForm;
end;
end.

Прошу заметить, что при экспорте функции также было использовано ключевое слово stdcall.

Следует обратить особое внимание на работу с дочерними формами в DLL. Если, к примеру, в вызывающем приложении главная форма имеет значение свойства FormStyle, равным MDIForm, то при попытке вызова из DLL MDIChild-формы, на экране появится сообщение об ошибке, в котором будет говориться, что нет ни одной активной MDI-формы.

В тот момент, когда вы пытаетесь показать ваше дочернее окно, VCL проверяет корректность свойства FormStyle главной формы приложения. Однако в нашем случае все вроде бы верно. Так в чем же дело? Проблема в том, что при проведении такой проверки, рассматривается объект Application, принадлежащий не вызывающему приложению, а собственно динамической библиотеке. Ну, и естественно, поскольку в DLL нет главной формы, проверка выдает ошибку. Для того чтобы избежать такой ситуации, надо назначить объекту Application динамической библиотеки объект Application вызывающего приложения. Естественно, это заработает только в том случае, когда вызывающая программа - VCL-приложение. Кроме того, перед выгрузкой библиотеки из памяти необходимо вернуть значение объекта Application библиотеки в первоначальное состояние. Это позволит менеджеру памяти очистить оперативную память, занимаемую библиотекой. Следовательно, вам нужно сохранить указатель на "родной" для библиотеки объект Application в глобальной переменной, которая может быть использована при восстановлении его значения.

Итак, вернемся немного назад и перечислим шаги, необходимые нам для работы с помещенным в DLL MDIChild-формами.

В динамической библиотеке создаем глобальную переменную типа TApplication. Сохраняем указатель на объект Application DLL в глобальной переменной. Объекту Application динамической библиотеки ставим в соответствие указатель на Application вызывающего приложения. Создаем MDIChild-форму и работаем с ней. Возвращаем в первоначальное состояние значение объекта Application динамической библиотеки и выгружаем DLL из памяти.

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

var
  DllApp: TApplication;

Затем создаем процедуру, которая будет изменять значение объекта Application и создавать дочернюю форму. Процедура может выглядеть примерно так:

procedure ShowMDIChild(MainApp: TApplication);
var
  Child: TMDIChild;
begin
  if not Assigned(DllApp) then
  begin
    DllApp := Application;
    Application := MainApp;
  end;
  Child := TMDIChild.Create(Application.MainForm);
  Child.Show;
end;

Все, что нам теперь необходимо сделать, - это предусмотреть возвращение значения объекта Application в исходное состояние. Делаем это с помощью процедуры MyDllProc:

procedure MyDLLProc(Reason: Integer);
begin
  if Reason = DLL_PROCESS_DETACH then
    { DLL is выгружается. Восстанавливаем значение указателя Application}
    if Assigned(DllApp) then
      Application := DllApp;
end;

Вместо заключения

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

Автоматизация кодирования импорта функций из DLL.

Оформил: DeeCo

Автор: Кочетов Андрей.

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

Решение этой задачи позволит значительно сократить трудоёмкость (а значит, и время, и количество внесённых ошибок) написания похожего кода в разных проектах и/или для разных DLL.

Материал проиллюстрирован исходными текстами на Borland C++ Builder 6.0; однако все опубликованные идеи справедливы (и применимы с минимальной адаптацией) для любого языка/среды разработки. Введение и библиография В журнале "Программист" (№9 за 2002 г.) была опубликована статья Вячеслава Ермолаева "Использование template-классов при импортировании функций из DLL".

Уважаемый автор предлагает для решения этой задачи использовать механизм шаблонов (templates) языка С++. Это огромный шаг вперёд по сравнению с рутинным кодированием, но на мой взгляд, не идеальное решение, которому присущи некоторые недостатки:

Всё разнообразие возможных функций ограничивается богатством заранее описан-ных шаблонов; и хотя в статье автор отодвинул планку очень далеко - 13 параметров функций (а много ли Вы припомните функций, не укладывающихся в эти рамки ?), всё же сам факт несвободы как-то стесняет Если Вы всё же где-то откопаете функцию с большим количеством параметров, Вам придётся расширить набор шаблонов, т.е. изменить библиотеку классов, что является крайне нетехнологичным решением - Вы же не изменяете исходники VCL! При кодировании пользовательского приложения приходится использовать до-вольно-таки громоздкое описание импортируемых функций в обёртках template-классов, что не способствует мнемонической лёгкости чтения и понимания текста Механизм шаблонов языка C++ позволяет уменьшить объём исходного, но никак не объектного, полученного после компиляции кода, который раздувается тем сильнее, чем более разнообразны параметры в импортируемых из DLL функциях. Кроме того, в рамках предложенного метода остаётся нерешённой проблема автоматизации довольно трудоёмкой рутинной операции - определения полных идентифика-торов функций в DLL (строковых параметров для функции GetProcAddress): Особых проблем не наблюдается, если все функции в DLL скомпилированы как "extern "C" - в этом случае линкер просто добавляет символ подчёрки-вания перед именем функции Если же DLL собрана с функциями в стиле C++, всё совсем не так одно-значно: идентификаторы могут получиться до полуметра длиной , и выковыривание их из DLL - лишняя ручная работа; можно, конечно, зная прави-ла работы линкера, синтезировать их - но и это явно предмет для автоматизации, к тому же подозреваю, что у разных средств разработки (BCB, Visual C++) раз-ные правила, по которым работает линкер Решение Как прекрасна была бы жизнь, если б можно было все насущные нужды разработчика удовлетворить только средствами языка разработки! Увы, нет в мире совершенства, как говорил Лис, и поэтому фирмы-разработчики средств разработки генерируют всё новые, всё более мощные среды разработки (IDE), а также развивают сами языки программирования - взять те же Delphi, BCB, C# : сравните языковые средства с Pascal и C++. Вспомните также, сколько дополнительных (встроенных в IDE и отдельных) инструментальных средств входит в поставку BCB и Delphi.

Borland Software Corporation, отдав дань уважения OWL, задвинула её подальше и стала развивать Delphi и BCB на платформе VCL. Совершенно не вижу, почему бы благородным донам не поступить так же J.

Суть моего решения состоит в интеграции средства языка - компоненты - и дополнительного инструментального средства собственной разработки - DllWizard. Интерфейс-оболочку к DLL обеспечивает компонента TAskDll (исходный код - в архиве AskDll.zip). В её методах инкапсулированы: динамическая загрузка DLL (функция LoadLibrary) в методе LoadDll обработка исключительных ситуаций: при возникновении любых проблем с за-грузкой DLL формируется сообщение на языке локализации Windows и генерируется ис-ключение (Exception) для обработки в вызывающем приложении выгрузка DLL и освобождение памяти (функция FreeLibrary), выполняемые авто-матически при прекращении существования компоненты (например, при закрытии формы, на которой расположена компонента) Загрузка DLL и инициализация импортируемых функций осуществляется вызовом одного лишь метода компоненты - LoadDll. Параметр метода - указатель на функцию:
bool (*FuncGetProc)(HMODULE PtrDll)
Это должна быть функция вида:
bool Example_Load(HMODULE PtrDll)
{
  if((Func1=(DLL_Func1)GetProcAddress(PtrDll, "@Func1$qiii")) == NULL) return false;
  if((Func2=(DLL_Func2)GetProcAddress(PtrDll, "@Func2$qpct1")) == NULL) return false;
return true;
}

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

Задача решается с помощью DllWizard в 3 прохода: Автоматическое формирование описаний функций с их параметрами и возвращае-мыми значениями. Автоматическое формирование идентификаторов функций (строковых параметров для функции GetProcAddress) Генерация исходных текстов Рассмотрим пример работы с Example.DLL, экспортирующей 2 функции:
int __cdecl Func1(int i1, int i2, int i3);
char * __cdecl Func2(char * s1, char * s2

Итак, начинаем:

Запускаем DllWizard и создаём список всех функций, которые мы хотим импорти-ровать из DLL. Если DLL собственной разработки, достаточно просто указать путь к её исходнику и нажать кнопку "Найти": список сформируется автоматически (см.рис. 1) Указываем путь к DLL и нажимаем кнопку "Найти" (см.рис. 2) Нажимаем кнопку "Сгенерировать" - в каталогах, указанных на закладке "На-стройки" будут сформированы файлы

Статьи по Delphi (006)… рис. 1.

Статьи по Delphi (006)… рис. 2.

Имя DLL-ки является префиксом у всех сгенерированных файлов и у функции в модуле CPP. Исходный текст DLL и тестового приложения находится в архиве DllTest.zip.

Подкаталог DLL содержит исходный текст библиотеки: UnitExample.cpp Подкаталог EXE содержит исходный текст тестового приложения: UnitDllTest.cpp и в подкаталоге DllWizard - сгенерированные файлы: Заголовочные файлы: Example_Descript.h является служебным и содержит описание функций Example_Declare.h является служебным и содержит объявления указателей на функции Example_Extern.h следует включить в тот исходный модуль проекта прило-жения, из которого вызываются функции, импортируемые из DLL. Example_Load.cpp содержит функцию загрузки Example_Load Резюме Подводим итоги. Ниже описан порядок разработки пользовательского приложения, им-портирующего функции из динамически подключаемой библиотеки функций: С помощью DllWizard генерируем включаемые модули В проект включаем сгенерированный модуль Example_Load.cpp "Бросаем" на главную форму компоненту TAskDll и в её свойстве DllName указы-ваем имя DLL-ки (см.рис. 3)

Статьи по Delphi (006)… рис. 3.

В модуль главной формы и во все модули, в которых предполагается использовать импортируемые из DLL функции, включаем заголовочный файл Example_Extern.h Пишем пользовательский код и компилируем проект. Всё! Скриншот работы тес-тового приложения приведён на рис. 4

Статьи по Delphi (006)… рис. 4.

Преимущества технологии Разнообразие импортируемых функций не ограничено ничем Не изменяются коды библиотеки (компоненты) Не происходит разбухания объектного кода, т.к. не используются шаблоны - все функции конкретно и явно описаны в заголовочных файлах Полностью автоматизированный процесс генерации кода, включая опреде-ление идентификаторов функций (параметров для GetProcAddress) Мнемоника кода не ухудшается: имена функций остаются неизменными Минимальный объём ручного кодирования - всего 2 строки: Включение заголовочного файла Вызов метода LoadDll Технология применима не только для BCB, но и для, например, Visual C++, а также - с небольшой адаптацией - для любого языка/среды разработки; Например, в Visual C++: сгенерированный код можно использовать без изменений (только за-комментировав включение vcl.h) вместо компоненты TAskDll следует создать класс. Многие разработчики делают компоненты-обёртки для функций DLL - их применение намного удобнее. Для этих целей как нельзя лучше подходит данная технология: Создаётся компонента, производная от TAskDll Сгенерированный модуль (Example_Load.cpp) включается в проект пакета В конструкторе компоненты свойству DllName присваивается имя DLL В методе Loaded компоненты вызывается метод LoadDll. Всё! Заключение Я успешно использую эту технологию в своей работе. DllWizard - по сути тривиальная утилита - была написана, отлажена и локализована (английский и русский интерфейсы) за 3 часа. В настоящий момент она не является инструментом общего пользования; ей присущи некоторые ограничения, так как я писал "под себя", а не "для дяди". Тем не менее, мне не жалко поделиться тем, что есть. Хочу ещё раз подчеркнуть, что ограничения свойственны конкретной реализации, а не идее: Генерируется код только для С++ Нет полноценного лексического анализа исходного текста DLL: предполагается, что функции описаны в одну строку вида: __declspec(dllexport) char* Func2(char *s1, char *s2) , где __declspec(dllexport) - без пробелов тип возвращаемого значения - без пробелов после имени функции в скобках следует описание параметров Не поддерживается анализ перегруженных функций в одной DLL: например, void Func() long Func(long lp) Тем не менее, трудно отнести это к недостаткам, т.к. это крайне редкая ситуация

P.S. Немножко саморекламы: компонента TAskDll является составной частью пакета ASK Tools, см. соответствующую ссылку на http://goldenask.narod.ru/

Использование DLL в качестве Plug-in.

Создал Б-г Еву. Показывает ее Адаму. Адам спрашивает: - А как ей пользоваться? - Да ты что, это же обычный Plug-n-Play! Вставил - и играй! Вставил Адам, стал играть, понравилось. Играл так несколько дней, а потом у Евы месячные начались. Адам прибегает к Б-гу: - Госп-ди, там с Евой что-то непонятное случилось! - Адам, ну где ты видел Plug-n-Play без глюков?!
В темах для написания статей раздела "Hello World" присутствует вопрос о динамических библиотеках и модуле ShareMem. Я хотел бы несколько расширить постановку вопроса: Пусть нам надо построить систему безболезненно расширяемую функционально. Напрашивающее ся само собой решение — библиотеки динамической компоновки. И какие же грабельки разбросаны на этой тропинке? Грабли В месте моей текущей трудовой деятельности вопрос о такой системе всплыл давно. И поскольку он не собирался тонуть, создать такую систему пришлось. Так что всё изложенное ниже — из собственного опыта. Первый вопрос возникающий при создании библиотеки (DLL): А что это тут написано в закомментированной части исходного кода библиотеки. А рассказывается там следующее — если вы используете динамические массивы, длинные строки (что и является динамическим ма ссивом) как результат функции, то необходимо чтобы первым в секции uses стоял модуль ShareMem. Причём и в основном проекте! От себя добавлю, что это относится более широко к тем случаям, когда вы выделяете память в одной библиотеке, а освобождаете в друго й, что и произойдёт когда вы создадите динамический массив в одной Dll-ке, а освободите его в другой. Использовать ли ShareMem — вопрос конкретной постановки задачи. Если можно обойтись без таких выделений памяти, то вперёд, с песней! Иначе придётся вместе с программой таскать borlndmm.dll, которая и реализует безболезненный обмен указателями между библио теками. Можно задаться вопросом "А почему?". И получить ответ "Так надо!". По всей видимости, Delphi работает с Heap (кучей, откуда выделяется память) по-своему. Некоторое время назад мы на работе обсуждали этот вопрос, ползали по исходникам и к единому мнению та к и не пришли. Но есть предположение, что Delphi выделяет сразу большой кусок памяти в куче и уже потом по запросу отрезает от него требуемые кусочки, тем самым не доверяя системе выделять память. Возможно, это не так и если кто подправит меня, буду благо дарен. Так или иначе — проблема существует, и решение имеется. Вопрос второй, он освещался уже на этом сайте — а вот хочется положить форму в нашу библиотеку. Нет проблем, кладём, запускаем. Форма создаёт свою копию на панели задач. Почему? Если вы создавали окно средствами WinAPI, то обращали внимание на то, что заг оловок окна и текст соответствующей кнопки на панели задач совпадают и сделать их (тексты) различными невозможно. Т.е. когда процесс создаёт первое окно, у которого владелец — пустая ссылка (если точнее то Handle — дескриптор), то окно выводится на панель задач. А как же Delphi? В переменной Application:TApplication, которая имеется всегда, когда вы используете модуль Forms, при создании Application содаётся невидимое окно, которое становится владельцем для всех окон приложения. А поскольку у библиотеки н е происходит действий по инициализации окна переменной Application, то создаваемая форма не имеет окна владельца и как следствие — появление кнопки на панели задач. Решение уже описано, это передача ссылки на экземпляр объекта Application из вызывающей пр ограммы в вызываемый модуль и присвоение переменной Application переданного значения. Главное перед выгрузкой библиотеки не забыть вернуть старое значение Application. Совпадение свойств Name разных окон будет вызывать исключительную ситуацию. Избегнуть этого не сложно, а возникает ошибка видимо из-за того, что разные типы классов имеют одно имя в пределах одного контейнера. Достаточно важным является уничтожение окна перед выгрузкой библиотеки и завершением программы. Delphi расслабляет: за выделенными ресурсами следить не надо, окна сами создаются и уничтожаются и ещё много чего делается за программиста. Накидал компонентик ов, установил связи и всё готово... Представим: библиотека выгружена, окно из библиотеки существует, система за библиотекой уже почистила дескрипторы, да остальные ресурсики и что получается? Секунд пять Delphi при закрытии программы висит, а затем "Acces s violation ..." далее вырезано цензурой... Больше граблей замечено не было. Да и упомянутые — серьёзной проблемы не представляют, единственное, что нужно, писАть аккуратно, текст вылизывать, да и думать почаще. Построение программы с Plug In-ами Возможно 2 подхода к построению такой программы плагины получают информацию от ядра программы, но сами к ядру не обращаются. Назовём такой подход пассивным. в активном подходе плагины инициируют некоторые события и заставляют ядро их слушаться. Второй подход требует более сложного алгоритмического построения, поэтому я рассмотрю только первый. Пусть у нас есть программа, умеющая подключать динамические библиотеки (любые). Но для эффективной работы необходимо, чтобы эти библиотеки предоставляли стандартный интерфейс по передаче данных. По-русски, должен существовать стандартный набор экспортируе мых функций библиотеки, через которые программа будет с ними общаться. В процессе работы выяснилось, что для пассивной модели достаточно 6 функций: Получение внутренней информации о плагине (в программе function GetModuleInfo:TModuleInfo). При наличии в библиотеке такой функции и правильном её вызове, мы будем знать что эта DLL — наш плагин. Сама функция может возвращать что угодно, например название и тип плагина. Формирование начальных значений (в программе procedure Initialize). Плагин приводит себя в порядок после загрузки, т.е. заполняет переменные значениями по умолчанию. Передача данных в плагин (в программе procedure SetData(Kind:TDataKind;const Buffer;Size:Integer)). Позволяет передавать данные в плагин. Получение данных — в программе не реализована, но делается по типу SetData. Запуск плагина (в программе Run). Запускается плагин. Действия могут быть различными: показ окна, модальный показ окна, расчёт какого-либо параметра и т.д. И есесьно останов плагина. Здесь действия обратные пункту 2. Немного остановлюсь на передаче данных. Паскаль при всей своей жёсткой типизации предоставляет приятное средство передачи в функцию нетипизированных данных. Если программа знает о том, какие именно данные пришли, оттипизировать :) их достаточно просто. Эт от способ передачи используется в SetData. В модуле SharedTypes.Pas, используемом всеми тремя проектами описаны соответствующие константы TDataKind для типов передаваемых данных. Теперь о реализации

Пусть ядро, т.е. exe-файл, ищет плагины, запускает их и по таймеру передаёт в них два цифровых значения, которые один плагин будет изображать в текстовом виде, а второй в виде диаграмм. Реализация плагинов отличается минимально, поэтому расскажу об одном — Digital.dll. Начнём перечисление функций:

// получение информации о плагине
function GetModuleInfo:TModuleInfo;stdcall;
var
  Buffer:array [byte] of char;
begin
  with Result do begin
    Name:='Отображение цифровых данных';
    Kind:=mkDigital;
    if GetModuleFileName(hInstance,@Buffer,SizeOf(Buffer)-1)>0 then
      Path:=ExtractFilePath(StrPas(Buffer));
  end;
end;
// Функция возвращает информацию о модуле. В данном
// случае это цифровое отображение, путь и тип модуля.
// инициализация
procedure Initialize;stdcall;
begin
  // запоминание старого Application
  OldApp:=Application;
  fmDigitalMain:=nil;
end;
// Процедура запоминает переменную Application
// и делает нулевой ссылку на форму плагина.
// запуск
procedure Run;stdcall;
begin
  // создание окна плагина
  if fmDigitalMain=nil then
    fmDigitalMain:=TfmDigitalMain.Create(Application);
end;
// Процедура запуска плагина созда¸т окно.
// Окно созда¸тся видимым.
// останов
procedure Terminate;stdcall;
begin
  // освобождение окна
  fmDigitalMain.Free;
  fmDigitalMain:=nil;
  // восстановление старого TApplication
  Application:=OldApp;
end;
// Процедура уничтожает окно и возвращает старый TApplication.
// при¸м данных
procedure SetData(Kind:TDataKind;const Buffer;Size:Integer);stdcall;
begin
  case Kind of
    // передача TApplication
    dkApplication:if Size=SizeOf(TApplication) then
      Application:=TApplication(Buffer);
    // передача данных во время работы
    dkInputData:if fmDigitalMain<>nil then begin
      fmDigitalMain.SetData(Buffer,Size);
    end;
  end;
end;
// Процедура получения данных. В зависимости от полученного
// типа данных с данные в переменной Buffer соответственно
// типизируются. Здесь происходит обращение к форме плагина,
// расписывать я его не буду, там вс¸ просто, см. исходники.
// Типы, которые используются  здесь, описаны в SharedTypes.pas
По плагинам это всё. Ядро

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

type
  // описания типов функций модуля
  TGetModuleInfo=function:TModuleInfo;stdcall;
  TInitialize=procedure;stdcall;
  TRun=procedure;stdcall;
  TTerminate=procedure;stdcall;
  TSetData=procedure(Kind:TDataKind;const Buffer;Size:Integer);stdcall;
  // непосресдвенно сам класс
  TModule=class
  private
    FFileName:String;  //имя файла
    FHandle:THandle;   // дескриптор библиотеки
    FModuleInfo:TModuleInfo;  // информация о модуле
    // адреса функций плагина
    FGetModuleInfo:TGetModuleInfo; // функция получения информации о модуле
    FInitialize:TInitialize;  // процедура инициализации 
    FRun:TRun;  // процедура запуска
    FTerminate:TTerminate;  // процедура останова
    FSetData:TSetData;  // процедура передачи данных
  public
    constructor Create(AFileName:String;var IsValidModule:Boolean);
    destructor Destroy;override;
    // вызов функций плагина
    function GetModuleInfo:TModuleInfo;
    procedure Initialize;
    procedure Run;
    procedure Terminate;
    procedure SetData(Kind:TDataKind;const Buffer;Size:Integer);
    // свойства плагина
    property FileName:String read FFileName;
    property Handle:THandle read FHandle;
    property ModuleInfo:TModuleInfo read FModuleInfo;
  end;
Как видно из текста, это простая надстройка над плагином, не добавляющая функциональности, но позволяющая хранить всё в одном объекте.

Теперь осталось только собрать плагины и запустить. Сбор информации и запуск происходит по нажатию одноимённой кнопки на главной форме. Как собирать плагины — дело вкуса. В этом примере я сканирую заданный каталог, можно хранить в INI-файле, реестре, можн о придумать свой формат хранения. Сбор плагинов:

// нажатие кнопки запуска
procedure TfmMain.btStartClick(Sender: TObject);
  // добавление плагинов в список
  procedure AppendModulesList(FileName:String);
  var
    Module:TModule;
    IsValid:Boolean;
  begin
    // создание экземпляра плагина
    Module:=TModule.Create(FileName,IsValid);
    // если создан некорректно
    if not IsValid then
      // удаление
      Module.Free
    else begin
      // добавление
      SetLength(ModulesList,Length(ModulesList)+1);
      ModulesList[Length(ModulesList)-1]:=Module;
    end;
  end;
var
  sr:TSearchRec;
  i:Integer;
begin
  // построение списка модулей
  SetLength(ModulesList,0);
  // поиск файлов *.dll
  if FindFirst(edPath.Text+'*.dll',faAnyFile and not faDirectory,sr)=0 then begin
    AppendModulesList(edPath.Text+sr.Name);
    while FindNext(sr)=0 do
      AppendModulesList(edPath.Text+sr.Name);
  end;
  // запуск найденных модулей
  if Length(ModulesList)>0 then begin
    for i:=0 to Length(ModulesList)-1 do begin
      // инициализация
      ModulesList[i].Initialize;
      // передача Application
      ModulesList[i].SetData(dkApplication,Application,SizeOf(Application));
      // запуск плагина
      ModulesList[i].Run;
    end;
    // старт таймера
    Events.Enabled:=True;
  end;
end;

Мне кажется, что я достаточно подробно описал в комментариях производимые действия :) Ну и последнее — засылка данных по таймеру:

procedure TfmMain.EventsTimer(Sender: TObject);
var
  Values:array [0..1] of Word;
  i:Integer;
begin
  // формирование случайных значений
  Values[0]:=Random($ffff);
  Values[1]:=Random($ffff);
  // передача данных
  if Length(ModulesList)>0 then
    for i:=0 to Length(ModulesList)-1 do begin
      ModulesList[i].SetData(dkInputData,Values,SizeOf(Values));
    end;
end;

Желательно не забывать об освобождении модулей. Это уже в самом конце (см. исходные тексты).

Можно ли определить полный путь и имя файла запущенной DLL из самой DLL.

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

uses Windows; 
procedure ShowDllPath stdcall;
var
  TheFileName: array[0..MAX_PATH] of char;
begin
  FillChar(TheFileName, sizeof(TheFileName), #0);
  GetModuleFileName(hInstance, TheFileName, sizeof(TheFileName));
  MessageBox(0, TheFileName, 'The DLL file name is:', mb_ok);
end;

Модуль-заготовка для динамического подключения библиотек.

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

{ **** UBPFD *********** by delphibase.endimus.com ****
>> Модуль-заготовка для динамического подключения библиотек.
Модуль подключается к проекту.
Для загрузки и выгрузки библиотек используется по одной функции.
Зависимости: windows.pas, sysutils.pas
Автор:       panov, panov@hotbox.ru
Copyright:   нет
Дата:        30 марта 2004 г.
***************************************************** }
unit proc;
interface
uses windows, sysutils, classes;
const
  _MyLibraryName = '..\dll\proc.dll'; //имя библиотеки
var
  hLib: THandle;
procedure LoadLib;
procedure UnloadLib;
implementation
procedure LoadLib;
begin
  if hLib <> 0 then
    Exit;
  hLib := LoadLibrary(PChar(_MyLibraryName));
  if (hLib = 0) then
  begin
    raise Exception.Create('Не могу загрузить библиотеку ' + _MyLibraryName);
    Exit;
  end;
end;
procedure UnloadLib;
begin
  FreeLibrary(hLib);
  hLib := 0;
end;
initialization
  hLib := 0;
end.

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

unit proc;
interface
uses windows, sysutils;
const
  _MyLibraryName = '..\dll\proc.dll'; //имя библиотеки
var
  hLib: THandle;
  ExecCmd: procedure(const aCommand: string; const aShow, aWaitExit: Boolean);
procedure LoadLib;
procedure UnloadLib;
implementation
procedure LoadLib;
begin
  if hLib <> 0 then
    Exit;
  hLib := LoadLibrary(PChar(_MyLibraryName));
  if (hLib = 0) then
  begin
    raise Exception.Create('Не могу загрузить библиотеку ' + _MyLibraryName);
    Exit;
  end;
  @ExecCmd := GetProcAddress(hLib, 'ExecCmd');
end;
procedure UnloadLib;
begin
  FreeLibrary(hLib);
  hLib := 0;
end;
initialization
  hLib := 0;
end.
{
В любом модуле пишем:
  uses proc;
...
  LoadLib; //Загрузка библиотеки.
  ExecCmd('c:\command.com',True,True);
  UnloadLib; //Выгрузка библиотеки.
}

DLL - убийственная утилита.

Программа МсLаиd.ехе вызвала неустранимую ошибку в модуле кикиshка.dll при обращении к системному таймеру: Stаск Оvегf1оw. Кикиshка.dll будет закрыта. При повторном появлении ошибки обратитесь к разработчику МсLаиd.ехе.

Вот маленький метод, взятый мною из небольшого проекта, созданного мною для удаления DLL из памяти. На форме присутствует одно поле редактирования TEdit с именем EditDLLName, кнопки Ok и Close. Следующий код выполняется при нажатии на кнопке Ok:

procedure TForm1.OkBtnClick(Sender: TObject);
var
  hDLL: THandle;
  aName: array[0..10] of char;
  FoundDLL: Boolean;
begin
  if EditDLLName.Text = '' then
  begin
    MessageDlg('Сначала вы должны ввести имя выгружаемой DLL!', mtInformation,
      [mbOk], 0);
    exit;
  end;
  StrPCopy(aName, EditDLLName.Text);
  FoundDLL := false;
  repeat
    hDLL := GetModuleHandle(aName);
    if hDLL = 0 then
      break;
    FoundDLL := true;
    FreeLibrary(hDLL);
  until false;
  if FoundDLL then
    MessageDlg('Успешно!', mtInformation, [mbOk], 0)
  else
    MessageDlg('DLL не найдена!', mtInformation, [mbOk], 0);
  EditDLLName.Text := '';
end;

Получить специфическую для DLL информацию о версии.

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

{*************************** 
** DllGetVersion Function ** 
***************************}
 { 
  Implemented by many of the Microsoft® Windows® Shell dynamic-link libraries (DLLs) to 
  allow applications to obtain DLL-specific version information. 
}
 { 
  Using DllGetVersion to Determine the Version Number 
  Starting with version 4.71, the Shell and common controls DLLs, among others, 
  began exporting DllGetVersion. 
  This function can be called by an application to determine which DLL 
  version is present on the system. It returns a structure that contains version information. 
  Note  DLLs do not necessarily export DllGetVersion. Always test for it 
  before attempting to use it. 
  For systems earlier than Windows 2000, DllGetVersion returns a DLLVERSIONINFO structure 
  that contains the major and minor version numbers, the build number, 
  and a platform identifier (ID). 
  For Windows 2000 and later systems, DllGetVersion might instead return a 
  DLLVERSIONINFO2 structure. This structure contains the QFE number that identifies 
  the service pack and provides a more robust way to compare version numbers than DLLVERSIONINFO. 
  Because the first member of DLLVERSIONINFO2 is a DLLVERSIONINFO structure, 
  the new structure is backward-compatible. 
}
 // DLLVERSIONINFO structure 
type
   PDLLVerInfo=^TDLLVersionInfo;
   TDLLVersionInfo=Record
     cbSize,   // Size of the structure, in bytes. 
    dwMajorVersion, // Major version of the DLL 
    dwMinorVersion, // Minor version of the DLL 
    dwBuildNumber, // Build number of the DLL 
    dwPlatformID: DWord; // Identifies the platform for which the DLL was built 
end;
 var
   DllGetVersion: function(dvi: PDLLVerInfo): PDLLVerInfo; stdcall;
 function GetDllVersion(DllName: string; var DLLVersionInfo: TDLLVersionInfo): Boolean;
 var
   hInstDll: THandle;
   p: pDLLVerInfo;
 begin
   Result := False;
   // Get a handle to the DLL module. 
  // das Handle zum DLL Modul ermitteln. 
  hInstDll := LoadLibrary(PChar(DllName));
   if (hInstDll = 0) then Exit;
   // Return the address of the specified exported (DLL) function. 
  // Adresse der Dll-Funktion ermitteln 
  @DllGetVersion := GetProcAddress(hInstDll, 'DllGetVersion');
   // If the handle is not valid, clean up an exit. 
  // Wenn das Handle иngь1tig ist, wird die Funktion verlassen 
  if (@DllGetVersion) = nil then
   begin
     FreeLibrary(hInstDll);
     Exit;
   end;
   new(p);
   try
     ZeroMemory(p, SizeOf(p^));
     p^.cbSize := SizeOf(p^);
     // Call the DllGetVersion function 
    // Die DllGetVersion() Funktion aufrufen 
    DllGetVersion(p);
     DLLVersionInfo.dwMajorVersion := p^.dwMajorVersion;
     DLLVersionInfo.dwMinorVersion := p^.dwMinorVersion;
     @DllGetVersion := nil;
     Result := True;
   finally
     dispose(P);
   end;
  // Free the DLL module. 
 // Dll wieder freigeben. 
 FreeLibrary(hInstDll);
 end;
 // Example to get version info from comctl32.dll 
// Beispiel, um von comctl32 Versions/Informationen zu erhalten 
procedure TForm1.Button1Click(Sender: TObject);
 var
   DLLVersionInfo: TDLLVersionInfo;
 begin
  if not GetDllVersion('comctl32.dll',DLLVersionInfo) then
  begin
    DLLVersionInfo.dwMajorVersion := 4;
    DLLVersionInfo.dwMinorVersion := 0;
  end;
  with DLLVersionInfo do
    ShowMessage(Format('ComCtl Version: %d.%d / Build: %d',[dwMajorVersion, dwMinorVersion, dwBuildNumber]))
 end;

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

- Что общего между ЭВМ и половым членом? - Оба имеют два устойчивых состояния - либо стоит, либо висит.

Создайте и откомпилируйте пустой проект DLL, который содержит ссылку на файл ресурсов .res, который содержит Ваши ресурсы.

library ResTest;
uses
  SysUtils;
{$R MYRES.RES} 
begin 
end.

Для использования такой DLL, просто загрузите dll и ресурсы, которые Вы будете использовать:

{$IFDEF WIN32}
const
  BadDllLoad = 0;
{$ELSE}
const
  BadDllLoad = 32;
{$ENDIF}
procedure TForm1.Button1Click(Sender: TObject);
var
  h: THandle;
  Icon: THandle;
begin
  h := LoadLibrary('RESTEST.DLL');
  if h then
    ShowMessage('Bad Dll Load')
  else
  begin
    Icon := LoadIcon(h, 'ICON_1');
    DrawIcon(Form1.Canvas.Handle, 10, 10, Icon);
    FreeLibrary(h);
  end;
end;

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

- Если встать между двумя программистами и загадать желание, то оно обязательно сбудется! - Но желание будет глючить!
Делаешь текстовый файл с ресурсами, типа
--my.rc--
STRINGTABLE
{
00001, "My String #1"
00002, "My String #2"
}
Далее компилируешь его:
brcc32 my.rc
У тебя получится my.res. Делаешь DLL:

--my.dpr--

library my;
{$R my.res}
begin
end.
Компилируешь Дельфиским компилятором:
dcc32 my.dpr
Получаешь, наконец-то свою my.dll Теперь о том, как использовать.

В своей программе:

var
  h: THandle;
  S: array [0..255] of Char;
begin
  h := LoadLibrary('MY.DLL');
  if h <= 0 then 
    ShowMessage('Bad Dll Load')
  else
  begin
    SetLength(S, 512);
    LoadString(h, 1, @S, 255);
    FreeLibrary(h);
  end;
end;

Отладка DLL в Windows XP.

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

Until up to Win2k, you had to set the HostApplication on Start=>Parameter to debug a DLL. This will not work on Windows XP. That Delphi will know the DLL if you run the progam, you have to do the following: The program must load the DLL. After this, press Ctrl-Alt-M in Delphi, to list all modules. Sometimes there will be the DLL with path. Solution: Right click on the DLL, select "Reload symbols" and set the full path to the DLL. Now the breakpoints should be active. When the DLL will be compiled in the system path (directory in PATH) this problem dont occur. {****} Bis und mit W2k hat es gereicht, unter Start=>Parameter=>HostAnwendung eine EXE an zu geben, welche die DLL 1дdt. Dies reicht leider bei Windows XP nicht mehr Damit Delphi die DLL beim Аиsfьhгеn des Programmes erkennt, muss folgendes gemacht werden: Das Programm muss die DLL laden. Danach Ctrl-Alt-M innerhalb von Delphi dгьскеn, um die Module aufzulisten. Unter иmstдndеn wird hier die DLL ohne Pfadangabe angezeigt. Wenn dies der Fall ist, handelt es sich um das hier beschriebene Problem. Lцsиng: Auf die DLL klicken, ььег die rechte Maustaste "Symbole neu laden" anklicken und dann den kompletten Pfad zur DLL angeben. Jetzt sind die Breakpoints aktiv. Falls die DLL in einen Systempfad compiliert wird (ein Verzeichnis im PATH), besteht dieses Problem nicht.

И снова баннеры - Download Accelerator 5.3.0.0.

Оформил: DeeCo Автор: http://www.cracklab.narod.ru

Page: http://www.downloadaccelerator.com/ [Вступление:] Мне захотелось чего-нибудь поломать, я зашёл на download.com.com и нашел там это чудо. Программа очень странная, потому как вся документация, все хелпы и readme находятся в ... Интернете! Так что я не смог прочитать ни сколько стоит прога, ни как её зарегить. Наверное, американосы уже привыкли, что у каждого должна быть выделенка, а диал-ап с поминутной оплатой - это для них то же самое, что для нас чунгачкук с луком :) [Беглый осмотр:] Я запустил прогу и первое что я увидел - это большой баннер с рекламой. Ну вот, опять баннеры, а с другой стороны вспомню как эту хрень убирать :) [Начинаем:] В борьбе с баннерами есть 2 ключевых момента: 1) Отключить механизм загрузки самих баннеров из Инета, т.к. это занимает и без того небольшую скорость моего модема. 2) Найти процедуру создания окошка с баннером и сделать так, чтобы размер баннера в высоту стал равен 0, и подтянуть все остальные окошки вверх до упора под главное меню. [Приступим к пункту 1:] Для начала я дизассемблировал программу в W32Dаsm'е и защел в String Reference. Там было дохрена всяких строк, но я искал пока неизвестно что и...нашел строку:
	* Possible StringData Ref from Data Obj ->"http://ads.downloadaccelerator.com/cgi-bin/ads"
ОООчень похоже на адрес, откуда прога тянет картинки баннеров, это видно по "ads", что очень часто расшифровывается как ADultSystem :) вот и отлично, можно прямо щас затереть в программе этот адрес и больше прога никуда не сунется. Этот вариант лучше, чем искать таймер, затирать вызовы функции, потому как такие затирания могут нарушить работу проги, а нам это не надо, так что обойдёмся малой кровью. Затираем. [Приступим к пункту 2:] Это уже посложнее, потому как единого подхода к этой проблеме нет и каждый раз чего-то новенькое приходится выдумывать. Для начала я взял утилиту Spy и выяснил, что баннерное окно - это (как обычно в таких ситуациях) два окна: одно родительское (617x80) другое - сам баннер (468x60) размеры баннерного окна говорят уже о том, что он - баннер. Теперь мне нужно найти функцию создания этого окна, а в функцию параметры чаще всего передаются через стек, т.е. командой push. Значит мне нужно найти команду: push 00000050 т.к. высота окна 80 = 50h Я открываю DAP.exe в HView, перехожу в режим ассемблера и нажимаю два раза F7 это поиск по ассемблерной команде, ввожу туда push 50 и начинаю искать. Такая команда встречается 7 раз. Теперь я методом тыка по очереди в каждом из семи раз меняю push 50 на push 00, т.е. чтобы размер окна был равен 0, и проверяю. На четвертой попытке у меня получилось: .00461F2D: 6A02 push 002 .00461F2F: 6A50 push 050 .00461F31: 50 push eax .00461F32: 6A00 push 000 .00461F34: 6A00 push 000 .00461F36: 6A00 push 000 .00461F38: 8BCE mov ecx,esi .00461F3A: E8737F0400 call MFC42.6197 Очень похоже на вызов функции создания окна. Меняем по адресу 00461F2F: push 50 на push 00 и запускаем программу: Окна с баннером больше нет, а все остальные окна подтянуты под главное меню. УрА! Исследовал и наваял туториал: vallkor //PTDS E-mail: vallkor@chat.ru (vallkor@etel.dn.ua) Page: http://vallkor.chat.ru

Реализация Drag-and-Drop из внешнего приложения в ваше.

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

{ 
  The following example demonstrates the Drag&Drop machanism from an external 
  application (Wordpad, Microsoft,..) to a TMemo in your own application. 
{ 
  Das folgende Beispiel demonstriert der Drag&Drop Maechanismus 
  aus externen Anwendungen (wie zum Beispiel WordPad oder Microsoft Word) 
  in ein TMemo des eigenen Delphi-Programms. 
 }
 unit TMemoDragDropFrm;
 { **************************************************************** 
  Source File Name :  TMemoDragDropFrm.pas 
  Typ              :  Hauptformular 
  Autor            :  Andreas Kosch 
  Compiler         :  Delphi 4.02 CSS 
  Betriebssystem   :  Windows 98 
  Beschreibung     :  Text via OLE Drag&Drop ььегnеhmеn aus einer 
                      anderen Anwendung (wie zum Beispiel WordPad) 
                      ььегnеhmеn. 
  16.01.2003: Test mit Delphi 7 und Microsoft Word XP unter Windowx XP 
 **************************************************************** }
  { Comments by Thomas Stutz }
 interface
 uses
   Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
   Dialogs, ComCtrls, ActiveX, ShlObj, ComObj, StdCtrls;
 type
   // TForm1's declaration indicates that it's a TForm and that 
  // it supports the IDropTarget interface: 
 { 
  IDropTarget 
  Any application wanting to accept drop operations must 
  implement the IDropTarget interface. 
  Methods of the IDropTarget interface: 
  DragEnter 
    Dragged item has just been moved into the application's window, 
    return the relevant icon. 
  DragOver 
    Dragged item is being moved over the application's window, 
    return the relevant icon. 
  DragLeave 
    Dragged item has just moved out of the application's window. 
  Drop 
    The dragged item has been dropped on this application. 
  }
   TForm1 = class(TForm, IDropTarget)
     Memo1: TMemo;
     procedure FormCreate(Sender: TObject);
     procedure FormDestroy(Sender: TObject);
   private
     { Private declarations }
     // IDropTarget 
    function DragEnter(const dataObj: IDataObject;
                        grfKeyState: Longint;
                        pt: TPoint;
                        var dwEffect: Longint): HResult; stdcall;
     function DragOver(grfKeyState: Longint;
                       pt: TPoint;
                       var dwEffect: Longint): HResult; stdcall;
     function DragLeave: HResult; stdcall;
     function Drop(const dataObj: IDataObject;
                   grfKeyState: Longint; pt: TPoint;
                   var dwEffect: Longint): HResult; stdcall;
    // IUnknown 
   // Ignore referance counting 
   function _AddRef: Integer; stdcall;
    function _Release: Integer; stdcall;
   public
     { Public declarations }
   end;
 var
   Form1: TForm1;
 implementation
 {$R *.dfm}
 uses
   ShellAPI;
 // In the OnCreate event handler, two important methods are called. 
// First, OleInitalize is called. This initializes the OLE libraries and should always be 
// called before your application uses any OLE functions. 
// RegisterDragDrop registers the window as a valid drop target. 
// If this isn't called, the window will never receive any drop events. 
procedure TForm1.FormCreate(Sender: TObject);
 begin
   OleInitialize(nil);
   {Allow window to accept drop events}
   OleCheck(RegisterDragDrop(Handle, Self));
   { Execute Wordpad for testing }
   ShellExecute(Handle, 'open', 'wordpad', 'c:\Test.doc', nil, SW_SHOW);
 end;
 // OnDestroy does the exact opposite. It calls RevokeDropTarget to indicate that 
// drop events are no longer accepted. 
// It then calls OleUninitialize, since the application is finished using all OLE functions. 
procedure TForm1.FormDestroy(Sender: TObject);
 begin
   {Finished accepting drops}
   RevokeDragDrop(Handle);
   OleUninitialize;
 end;
 {-----------------------------------------------------------------}
 { IDropTarget-Implementierung                                     }
 {-----------------------------------------------------------------}
 function TForm1.DragEnter(const dataObj: IDataObject;
   grfKeyState: Longint;
   pt: TPoint;
   var dwEffect: Longint): HResult;
 begin
   dwEffect := DROPEFFECT_COPY;
   Result  := S_OK;
 end;
 function TForm1.DragOver(grfKeyState: Longint;
   pt: TPoint;
   var dwEffect: Longint): HResult;
 begin
   dwEffect := DROPEFFECT_COPY;
   Result := S_OK;
 end;
 function TForm1.DragLeave: HResult;
 begin
   Result := S_OK;
 end;
 function TForm1._AddRef: Integer;
 begin
    Result := 1;
 end;
 function TForm1._Release: Integer;
 begin
    Result := 1;
 end;
 function TForm1.Drop(const dataObj: IDataObject;
   grfKeyState: Longint;
   pt: TPoint;
   var dwEffect: Longint): HResult;
 var
   aFmtEtc: TFORMATETC;
   aStgMed: TSTGMEDIUM;
   pData: PChar;
 begin
   {Make certain the data rendering is available}
   if (dataObj = nil) then
     raise Exception.Create('IDataObject-Pointer is not valid!');
   with aFmtEtc do
   begin
     cfFormat := CF_TEXT;
     ptd := nil;
     dwAspect := DVASPECT_CONTENT;
     lindex := -1;
     tymed := TYMED_HGLOBAL;
   end;
   {Get the data}
   OleCheck(dataObj.GetData(aFmtEtc, aStgMed));
   try
     {Lock the global memory handle to get a pointer to the data}
     pData := GlobalLock(aStgMed.hGlobal);
     { Replace Text }
     Memo1.Text := pData;
   finally
     {Finished with the pointer}
     GlobalUnlock(aStgMed.hGlobal);
     {Free the memory}
     ReleaseStgMedium(aStgMed);
   end;
   Result := S_OK;
 end;
 end.

Описание протокола DNS (Domain Name System).

Ругательство программистов: Web твою мать!!!

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

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

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

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

Мне неизвестна ни одна машина с доменным именем из одного сегмента; очень редко используются доменные имена из двух сегментов; имена из трех и четырех сегментов составляют подавляющую долю всех имен Internet; имена из пяти сегментов встречаются довольно редко, а из шести и более мне неизвестны.

Допустим, клиент запросил адрес "www.организация.город.страна". Поиск информации по доменному имени происходит следующим образом:

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

Это приближенная модель, которая тем не менее позволяет представить работу системы DNS.

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

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

Хочу обратить особое внимание на сходство, различие и взаимодействие систем DNS и IP-маршрутизации. Как и IP-маршрутизация, DNS работает по принципу делегирования полномочий, но выделение доменных имен совершенно не зависит от выделения IP-адресов. Для примера рассмотрим домен freebsd.org. Это - домен организации, занимающейся распространением операционной системы FreeBSD Unix. FTP-сервер, содержащий дистрибутив операционной системы и множества утилит для нее, имеет копии в нескольких десятках стран. Имена серверов выглядят так:

ftp.freebsd.org первичный сервер в США ftp.страна.freebsd.org основной сервер в стране ftрчисло.страна.freebsd.org дополнительный сервер в стране

Так например на 11 февраля 1998 года.

ftp.ru.freebsd.org соответствует ftp.ru ftp2.ru.freebsd.org соответствует ftp.gamma.ru ftp3.ru.freebsd.org соответствует ftp.chg.ru

Таким образом, машины, находящиеся в России оказались произвольно (по воле DNS-мастера из университета Bercley) включенными в домен freebsd.org; однако, они также состоят в своих зонах. Система DNS позволяет любому DNS-мастеру включить любой сервер в свою зону, хотя это включение никого ни к чему не обязывает.

Однако, некоторым сервисам этого недостаточно - так E-mail требует, чтобы машина, принимающая письмо, признала своим адрес, указанный в качестве пункта назначения. Протокол HTTP 1.1 (в 1.0 этого не было) требует, чтобы в HTTP-запросе указывался не путь к файлу, отсчитанный от корня сервера (хотя такие запросы тоже признаются), но и имя сервера; при этом сам сервер знает, какие имена - его, а остальные обрезает и обслуживает в соответствии с HTTP 1.0.

Делегирование зоны ...in-addr.arpa дается только от провайдера вместе с IP-адресами. Собственно, это связано с предназначением ReverceDNS - сообщать доменное имя по IP-адресу. Наверняка мастер зоны freebsd.org держит Reverce-зону для IP-номеров, выделенных университету Bercley; но все эти серверы (кроме сервера, расположенного в университете) не входят в эту Reverce-зону, а значит, ему неподконтрольны.

Одна из проблем состит в том, что Reverce-зону можно выделить только на сеть класса A, B или C (на 16777216, 65536 или 256 адресов) и никак иначе. Можно получить правА на несколько зон одного или разных классов, но что делать тем, кому выделили меньше 256 адресов? А ведь в условиях исчерпания адресного пространства не редкость выделения пула уже на 16 адресов!

DNS-услуги Internet-провайдера

Как правило, провайдер предоставляет клиенту целый комплекс услуг. В число оказываемых DNS-услуг входят:

делегирование зоны ...in-addr.arpa клиентам, имеющим пул адресов, кратный 256. регистрация доменного имени клиента у держателя той зоны, в которой клиент хочет зарегистрироваться; поддержание вторичного сервера прямой и обратной DNS-зон клиента; поддержание первичного сервера этих зон, если клиент по какой-либо причине не поддерживает их сам (особенно это относится к случаю виртуальных зон и к случаю выделения малого пула адресов);

Если провайдер будет отказываться - сошлитесь на меня. :-).

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

Имена зон условно можно разделить на "организационные" и "географические". В высшей зоне зарегестрированы следующие "организационные" зоны:

com commercial (коммерческие) edu educational (образовательные) gov goverment (правительственные) mil military (военные) net network (организации, обеспечивающие работу сети) org organization (некоммерческие организации)

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

Каждая страна (государство) имеет свой географический домен из двух букв:

ae United Arab Emirates (Объединенные Арабские Эмираты) au Australia (Австралия) be Belgium (Бельгия) br Brazil (Бразилия) by Belarus (Белоруссия) ca Canada (Канада) ch Switzerland (Швейцария) cz Czech Republic (Чехия) de Germany (Германия) dk Denmark do Dominican Republic (Доминиканская республика) ee Estonia (Эстония) eo ??? es Spain (Испания) fi Finland (Финляндия) fr France (Франция) hu Hungary (Венгрия) il Israel (Израиль) in India (Индия) iz ??? jp Japan (Япония) kg Kyrgyzstan (Кыргызстан) kr South Korea (Южная Корея) kz Kazakhstan (Казахстан) lt Lithuania (Литва) lv Latvia (Латвия) mx Mexico (Мексика) nl Netherlands (Нидерланды) no Norway (Норвегия) nz New Zealand (Новая Зеландия) pl Poland (Польша) ro Romania (Румыния) ru Russia (Россия) si Slovenia (Словения) sk Slovak Republic (Словакия) su Soviet Union (Советский Союз - поддерживается, но не распределяется) ua Ukraine (Украина) uk United Kingdom (Соединенное Королевство ВеликоБритания / Англия) yu Yugoslavia (Югославия) za South Africa (Южная Африка)

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

В зонах государств опять же имеются "организационные" и "географические" зоны. "Организационные" в большинстве своем повторяют структуру "организационных" зон верхнего уровня, разве что вместо "com" используется "co". "Географические" выделяются городам, областям и т.п. территориальным образованиям. Непосредственно в тех и других размещаются домены организаций или домены персональных пользователей.

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

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

Имена "функциональные" вытекают из функций, выполняемых машиной:

www HTTP (WWW) сервер ftp FTP сервер ns, nss, dns DNS (Name) сервер mail Mail сервер relay Mail Exchanger *proxy соответствующий Proxy сервер

Я считаю нежелательным присваивать какой-либо машине функциональное имя - в любой момент может потребоваться перенести соответствующую функцию на другую машину. Для этого лучше всего использовать псевдонимы, которые перенаправляют запросы к данному имени на записи, относящиеся к другому имени. Но вот ссылаться на псевдонимы при обьявлении Mail Ехсhаngеr'ов и вообще использовать их в правой части записей считается нежелательным, а зачастую является недопустимым.

Как преобразовать DOC в RTF при помощи OLE.

Создайте новое приложение и пометите на него: кнопку с именем Button3, объект RitchText с именем WordEditor и компонент OpenDialog. Теперь посмотрим, как пролистывать все файлы *.doc и загружать их в объект RitchText. ЗАМЕЧАНИЕ: Format:=6 указывает Word-у на необходимость сохранять файл как RTF. Расширения недостаточно. Другие форматы файлов:
Format      File 
Argument    Format 
--------    ------ 
   0        Нормальный (формат Word)
   1        Шаблон документа
   2        Только текст (extended characters saved in ANSI character set) 
   3        Текст+Переносы (plain text with line breaks; extended 
            characters saved in ANSI character set) 
   4        Только текст (PC-8) (extended characters saved in IBM PC 
            character set) 
   5        Текст+Переносы (PC-8) (text with line breaks; extended 
            characters saved in IBM PC character set) 
   6        Формат Rich-text (RTF) 
procedure TImport_Form.ToolButton3Click(Sender: TObject);
var
  WordApp: Variant;
begin
  if OpenDialog1.Execute then
  begin
    Edit1.Text := ExtractFileName(OpenDialog1.FileName);
    StatusBar1.SimpleText := OpenDialog1.FileName;
    WordApp := CreateOleObject('Word.Basic');
    if not VarIsEmpty(WordApp) then
    begin
      WordApp.FileOpen(OpenDialog1.FileName);
      WordApp.FileSaveAs(Name := 'c:\temp_bb.rtf', Format := 6);
      WordApp.AppClose;
      WordApp := Unassigned;
      WordEditor.Lines.LoadFromFile('c:\temp_bb.rtf');
    end
    else
      ShowMessage('Could not start MS Word');
  end;
end;

Пример шифрования данных.

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

procedure DoEncode(var Source: string; const Key: string);
asm
 Push ESI
 Push EDI
 Push EBX
 Or EAX,EAX
 Jz @Done
 Push EAX
 Push EDX
 Call UniqueString
 Pop EDX
 Pop EAX
 Mov EDI,[EAX]
 Or EDI,EDI
 Jz @Done
 Mov ECX,[EDI-4]
 Jecxz @Done
 Mov ESI,EDX
 Or ESI,ESI
 Jz @Done
 Mov EDX,[ESI-4]
 Dec EDX
 Js @Done
 Mov EBX,EDX
 Mov AH,DL
 Cld
 @L1:
 Test AH,8
 Jnz @L3
 Xor AH,1
 @L3:
 Not AH
 Ror AH,1
 Mov AL,[ESI+EBX]
 Xor AL,AH
 Xor AL,[EDI]
 Stosb
 Dec EBX
 Jns @L2
 Mov EBX,EDX
 @L2:
 Dec ECX
 Jnz @L1
 @Done:
 Pop EBX
 Pop EDI
 Pop ESI
end;
Комментарий от Chingachguk'a Мне кажется, у этого алгоритма есть два недостатка: 1) Код, сильно зависимый от компилятора. Далеко не всегда регистр EAX будет указывать на ячейку с адресом Source, а регистр EDX - на пароль(Key). Но это мелочь.

2) Единственный байт гаммы(или ксорирующей последовательности), который меняется при шифровании - это длина пароля. Остальные символы пароля НИКАК НЕ ПЕРЕМЕШИВАЮТСЯ в ходе шифрования. Алгоритм шифрования примерно такой:

Len := Lengh(Key);
Index := Lengh(Key) - 1;
i := 1;
repeat
  Len := func1(Len);
  Source[i] := (Key[Index] xor Len) xor Source[i];
  dec(Index);
  if Index := 0 then
    Index := Lengh(Key) - 1;
until
  i < Lenght(Source);
Нетрудно видеть, что основной для тупого подбора является длина пароля. Пусть она равна 10. Очевидно, что 1-ый,11,21.. символы будут зашифрованы ОДИНАКОВЫМ значением Key[Index], но разными значениями Len. Казалось бы, Len для 1,11,21... будет разным, но это ерунда - ведь Len вычисляется однозначно на ЛЮБОМ шаге через реккурентный закон func1 !

И это - фатальный недостаток.

Как избежать использования неактуальных указателей.

Автор: David S. Becker
Программер просит у друга денег в долг: - Одолжи 250$ до получки, ну или для ровного счета 256?
Я создал простой модуль и разработал несколько простых методов, помогающих избежать использования неактуальных (в оригинале было "stale" - черствый, несвежий) указателей. Я настоятельно рекомендую добавить во все модули, содержащие указатели или объектные переменные секцию инициализации ('initialization') и установить все указатели (объектные переменные это те же реальные указатели) в nil. Что это даст: прежде чем хотя бы один указатель будет использован, он обязательно будет проверен, освобожден и установлен в nil. Затем, после освобождения указателей, просто установите их в nil. Мой модуль содержит функцию Nilify() для установки указателей в nil, а также специальные версии методов Free, Dispose, и FreeMem (названные NilXXX) для проверки значения nil перед освобождением памяти, и установления указателя в nil сразу после того, как он был освобожден. Я также включил специальную версию Assigned(), названную IsNil(), которая вместо переменного (var) параметра получает константу, которую вы можете затем использовать в своих свойствах, и т.п.

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

unit Pointers;
{
Автор: David S. Becker (dsb@plaza.ds.adp.com)
Дата: 1/27/97
Авторские права: Нет
Дистрибутивные права: Свободные, неограниченное использование, в случае любых изменений кода
с вашей стороны или наличия каких-либо замечаний или предложений пришлите их пожалуйста мне.
Данный модуль создавался для помощи в управлении указателями и объектами. Так как
компилятор не инициализирует указатели и объекты в nil и не сбрасывает
их в nil при освобождении, существует вероятность применения неактуального
указателя. По этой причине я рекомендую добавление секции 'initialization'
во все модули и вызове Nilify() для всех указателей/объектов в данном модуле.
Это позволит быть уверенным, что все указатели/объекты стартуют как nil.
Кроме того, вместо стандартных аналогов, вы можете использовать NilFree
(для объектов), NilDispose (для указателей, создаваемых с помощью New),
и NilFreeMem (для указателей, создаваемых с помощью GetMem). Эти процедуры
безопасны при вызове nil-вых указателей/объектов, так как перед выполнением
любых действий они проверяют их на nil. После освобождения распределенной
указателем/объектом памяти они сбрасываются в nil. Строгое соблюдение функций
модуля значительно снижает риск использования неактуального указателя.
(Конечно, вы еще можете получить неактуальные указатели из VCL, т.к.
они, естественно, не используют данные функции.)
}
{==============================================================================}
interface
{------------------------------------------------------------------------------}
{ Проверка указателя на nil }
{ ПРИМЕЧАНИЕ: Данная функция отличается от Assigned() тем, что Assigned() }
{ требует переменную, а IsNil() нет.                                      }
function IsNil(const p: Pointer): Boolean;
{ Устанавливает указатель в nil }
procedure Nilify(var p);
{ Освобождает не-nil объект и устанавливает его в nil }
procedure NilFree(o: TObject);
{ Освобождает не-nil указатель, созданный с помощью New и устанавливает его в nil }
procedure NilDispose(var p: Pointer);
{ Освобождает не-nil указатель и устанавливает его в nil }
procedure NilFreeMem(var p: Pointer; size: Word);
{==============================================================================}
implementation
{------------------------------------------------------------------------------}
function IsNil(const p: Pointer): Boolean;
begin
  Result := (p = nil);
end;
{------------------------------------------------------------------------------}
procedure Nilify(var p);
begin
  Pointer(p) := nil;
end;
{------------------------------------------------------------------------------}
procedure NilFree(o: TObject);
begin
  if not IsNil(o) then
  begin
    o.Free;
    Nilify(o);
  end;
end;
{------------------------------------------------------------------------------}
procedure NilDispose(var p: Pointer);
begin
  if not IsNil(p) then
  begin
    Dispose(p);
    Nilify(p);
  end;
end;
{------------------------------------------------------------------------------}
procedure NilFreeMem(var p: Pointer; size: Word);
begin
  if not IsNil(p) then
  begin
    FreeMem(p, size);
    Nilify(p);
  end;
end;
end.

Как получить дескриптор окна Windows, содержащего DOS программу.

Hiroshima-45... Chernobyl-86... Windows-95...
В следуещем примере используется функция Windows API FindWindow(). Обратите внимание, что WndClass консольного окна отличаются для Windows 95 и Window NT и заголовок окна может содержать полный путь под Windows NT.

Пример:

 procedure TForm1.Button1Click(Sender: TObject); 
 var 
   info : TOSVersionInfo; 
   ClassName : string; 
   Title : string; 
 begin 
  {Проверяем -  Win95 или NT.} 
   info.dwOSVersionInfoSize := sizeof(info); 
   GetVersionEx(info); 
   if (info.dwPlatformId = VER_PLATFORM_WIN32_NT) then begin 
     ClassName := 'ConsoleWindowClass'; 
     Title := 'Command Prompt'; 
   end else begin 
     ClassName := 'tty'; 
     Title := 'MS-DOS Prompt'; 
   end; 
   ShowMessage(IntToStr(FindWindow(PChar(ClassName), PChar(Title)))); 
 end;

Выключение питания ATX корпуса из-под DOS.

Автор: Колесников Сергей Александрович
Вам пора завязывать с компьютером, если ложась спать и закрыв глаза, вы ждете надписи "Теперь можно выключить питание".
        mov ax,5301h
        sub bx,bx
        int 15h
        jb stop
        mov ax,530eh
        sub bx,bx
        int 15h
        jb stop
        mov ax,5307h
        mov bx,0001h
        mov cx,0003h
        int 15h
  stop: int 20h

Или.

mov ax,5301h
    sub bx,bx
    int 15h
    jc @@finish
    mov ax,530Eh
    sub bx,bx
    mov cx,102h
    int 15h
    jc @@finish
    mov ax,5307h
    mov bx,1
    mov cx,3
    int 15h
@@finish:
    int 20h

DOS команды.

Статьи по Delphi (006)…
...Ну! И кто открывал бутылку об F8 ?!
rundll32 shell32.dll,Control_RunDLL Выводит панель управления rundll32 shell32.dll,OpenAs_ RunDLL Выводит окошко «Открыть с помощью...» rundll32 shell32.dll,ShellAboutA Info-Box Показать окно «About Windows» rundll32 shell32.dll,Control_RunDLL desk.cpl Открыть свойства экрана rundll32 user,cascadechildwindows Сортировка окон каскадом rundll32 user,tilechildwindows Сортировка окон вниз rundll32 user,repaintscreen Обновить рабочий стол rundll32 shell,shellexecute Explorer Запустить проводник Windows rundll32 keyboard,disable Вырубить клавиатуру rundll32 mouse,disable Вырубить мышь rundll32 user,swapmousebutton Поменять местами кнопки мыши rundll32 user,setcursorpos Сместить курсор крысы в левый верхний угол rundll32 user,wnetconnectdialog Вызвать окно «Подключение сетевого диска» rundll32 user,wnetdisconnectdialog Вызвать окно «Отключение сетевого диска» rundll32 user,disableoemlayer Спровоцировать сбой rundll32 diskcopy,DiskCopyRunDll Показать окно «Copy Disk» rundll32 maui.dll,RnaWizard Вывод окна «Установка связи», с ключом «/1» - без окна rundll32 shell32,SHFormatDrive Вызвать окно «Форматирование диск 3,5(А)» rundll32 shell32,SHExitWindowsEx-1 Перегрузить explorer rundll32 shell32,SHExitWindowsEx 1 Выключение компьютера rundll32 shell32,SHExitWindowsEx 0 Завершить работу текущего пользователя rundll32 shell32,SHExitWindowsEx 2 Windows-98-PC boot rundll32 krnl386.exe,exitkernel Выход из Windows без любых вопросов rundll maui.dll,RnaDial”MyConnect” Вызвать окно «Установка связи» с соединением «MyConnect» rundll32 msprint2.dll,RUNDLL_PrintTestPage Выбрать в появившемся окне принтер и послать на него тест rundll32 user,setcaretblinktime Установить новую частоту мигания курсора rundll32 user,setdoubleclicktime Установить новую скорость двойного нажатия rundll32 sysdm.cpl,InstallDevice_Rundll Установить non-Plug&Play оборудование

При использовании DOS DBF файлов - перекодировка между форматами.

Чайник y хакера спрашивает, что такое UNIX, Windows'95, OS/2 и DOS? - Hy представь себе аэропорт. На взлетной полосе стоит навороченный красивый самолет. Пассажиры в него заходят - там ковры везде, все класно. Запускают двигатели - и самолет медленно объезжает вокруг аэропорта, после чего пассажиры выходят. Вот это полуось. На другой полосе стоит еще один самолет - тоже весь из себя, внутри ковры. Садяться в него пассажиры, самолет разгоняется, взлетает, и тут же разваливается на куски - это Windows. А в стороне на поле стоит старый, помятый, грязный кукурузник - пассажиры подходят, кладут вещи, потом толкают самолет, разгоняют его и сами запрыгивают - потом с трудом летят - это DOS. - А что такое UNIX? - Ну я же тебе говорил: - Представь себе аэропорт...

При использовании DOS DBF файлов можно сделать небольшую программку (или процедурку), которая произведет перекодировку между форматами. что-то типа:

function update_dos(s:string):string;
var c:STRING;
    I:INTEGeR;
    l:byte;
    dd:char;
begin
 i:=1;
 c:='';
 while i< length(s)+1 do
 begin
   l:=ord(s[i]);
   inc(i);
   if (l>=128) and (l<=192)then l:=l+64 else
   if (l>=224) and (l<240) then l:=l+16 else
   if l=241 then l:=184 else
   if l=240 then l:=168;
   dd:=chr(l);
   c:=c+dd;
 end;
update_dos:=c;
end;
function update_win(s:string):string;
var c:STRING;
    I:INTEGeR;
    l:byte;
    dd:char;
begin
 i:=1;
 c:='';
 while i< length(s)+1 do
 begin
   l:=ord(s[i]);
   inc(i);
   if (l>=192) and (l<240)then l:=l-64 else
   if (l>=240) and (l<256) then l:=l-16 else
   if l=184 then l:=241 else    
   if l=168 then l:=240;
   dd:=chr(l);
   c:=c+dd;
 end;
update_win:=c;
end;

Это и туда и обратно, у меня работает на старых DBF. Осталось только вызвать в нужный момент.

Получение переменных окружения DOS.

Автор: Dr. Bob
- Служба технической подержки, здравствуйте! - У меня не запускается компьютер. Ваш высокоскоростной интернет сломал его. - Что вы имеете ввиду? - Я говорю вам, он не запускается. - Он работал, когда наш специалист был у Вас. - Да, он перестал работать после того, как я его накрыла. - Накрыли? - Я накрыла его пластиковой пленкой, чтобы туда не пропадали кошачьи волосы. У меня много кошек. Не плохая идея, на правда ли?
Функция GetEnvironmentStrings возвращает адрес памяти со средой текущего процесса. Все переменные возвращаются в виде строк, оканчивающихся нулем. Набор строк терминируется двумя нулями.

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

Получение переменных окружения DOS 2.

unit TDosEnv;
interface
uses
  SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
  Forms, Dialogs;
type
  TDosEnvironment = class(TComponent)
  public
    { Public объявления класса }
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
  private
    { Объявления Private-полей }
    FDosEnvList: TStringList;
    procedure DoNothing(const Value: TStringList);
  protected
    { Объявления Protected-методов }
    dummy: Word;
    function GetDosEnvCount: Word;
  public
    { Public interface объявления }
    function GetDosEnvStr(const Name: string): string;
    { Данная функция является измененной версией функции GetEnvVar,
    присутствующей в поставляемом с Delphi модуле WinDos. Она
    использует паскалевские строки вместо строк с терминирующим нулем.
    }
  published
    { Published design объявления }
    property DosEnvCount: Word read GetDosEnvCount write dummy;
    property DosEnvList: TStringList read FDosEnvList write DoNothing;
  end;
procedure Register;
implementation
constructor TDosEnvironment.Create(AOwner: TComponent);
var
  P: PChar;
  i: Integer;
begin
  inherited Create(AOwner);
  FDosEnvList := TStringList.Create;
  P := GetDosEnvironment; { Win API }
  i := 0;
  while P^ <> #0 do
  begin
    Inc(i);
    FDosEnvList.Add(StrPas(P));
    Inc(P, StrLen(P) + 1) { Быстрый переход к следующей переменной }
  end
end {Create};
destructor TDosEnvironment.Destroy;
begin
  FDosEnvList.Free;
  FDosEnvList := nil;
  inherited Destroy
end {Destroy};
procedure TDosEnvironment.DoNothing(const Value: TStringList);
begin
  MessageDlg('TDosEnvironment.DosEnvList только для чтения!', mtInformation,
    [mbOk], 0)
end {DoNothing};
function TDosEnvironment.GetDosEnvCount: Word;
{ Возвращает количество переменных окружения.
}
begin
  if Assigned(FDosEnvList) then {!!}
    Result := FDosEnvList.Count
  else
    Result := 0;
end {GetDosEnvCount};
function TDosEnvironment.GetDosEnvStr(const Name: string): string;
{ Данная функция является измененной версией функции GetEnvVar,
присутствующей в поставляемом с Delphi модуле WinDos. Она
использует паскалевские строки вместо строк с терминирующим нулем.
}
var
  i: Integer;
  Tmp: string;
  Len: Byte absolute Name;
begin
  i := 0;
  Result := '';
  if Assigned(FDosEnvList) then {!!}
    while i < FDosEnvList.Count do
    begin
      Tmp := FDosEnvList[i];
      Inc(i);
      if Pos(Name, Tmp) = 1 then
      begin
        Delete(Tmp, 1, Len);
        if Tmp[1] = '=' then
        begin
          Delete(Tmp, 1, 1);
          Result := Tmp;
          i := FDosEnvList.Count { конец while-цикла }
        end
      end
    end
end {GetDosEnvStr};
procedure Register;
begin
  RegisterComponents('Dr.Bob', [TDosEnvironment]);
end {Register};
end.

Считывать строки из текстового DOS файла.

Автор: Dimka Maslov
Налоговая полиция конфисковала большую партию пиратских дисков с Windows-98. То, что программа ненастоящая, стало ясно после того, как она поднялась за считанные секунды и к тому же нормально работала на 486-м процессоре.
procedure ReadDosTextFile(FileName: string; List: TStrings);
var
 S: string;
 i: integer;
begin
 List.BeginUpdate;
 try
  List.LoadFromFile(FileName);
  for i:=0 to List.Count - 1 do begin
   S:=List[i];
   OemToChar(PChar(S), PChar(S));
   List[i]:=S;
  end;
 finally
  List.EndUpdate;
 end;
end;
procedure WriteDosTextFile(FileName: string; List: TStrings);
var
 S: string;
 I: integer;
 L: TStringList;
begin
 L:=TStringList.Create;
 try
  L.Assign(List);
  for i:=0 to L.Count - 1 do begin
   S:=L[i];
   CharToOem(PChar(S), PChar(S));
   L[i]:=S;
  end;
  L.SaveToFile(FileName);
 finally
  L.Free;
 end;
end;

Получить выходные данные DOS приложения.

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

{----------------------------CreateDOSProcessRedirected--------------------------- 
 Description    : executes a (DOS!) app defined in the CommandLine parameter redirected 
                  to take input from InputFile and give output to OutputFile 
 Result         : True on success 
 Parameters     : 
                  CommandLine : the command line for the app, including its full path 
                  InputFile   : the ascii file where from the app takes input 
                  OutputFile  : the ascii file to which the app's output is redirected 
                  ErrMsg      : additional error message string. Can be empty 
 Error checking : YES 
 Target         : Delphi 2, 3, 4 
 Author         : Theodoros Bebekis, email bebekis@otenet.gr 
 Notes          : 
 Example call   : CreateDOSProcessRedirected('C:\MyDOSApp.exe', 
                                             'C:\InputPut.txt', 
                                             'C:\OutPut.txt', 
                                             'Please, record this message') 
-----------------------------------------------------------------------------------}
 function CreateDOSProcessRedirected(const CommandLine, InputFile, OutputFile,
   ErrMsg: string): Boolean;
 const
   ROUTINE_ID = '[function: CreateDOSProcessRedirected ]';
 var
   OldCursor: TCursor;
   pCommandLine: array[0..MAX_PATH] of Char;
   pInputFile, pOutPutFile: array[0..MAX_PATH] of Char;
   StartupInfo: TStartupInfo;
   ProcessInfo: TProcessInformation;
   SecAtrrs: TSecurityAttributes;
   hAppProcess, hAppThread, hInputFile, hOutputFile: THandle;
 begin
   Result := False;
   { check for InputFile existence }
   if not FileExists(InputFile) then
     raise Exception.CreateFmt(ROUTINE_ID + #10 + #10 +
       'Input file * %s *' + #10 +
       'does not exist' + #10 + #10 +
       ErrMsg, [InputFile]);
   { save the cursor }
   OldCursor     := Screen.Cursor;
   Screen.Cursor := crHourglass;
   { copy the parameter Pascal strings to null terminated strings }
   StrPCopy(pCommandLine, CommandLine);
   StrPCopy(pInputFile, InputFile);
   StrPCopy(pOutPutFile, OutputFile);
   try
     { prepare SecAtrrs structure for the CreateFile calls 
      This SecAttrs structure is needed in this case because 
      we want the returned handle can be inherited by child process 
      This is true when running under WinNT. 
      As for Win95 the documentation is quite ambiguous }
     FillChar(SecAtrrs, SizeOf(SecAtrrs), #0);
     SecAtrrs.nLength        := SizeOf(SecAtrrs);
     SecAtrrs.lpSecurityDescriptor := nil;
     SecAtrrs.bInheritHandle := True;
     { create the appropriate handle for the input file }
     hInputFile := CreateFile(pInputFile,
       { pointer to name of the file }
       GENERIC_READ or GENERIC_WRITE,
       { access (read-write) mode }
       FILE_SHARE_READ or FILE_SHARE_WRITE,
       { share mode } @SecAtrrs,                             { pointer to security attributes }
       OPEN_ALWAYS,                           { how to create }
       FILE_ATTRIBUTE_TEMPORARY,              { file attributes }
       0);                                   { handle to file with attributes to copy }
     { is hInputFile a valid handle? }
     if hInputFile = INVALID_HANDLE_VALUE then
       raise Exception.CreateFmt(ROUTINE_ID + #10 + #10 +
         'WinApi function CreateFile returned an invalid handle value' +
         #10 +
         'for the input file * %s *' + #10 + #10 +
         ErrMsg, [InputFile]);
     { create the appropriate handle for the output file }
     hOutputFile := CreateFile(pOutPutFile,
       { pointer to name of the file }
       GENERIC_READ or GENERIC_WRITE,
       { access (read-write) mode }
       FILE_SHARE_READ or FILE_SHARE_WRITE,
       { share mode } @SecAtrrs,                             { pointer to security attributes }
       CREATE_ALWAYS,                         { how to create }
       FILE_ATTRIBUTE_TEMPORARY,              { file attributes }
       0);                                   { handle to file with attributes to copy }
     { is hOutputFile a valid handle? }
     if hOutputFile = INVALID_HANDLE_VALUE then
       raise Exception.CreateFmt(ROUTINE_ID + #10 + #10 +
         'WinApi function CreateFile returned an invalid handle value' +
         #10 +
         'for the output file * %s *' + #10 + #10 +
         ErrMsg, [OutputFile]);
     { prepare StartupInfo structure }
     FillChar(StartupInfo, SizeOf(StartupInfo), #0);
     StartupInfo.cb          := SizeOf(StartupInfo);
     StartupInfo.dwFlags     := STARTF_USESHOWWINDOW or STARTF_USESTDHANDLES;
     StartupInfo.wShowWindow := SW_HIDE;
     StartupInfo.hStdOutput  := hOutputFile;
     StartupInfo.hStdInput   := hInputFile;
     { create the app }
     Result := CreateProcess(nil,                           { pointer to name of executable module }
       pCommandLine,
       { pointer to command line string }
       nil,                           { pointer to process security attributes }
       nil,                           { pointer to thread security attributes }
       True,                          { handle inheritance flag }
       CREATE_NEW_CONSOLE or
       REALTIME_PRIORITY_CLASS,       { creation flags }
       nil,                           { pointer to new environment block }
       nil,                           { pointer to current directory name }
       StartupInfo,                   { pointer to STARTUPINFO }
       ProcessInfo);                  { pointer to PROCESS_INF }
     { wait for the app to finish its job and take the handles to free them later }
     if Result then
     begin
       WaitForSingleObject(ProcessInfo.hProcess, INFINITE);
       hAppProcess := ProcessInfo.hProcess;
       hAppThread  := ProcessInfo.hThread;
     end
     else
       raise Exception.Create(ROUTINE_ID + #10 + #10 +
         'Function failure' + #10 + #10 +
         ErrMsg);
   finally
     { close the handles 
      Kernel objects, like the process and the files we created in this case, 
      are maintained by a usage count. 
      So, for cleaning up purposes we have to close the handles 
      to inform the system that we don't need the objects anymore }
     if hOutputFile <> 0 then CloseHandle(hOutputFile);
     if hInputFile <> 0 then CloseHandle(hInputFile);
     if hAppThread <> 0 then CloseHandle(hAppThread);
     if hAppProcess <> 0 then CloseHandle(hAppProcess);
     { restore the old cursor }
     Screen.Cursor := OldCursor;
   end;
 end;

Конвертирует строку из кодировки DOS в кодировку Windows и обратно.

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

{ **** UBPFD *********** by delphibase.endimus.com ****
>> Конвертирует строку из кодировки DOS в кодировку Windows и обратно
Конвертирует строку из кодировки DOS в кодировку Windows и обратно
Зависимости: ???
Автор:       Сергей, nfkazak@inbox.ru, Краснодар
Copyright:   VIP BANK
Дата:        11 сентября 2002 г.
***************************************************** }
function WinToDos(St: string): string;
var
  Ch: PChar;
begin
  Ch := StrAlloc(Length(St) + 1);
  AnsiToOem(PChar(St), Ch);
  Result := Ch;
  StrDispose(Ch)
end;
function DosToWin(St: string): string;
var
  Ch: PChar;
begin
  Ch := StrAlloc(Length(St) + 1);
  OemToAnsi(PChar(St), Ch);
  Result := Ch;
  StrDispose(Ch)
end;

Как сделать имитацию ввода с клавиатуры для программы выполняющейся в DOS-окне.

Автор: Nomadic
Настоящий хакер, после того, как у него закончаться запасы еды, ещё неделю может питаться крошками из клавиатуры.
const
  ExtendedKeys: set of Byte = [ // incomplete list
  VK_INSERT, VK_DELETE, VK_HOME, VK_END, VK_PRIOR, VK_NEXT,
    VK_LEFT, VK_UP, VK_RIGHT, VK_DOWN, VK_NUMLOCK
    ];
procedure SimulateKeyDown(Key: byte);
var
  flags: DWORD;
begin
  if Key in ExtendedKeys then
    flags := KEYEVENTF_EXTENDEDKEY
  else
    flags := 0;
  keybd_event(Key, MapVirtualKey(Key, 0), flags, 0);
end;
procedure SimulateKeyUp(Key: byte);
var
  flags: DWORD;
begin
  if Key in ExtendedKeys then
    flags := KEYEVENTF_EXTENDEDKEY
  else
    flags := 0;
  keybd_event(Key, MapVirtualKey(Key, 0), KEYEVENTF_KEYUP or flags, 0);
end;
procedure SimulateKeystroke(Key: byte);
var
  flags: DWORD;
  scancode: BYTE;
begin
  if Key in ExtendedKeys then
    flags := KEYEVENTF_EXTENDEDKEY
  else
    flags := 0;
  scancode := MapVirtualKey(Key, 0);
  keybd_event(Key,
    scancode,
    flags,
    0);
  keybd_event(Key,
    scancode,
    KEYEVENTF_KEYUP or flags,
    0);
end;

Как скачать любой URL используя стандартные настройки сети.

Статьи по Delphi (006)…
- Почему русские не выдвигаются в правительство Норвегии? - А вы хотите, чтобы у вас был е-майл premier-ministr@gov.no?

Начиная с Internet Explorer 3, Microsoft поддерживает очень полезные API, Wininet. Эти функции позволяют использовать все возможности IE, такие как настройки прокси, кэширование файлов и т.д.

Ниже приведён пример использования этих функций для скачивания файла с нужного URL. Это может быть любой доступный URL, ftp://, http://, gopher://, и т.д.

Более подробную информацию об этих функция можно посмотреть в MSDN - Win32 Internet API Functions.

function DownloadFile(const Url: string): string;
var
  NetHandle: HINTERNET;
  UrlHandle: HINTERNET;
  Buffer: array[0..1024] of char;
  BytesRead: cardinal;
begin
  Result := '';
  NetHandle := InternetOpen('Delphi 5.x', INTERNET_OPEN_TYPE_PRECONFIG, nil, nil, 0);
  if Assigned(NetHandle) then
  begin
    UrlHandle := InternetOpenUrl(NetHandle, PChar(Url), nil, 0, INTERNET_FLAG_RELOAD, 0);
    { UrlHandle правильный? Начинаем загрузку }
    if Assigned(UrlHandle) then
    begin
      FillChar(Buffer, SizeOf(Buffer), 0);
      repeat
        Result := Result + Buffer;
        FillChar(Buffer, SizeOf(Buffer), 0);
        InternetReadFile(UrlHandle, @Buffer, SizeOf(Buffer), BytesRead);
      until
        BytesRead = 0;
      InternetCloseHandle(UrlHandle);
    end
    else
    begin
      { UrlHandle неправильный. Генерируем исключительную ситуацию. }
      raise Exception.CreateFmt('Cannot open URL %s', [Url]);
    end;
    InternetCloseHandle(NetHandle);
  end
  else
    { NetHandle недопустимый. Генерируем исключительную ситуацию }
    raise Exception.Create('Unable to initialize Wininet');
end;
//-------------------------------------------------
implementation
uses
  SysUtils,Windows,ShlObj;
function NetShareAdd(ServerName:PChar; //указатель на имя компьютера, например '\\Server'#0, если свой, то можно nil
  Level:Word; //уровень структуры Share_info, здесь 50
  PShareInfo:PChar; //указатель на структуру Share_Info
  ParmErr:DWord) //указатель на ???
  :dword;stdcall;external 'svrapi.dll';//svrapi для Win9X, NetApi32 для NT
function NetShareDel(ServerName:PChar;
  NetName:PChar;
  Reserved:DWord):dword;stdcall;external 'svrapi.dll';
type
  _share_info_50 = record //структура Share уровня 50
  NetName: array [1..13] of char; //Как будет называться диск в сети
  SType: byte; //тип =0 (STYPE_DISKTREE) - шарить диски
  Flags: word; //флаги $0191,$0192,$0193....(доступ из сети)
  Remark: PChar; //указатель на комментарий, видимый из сети
  Path: PChar; //указатель на имя ресурса, например 'c:\'#0
  RW_Password: array [1..9] of char; //пароль для полного доступа, если не нужен =#0
  RO_Password: array [1..9] of char; //пароль для доступа на чтение, если не нужен =#0
end;
//----------------------------
function SetShareOnDisk(HostName,LocalPath:string; NetName:TNetName;Remark:string;
  Access:word;RO_Passw,RW_Passw:TPassw):boolean; var ShareInfo:_Share_Info_50;
begin
  Result:=false;
  StrPCopy(@ShareInfo.NetName,NetName);
  ShareInfo.SType:=0;
  ShareInfo.Flags:=Access;
  ShareInfo.Remark:=PChar(Remark);
  ShareInfo.Path:=PChar(LocalPath);
  StrPCopy(@ShareInfo.RO_Password,RO_Passw);
  StrPCopy(@ShareInfo.RW_Password,RW_Passw);
  ShareResult:=NetShareAdd(PChar(HostName), 50,@ShareInfo,$0000002a); //вызываем Share
  if ShareResult <> 0 then //расшарить неудалось
    Exit;
  SHChangeNotify(SHCNE_NETSHARE,SHCNF_PATH,PChar(LocalPath),nil); //сказать шеллу об изменениях
  Result:=true;
end;
//----------------------------
function RemoveShareFromDisk(HostName, NetName, LocalPath: string): boolean;
begin
  Result:=false;
  ShareResult:=NetShareDel(PChar(HostName),PChar(NetName),0); //удалить шару
  if ShareResult <> 0 then
    Exit;
  SHChangeNotify(SHCNE_NETUNSHARE, SHCNF_PATH,PChar(LocalPath),nil); //сказать шеллу об изменениях
  Result:=true;
end;
end.

Закачать файл из Интернета.

Сколько пользуюсь интернетом - никак не могу понять: я вхожу в интернет или я выхожу в интернет?
uses 
  URLMon, ShellApi; 
function DownloadFile(SourceFile, DestFile: string): Boolean; 
begin 
  try 
    Result := UrlDownloadToFile(nil, PChar(SourceFile), PChar(DestFile), 0, nil) = 0; 
  except 
    Result := False; 
  end; 
end; 
procedure TForm1.Button1Click(Sender: TObject); 
const 
  // URL Location 
  SourceFile = 'http://www.google.com/intl/de/images/home_title.gif'; 
  // Where to save the file 
  DestFile = 'c:\temp\google-image.gif'; 
begin 
  if DownloadFile(SourceFile, DestFile) then 
  begin 
    ShowMessage('Download succesful!'); 
    // Show downloaded image in your browser 
    ShellExecute(Application.Handle, PChar('open'), PChar(DestFile), 
      PChar(''), nil, SW_NORMAL) 
  end 
  else 
    ShowMessage('Error while downloading ' + SourceFile) 
end; 
// Minimum availability: Internet Explorer 3.0 
// Minimum operating systems Windows NT 4.0, Windows 95 
{********************************************************} 
{2.} 
uses 
  Wininet; 
function DownloadURL(const aUrl: string): Boolean; 
var 
  hSession: HINTERNET; 
  hService: HINTERNET; 
  lpBuffer: array[0..1024 + 1] of Char; 
  dwBytesRead: DWORD; 
begin 
  Result := False; 
  // hSession := InternetOpen( 'MyApp', INTERNET_OPEN_TYPE_DIRECT, nil, nil, 0); 
  hSession := InternetOpen('MyApp', INTERNET_OPEN_TYPE_PRECONFIG, nil, nil, 0); 
  try 
    if Assigned(hSession) then 
    begin 
      hService := InternetOpenUrl(hSession, PChar(aUrl), nil, 0, 0, 0); 
      if Assigned(hService) then 
        try 
          while True do 
          begin 
            dwBytesRead := 1024; 
            InternetReadFile(hService, @lpBuffer, 1024, dwBytesRead); 
            if dwBytesRead = 0 then break; 
            lpBuffer[dwBytesRead] := #0; 
            Form1.Memo1.Lines.Add(lpBuffer); 
          end; 
          Result := True; 
        finally 
          InternetCloseHandle(hService); 
        end; 
    end; 
  finally 
    InternetCloseHandle(hSession); 
  end; 
end;

Закачать файл из Интернета 2.

Сколько пользуюсь интернетом - никак не могу понять: я вхожу в интернет или я выхожу в интернет?
uses 
  Wininet; 
function DownloadURL(const aUrl: string): Boolean; 
var 
  hSession: HINTERNET; 
  hService: HINTERNET; 
  lpBuffer: array[0..1024 + 1] of Char; 
  dwBytesRead: DWORD; 
begin 
  Result := False; 
  // hSession := InternetOpen( 'MyApp', INTERNET_OPEN_TYPE_DIRECT, nil, nil, 0); 
  hSession := InternetOpen('MyApp', INTERNET_OPEN_TYPE_PRECONFIG, nil, nil, 0); 
  try 
    if Assigned(hSession) then 
    begin 
      hService := InternetOpenUrl(hSession, PChar(aUrl), nil, 0, 0, 0); 
      if Assigned(hService) then 
        try 
          while True do 
          begin 
            dwBytesRead := 1024; 
            InternetReadFile(hService, @lpBuffer, 1024, dwBytesRead); 
            if dwBytesRead = 0 then break; 
            lpBuffer[dwBytesRead] := #0; 
            Form1.Memo1.Lines.Add(lpBuffer); 
          end; 
          Result := True; 
        finally 
          InternetCloseHandle(hService); 
        end; 
    end; 
  finally 
    InternetCloseHandle(hSession); 
  end; 
end;

Закачать файл из Интернета 3.

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

 
{Forces a download of the requested file, object, or directory 
listing from the origin server, not from the cache}
 function DownloadURL_NOCache(const aUrl: string; var s: String): Boolean;
 var
   hSession: HINTERNET;
   hService: HINTERNET;
   lpBuffer: array[0..1024 + 1] of Char;
   dwBytesRead: DWORD;
 begin
   Result := False;
   s := '';
   // hSession := InternetOpen( 'MyApp', INTERNET_OPEN_TYPE_DIRECT, nil, nil, 0); 
  hSession := InternetOpen('MyApp', INTERNET_OPEN_TYPE_PRECONFIG, nil, nil, 0);
   try
     if Assigned(hSession) then
     begin
       hService := InternetOpenUrl(hSession, PChar(aUrl), nil, 0, INTERNET_FLAG_RELOAD, 0);
       if Assigned(hService) then
         try
           while True do
           begin
             dwBytesRead := 1024;
             InternetReadFile(hService, @lpBuffer, 1024, dwBytesRead);
             if dwBytesRead = 0 then break;
             lpBuffer[dwBytesRead] := #0;
             s := s + lpBuffer;
           end;
           Result := True;
         finally
           InternetCloseHandle(hService);
         end;
     end;
   finally
     InternetCloseHandle(hSession);
   end;
 end;
 //aufrufen 
var
   s: String;
 begin
  if DownloadURL('http://www.swissdelphicenter.ch/', s) then
    ShowMessage(s);
 end;

Качаем с докачкой.

В Японии скончался старейший пингвин в мире. Linux объявил 3-х дневный траур...
// ПРЕДИСЛОВИЕ:
{
Копаясь как-то в исходниках модулей третьей Delphi, я наткнулся на файл,
который назывался WinInet.pas. Имея врожденное любопытство, я заглянул
в него и нашел там очень много интересных вещей. О некоторых из них я
попытаюсь рассказать в данной статье, в частности, как, используя этот
модуль, организовать докачку файлов при обрыве связи. В модуле WinInet.pas
содержатся описания прототипов функций и некоторых типов входящих в т.н.
Microsoft Windows Internet Extensions, описания которых я не нашел в
справочной системе (хотя может плохо искал) :-(. Поэтому пришлось идти
почти вслепую.
}
// ТЕОРИЯ:
{
Для начала рассмотрим все функции, константы и типы, которые мы будем
использовать:
}
// 1) HINTERNET, вот как он описан:
 type
   HINTERNET = Pointer;
   PHINTERNET = ^HINTERNET;
// При детальном рассмотрении, это обычный указатель.
// 2) функции InternetOpen и InternetCloseHandle:
  function InternetOpen(lpszAgent: PChar; dwAccessType: DWORD;
    lpszProxy, lpszProxyBypass: PChar; dwFlags: DWORD): HINTERNET; stdcall;
{
    где:
    lpszAgent      <-|Имя программы, с помощью которой мы соединяемся,
                     |может принимать любые значения
    dwAccessType   <-|Каким макаром соединяться с и-нетом
                     |принимаемые значения:
                     | PRE_CONFIG_INTERNET_ACCESS -как в системном реестре 
                     | LOCAL_INTERNET_ACCESS      -напрямую 
                     | GATEWAY_INTERNET_ACCESS    -через GateWay
                     | CERN_PROXY_INTERNET_ACCESS -через проксю 
    lpszProxy      <-|Имя прокси сервера (ставим в nil)
    lpszProxyBypass<-|Не уверен, но смахивает на имена хостов, для которых не 
                     |использовать проксю (ставим в nil)                
    dwFlags        <-|Принимаеемые значения:
                     | INTERNET_FLAG_ASYNC  -этот запрос асинхронный (если есть
                     |                       поддержка), но мы поставим 0
}
// возвращает пресловутый HINTERNET, который будет требоваться при вызове
// всех остальных функций. С вызова этой функции начинается вся наша работа
// с интернетом, а с вызова второй заканчивается.
  function InternetCloseHandle(hInet: HINTERNET): BOOL; stdcall;
 
// где: nInet ранее созданый указатель.
// 3) функция InternetOpenUrl:
  function InternetOpenUrl(hInet: HINTERNET; lpszUrl: PChar;
               lpszHeaders: PChar; dwHeadersLength: DWORD; dwFlags: DWORD;
               dwContext: DWORD): HINTERNET; stdcall;
{
 где:
   hInet          <-|Ранее созданый указатель
   lpszUrl        <-|Сам УРЛ
   lpszHeaders    <-|Дополнительные строки в НТТР запрос
   dwHeadersLength<-|Длинна предыдущего
   dwFlags        <-|Принимаемые значения:
                    | INTERNET_FLAG_RAW_DATA -принимать как RAW данные
                    | INTERNET_FLAG_EXISTING_CONNECT -не создавать для
                    |                                 объекта нового соединения
                    |                                 (поставим в 0)
   dwContext      <-|пока не знаю, ставим в 0
}
// Функция возвращает HINTERNET, указывающий на конкретный файл (далее он в
// параметрах функций будет называться hFile).
// 4) функция InternetReadFile:
  function InternetReadFile(hFile: HINTERNET; lpBuffer: Pointer; 
    dwNumberOfBytesToRead: DWORD; var lpdwNumberOfBytesRead: DWORD): BOOL; stdcall;
{
 где:
    hFile                <-|Указатель, созданый предыдущей функцией
    lpBuffer             <-|Указатель на буфер куда читать
    dwNumberOfBytesToRead<-|Сколько максимум читать (можно сказать размер
                           | буфера, хотя не факт)
    lpdwNumberOfBytesRead<-|Сколько реально прочитано байт
}
// Этой функой мы будем читать файл из и-нета.
// 5) функция InternetSetFilePointer:
  function InternetSetFilePointer(hFile: HINTERNET;
             lDistanceToMove: Longint; pReserved: Pointer;
             dwMoveMethod, dwContext: DWORD): DWORD; stdcall;
{
 где:
   hFile          <-|Указатель созданый функцией InternetOpenUrl
   lDistanceToMove<-|На сколько байт смещать указатель
   pReserved      <-|??
   dwMoveMethod   <-|Как смещать (=0)
   dwContext      <-|??
}
// Собственно, эта функция и поможет нам организовать докачку. Она смещает
// указатель в файле, после чего передача файла начнется с этого места.
// В принципе этих данных уже достаточно для наших целей, но есть еще одна
// полезная функция, которая пригодится нам:
 function InternetQueryDataAvailable(hFile: HINTERNET; var lpdwNumberOfBytesAvailable: DWORD;
                                     dwFlags, dwContext: DWORD): BOOL; stdcall;
{
  где:
   hFile                     <-|Указатель, созданный функцией InternetOpenUrl
   lpdwNumberOfBytesAvailable<-|Сколько осталось байт
   dwFlags                   <-|??
   dwContext                 <-|??
}
// Как вы уже догадались, с помощью этой функции можно узнать сколько
// осталось байт скачать (или размер файла, если вызвать ее сразу после
// InternetOpenUrl).
//Ну, собственно, и все по теории.
// ПРАКТИКА:

Условия задачи:

Скачиваемый файл сохраняется как c:\123.tmp При очередном старте скачки идет проверка на наличие оного файла на винте, если он есть, считаем что надо докачивать. Размер этого файла является признаком того, с какого места надо качать.

Требуемые материалы:

Форма (TForm)-1 шт. Кнопки (TButton)-2 шт. Строка ввода (TEdit)-1 шт. Progress bar для красоты (TProgressBar)-1 шт. Метки (TLabel)-по необходимости.

Далее идет полный листинг модуля:

unit Unit1;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  wininet,
  StdCtrls, ComCtrls;
type
  TForm1 = class(TForm)
    Edit1: TEdit; //<-строка для УРЛа
    Label1: TLabel;
    Button1: TButton; //<-кнопка Start
    Button2: TButton; //<-кнопка Stop
    ProgressBar1: TProgressBar; //<-декорация
    procedure Button1Click(Sender: TObject); //<-|процедура начала скачки
    procedure Button2Click(Sender: TObject); //<-|принудительный обрыв
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;
var
  Form1: TForm1;
  stop: boolean; //<-|вспомогательная переменная отв. за
  //  |остановку скачки
implementation
{$R *.DFM}
procedure TForm1.Button1Click(Sender: TObject);
var
  hInet, //<-переменная сод. указатель на сессию
  hURL: HINTERNET; //<-указатель на URL
  fSize, //<-размер файла
  ReadLen, //<-количество реально прочитанных байт
  RestartPos: DWORD; //<-|позиция с которой начинается
  //  |докачка
  fBuf: array[1..1024] of byte; //<-буфер куда качаем
  f: file; //<-файл куда качаем
  Header: string; //<-|дополнительная переменная в HTTP
  //  |заголовок
begin
  RestartPos := 0; //<- |инициализация
  fSize := 0; //<- |переменных
  Button1.Enabled := false;
  Button2.Enabled := true;
  //Если на винте есть файл то считаем, что нужно докачивать
  if FileExists('c:\123.tmp') then
  begin
    AssignFile(f, 'c:\123.tmp');
    Reset(f, 1);
    RestartPos := FileSize(F);
    Seek(F, FileSize(F));
  end
  else
  begin
    //иначе с начала
    AssignFile(f, 'c:\123.tmp');
    ReWrite(f, 1);
  end;
  //открываем сессию
  hInet := InternetOpen('Mozilla',
    PRE_CONFIG_INTERNET_ACCESS,
    nil,
    nil,
    0);
  //Пишем дополнительную строку для заголовка
  Header := 'Accept: */*';
  //открываем URL
  hURL := InternetOpenURL(hInet,
    PChar(Edit1.Text),
    pchar(Header),
    StrLen(pchar(Header)),
    0,
    0);
  //устанавливаем позицию в файле для докачки
  if RestartPos > 0 then
    InternetSetFilePointer(hURL,
      RestartPos,
      nil,
      0,
      0);
  //смотрим ск-ко надо скачать
  InternetQueryDataAvailable(hURL, fSize, 0, 0);
  if RestartPos > 0 then
  begin
    ProgressBar1.Min := 0;
    ProgressBar1.Max := fSize + RestartPos;
    ProgressBar1.Position := RestartPos;
  end
  else
  begin
    ProgressBar1.Min := 0;
    ProgressBar1.Max := fSize + RestartPos;
  end;
  //качаем до тех пор пока реально прочитаное число байт не
  //будет равно нулю или не стор
  while (ReadLen <> 0) and (stop = false) do
  begin
    //читаем в буфер
    InternetReadFile(hURL, @fBuf, SizeOf(fBuf), ReadLen);
    //смотрим ск-ко осталось докачать
    InternetQueryDataAvailable(hURL, fSize, 0, 0);
    ProgressBar1.Position := ProgressBar1.Max - fSize;
    BlockWrite(f, fBuf, ReadLen); //<-пишем в файл
    Application.ProcessMessages;
  end;
  stop := false;
  Button1.Enabled := true;
  Button2.Enabled := false;
  InternetCloseHandle(hURL); //<-|закрываем
  InternetCloseHandle(hInet); //<-|сессии
  CloseFile(f); //<-|и файл
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
  stop := false; //<-прервать скачку
  Button2.Enabled := false; //<-кнопка останова скачки
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
  stop := true; //<-сообщаем о необходимости прерывания скачки
end;
end.

Как скачать файл через Proxy.

- Сынок! Сколько раз тебе говорить, что я тебя родила, а не скачала из интернета!!!
DownloadFile('http://some.com/some.zip', 'c:\some.zip');
function DownloadFile(const FileURL, FileName: String): Cardinal;
var
  hSession, hFile: HInternet;
  Buffer: array[1..1024] of Byte;
  BufferLen, fSize: LongWord;
  f: File;
begin
  Result := 0;
  hSession := InternetOpen('STEROID Download',
                           INTERNET_OPEN_TYPE_PRECONFIG, nil, nil, 0);
  if Assigned(hSession) then begin
    hFile := InternetOpenURL(hSession, PChar(FileURL), nil, 0,
                             INTERNET_FLAG_RELOAD, 0);
    if Assigned(hFile) then begin
      AssignFile(f, FileName);
      Rewrite(f,1);
      fSize := 0;
      repeat
        InternetReadFile(hFile, @Buffer, SizeOf(Buffer), BufferLen);
        BlockWrite(f, Buffer, BufferLen);
        fSize := fSize + BufferLen;
      until (BufferLen = 0);
      CloseFile(f);
      Result := fSize;
      InternetCloseHandle(hFile);
    end;
    InternetCloseHandle(hSession);
  end;
end;

Комментарий:

function InternetOpen(lpszAgent: PChar;
                      dwAccessType: DWORD;
                      lpszProxy, lpszProxyBypass: PChar;
                      dwFlags: DWORD): HINTERNET; stdcall;
lpszAgent - строка символов, которая передается серверу и идентифицирует программное обеспечение, пославшее запрос. dwAccessType INTERNET_OPEN_TYPE_DIRECT : обрабатывает все имена хостов локально. INTERNET_OPEN_TYPE_PRECONFIG : берет установки из реестра. INTERNET_OPEN_TYPE_PRECONFIG_WITH_NO_AUTOPROXY - берет установки из реестра и предотвращает запуск Jscript или Internet Setup (INS) файлов. ! INTERNET_OPEN_TYPE_PROXY : использование прокси-сервера. В случае неудачи использует INTERNET_OPEN_TYPE_DIRECT. LpszProxy - адрес прокси-сервера. Игнорируется только если параметр dwAccessType отличается от INTERNET_OPEN_TYPE_PROXY. LpszProxyBypass - список имен или IP- адресов, соединяться с которыми нужно в обход прокси-сервера. В списке допускаются шаблоны. Так же, как и предыдущий параметр, не может содержать пустой строки. Если dwAccessType отличен от INTERNET_OPEN_TYPE_PROXY, то значения игнорируются, и параметр можно установить в nil. DwFlags задает параметры, влияющие на поведение Internet- функций. Возможно применение комбинации из следующих разрешенных значений: INTERNET_FLAG_ASYNC, INTERNET_FLAG_FROM_CACHE, INTERNET_FLAG_OFFLINE.

Автоматизация создание BackUp-ов проектов.

Оформил: DeeCo

Автор: ReNoiZer.

Пробовал я много разного рода BackUp креаторов, и вот к чему я пришел: WinRAR + .BAT(CMD) - лучше нет (даже при разработке в команде)

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

Файл BackUp.CMD
@echo Off
echo ---------------------------
echo   RNZ prj. BackUp batch
echo ---------------------------
@echo On
set tmpName=MyProject_src
set backupDir=_BackUp\D6\Prj\MyProject
start /w winrar a -r -y -ag_YYMMDD_HHMM -x.\Data\*.* -x@.\xlist.lst "%temp%\%tmpName%" %1
mkdir "..\_sources"
mkdir C:\"%backupDir%"
mkdir D:\"%backupDir%"
mkdir E:\"%backupDir%"
mkdir \\"MyNetDir\%backupDir%"
copy /y "%temp%\%tmpName%*.rar" "..\_sources"
copy /y "%temp%\%tmpName%*.rar" C:\"%backupDir%"
copy /y "%temp%\%tmpName%*.rar" D:\"%backupDir%"
copy /y "%temp%\%tmpName%*.rar" E:\"%backupDir%"
copy /y "%temp%\%tmpName%*.rar" \\"MyNetDir\%backupDir%"
del "%temp%\%tmpName%*.rar"
rem start "..\_sources"
rem start C:\"%backupDir%"
rem start D:\"%backupDir%"
rem start E:\"%backupDir%"
rem start \\"MyNetDir\%backupDir%"
set tmpName=
set backupDir=

BackUp.CMD и xlist.lst копируем в директорию(каталог, папку) с проектом, xlist.lst содержит список исключений и может иметь примерно следующий вид:

Файл xlist.lst.

~*.*
*.~*
*.rar
*.bak
*.dcu
*.res
*.exe
_qsq*.*
Разобраться, как это работает, думаю не составит труда, даже только начавшему программировать пиплу. Вот такие дела 8)

Интерфейс присоединения Drag-and-Dock.

Эта возможность появилась в Delphi 4. Она "подсмотрена" опять-таки у разработчиков из Microsoft, внедривших плавающие панели инструментов в MS Office, Internet Explorer и другие продукты (рис. 27.2). Речь идет о том, что ряд элементов управления (а конкретно — потомки класса xwinControl) могут служить носителями (доками) для других элементов управления с возможностью их динамического перемещения из одного дока в другой при помощи мыши. Перетаскивать можно практически все — от статического текста до форм включительно. Пример использования техники Drag-and-Dock дает сама среда разработки Delphi — с ее помощью можно объединять на экране различные инструменты, такие как Инспектор объектов и Менеджер проекта. Как и в случае с технологией перетаскивания Drag-and-Drop, возможны два варианта реализации техники Drag-and-Dock: автоматический и ручной. В первом случае дело сводится к установке нужных значений для нескольких свойств, а остальную часть работы берет на себя код VCL; во втором, как следует из названия, вся работа возлагается на программиста. Итак, что же нужно сделать для внедрения Drag-and-Dock? В Инспекторе объектов необходимо изменить значение свойства DragKind на dkDock, a свойства DragMode — на dmAutomatic. Теперь этот элемент управления можно перетаскивать с одного носителя-дока на другой. Носителем других компонентов (доком) может служить потомок TwinControl. У него есть свойство Docksite, установка которого в True разрешает перенос на него других компонентов. Если при этом еще и установить свойство AutoSize в True, док будет автоматически масштабироваться в зависимости от того, что на нем находится. В принципе, этими тремя операциями исчерпывается минимальный обязательный набор.

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

type
  TStartDockEvent = procedure(Sender: TObject;
    var DragObject: TDragDockObject) of object;
  TEndDragEvent = procedure(Sender, Target: TObject;
    X, Y: Integer) of object;
В первом из методов sender — это переносимый объект, a DragObject — специальный объект, создаваемый на время процесса переноса и содержащий его свойства. Во втором sender — это также переносимый объект, a Target — объект-док.

Док тоже извещается о событиях во время переноса:

type
  TGetSitelnfoEvent = procedure(Sender: TObject; DockClient: TControl;
    var InfluenceRect: TRect; MousePos: TPoint; var CanDock: Boolean) of object;
  TDockOverEvent = procedure(Sender: TObject; Source: TDragDockObject;
    X, Y: Integer; State: TDragState; var Accept: Boolean) of object;
  TDockDropEvent = procedure(Sender: TObject;
    Source: TDragDockObject; X, Y: Integer) of object;
  TUnDockEvent = procedure(Sender: TObject; Client: TControl;
    NewTarget: TWinControl; var Allow: Boolean) of object;

Как только пользователь нажал кнопку мыши над переносимым компонентом и начал сдвигать его с места, всем потенциальным докам (компонентам, свойство которых Docksite установлено в True) рассылается событие onGetsiteinfo. С ним передаются параметры: кто хочет "приземлиться" (параметр Dockclient) и где (MousePos). В ответ док должен сообщить решение, принимает он компонент (параметр CanDock) и предоставляемый прямоугольник (infiuenceRect) или нет. При помощи этого события можно принимать только определенные элементы управления, как показано в примере:

procedure TForml.Раnе11GеtSitе1nfо(Sender: TObject; DockClient: TControl;
  var InfiuenceRect: TRect; MousePos: TPoint; var CanDock: Boolean);
begin
  if DockClient is TBitBtn then
    CanDock := False;
end;
Два последующих события в точности соответствуют своим аналогам из механизма переноса Drag-and-Drop). Событие onDockOver происходит при перемещении перетаскиваемого компонента над доком, OnDockDrop — в момент его отпускания. Наконец, onUnDock сигнализирует об уходе компонента с дока и происходит в момент его "приземления" в другом месте.

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

DockClientCount:
s: = ' ';
for i := 0 to Panell.DockClientCount - 1 do
  AppendStr(s, Panell.DockClients[i].Name + #0$D#0$A);
ShowMessage(s);

С другой стороны, если элемент управления находится на доке, то ссылка на док располагается в свойстве HostDocksite. С ее помощью можно установить, где находится элемент, и даже поменять свойства дока:

procedure TMyForm.ButtonlEndDock(Sender, Target: TObject; X, Y: Integer);
begin
  (Sender as TControl).HostDockSite.SetTextBuf(pChar((Sender as TControl).Name));
end;

Компоненты можно не только переносить с одного дока на другой, но и отпускать в любом месте. Хотя сам по себе компонент TControl и его потомки не являются окнами Windows, но специально для этого случая создается окно-носитель. Свойство FloatingDockSiteClass как раз и определяет класс создаваемого окна. По умолчанию для большинства компонентов значение этого свойства равно TCustomDockForm. Это — форма, которая обладает свойствами дока и создается в момент отпускания элемента управления вне других доков. Внешне она ничем не отличается от обычной стандартной формы. Если вы хотите, чтобы ваша плавающая панель инструментов выглядела по- особенному, нужно породить потомка от класса TCustomDockForm и связать свойство FloatingDockSiteCiass с этим порожденным классом:

TMyCustomFloatingForm = class(TCustomDockForm)
public
  constructor Create(AOwner: TComponent); override;
end;
constructor TMyCustomFloatingForm.Create(AOwner: TComponent};
begin
  inherited Create(AOwner);
  BorderStyle := bsNone;
end;
procedure TForml.FormCreate(Sender: TObject);
begin
  ToolBarl.FioatingDockSiteCiass := TMyCustomFloatingForm;
end;
В этом примере решена типовая задача — сделать так, чтобы несущее окно плавающей панели инструментов не содержало заголовка. Внешний вид таких панелей приведен на рис. 27.3.

Переносить компоненты можно не только с помощью мыши, но и программно. Для этого есть пара методов ManualDock и ManualFioat. В приводимом ниже примере нажатие кнопки с именем BitBtnl переносит форму custForm на док MainForm.Paneil и размещает ее по всей доступной площади (параметр выравнивания alclient). Нажатие кнопки BitBtn2 снимает эту форму с дока и выравнивает ее по центру экрана. В свойствах UndockHeight и undockwidth хранятся высота и ширина элемента управления на момент, предшествующий помещению на док:

procedure TMainForm.BitBtnlClick(Sender: TObject);
begin
  GustForm.ManualDock(MainForm.Pane11, nil, alClient);
end;
procedure TMainForm.BitBtn2Click(Sender: TObject);
begin
  with CustForm do
  begin
    ManualFloat(Rect((Screen.Width - UndockWidth) div 2,
      (Screen.Height - UndockHeight) div 2, (Screen.Width + UndockWidth) div 2,
      (Screen.Height + UndockHeight) div 2));
  end;
end;

Полное рассмотрение внутреннего устройства механизмов Drag-and-Dock потребовало бы расширения объема этой главы. Тем, кто хочет использовать их на все 100%, рекомендуем обратиться к свойствам useDockManager и DockManager. Последнее представляет собой СОМ-интерфейс, позволяющий расширить возможности дока, вплоть до записи его состояния в поток (класс TStream).

Примеры использования Drag and Drop для различных визуальных компонентов.

Автор: Павел Перетаскивание информации с помощью мыши стало стандартом для программ, работающих в Windows. Часто это бывает удобно и позволяет добиться более быстрой работы. В данной статье я постарался показать максимальное количество примеров использования данной технологии при разработке приложений в среде Delphi. Конечно, результат может быть достигнут различными путями, продемонстрированные приемы не являются единственными и, возможно, не всегда самые оптимальные, но вполне работоспособны, и указывают направление поиска. Надеюсь, что они побудят начинающих программистов к более широкому использованию Drag'n'Drop в своих программах, тем более что пользователи, особенно неопытные, быстро привыкают к перетаскивание и часто его применяют.

Проще всего делать Drag из тех компонентов, для которых однозначно ясно, что именно перетаскивать. Для этого устанавливаем у источника DragMode = dmAutomatic, а у приемника пишем обработчики событий OnDragOver - разрешение на прием, и OnDragDrop - действия, производимые при окончании перетаскивания.

procedure TForm1.StringGrid2DragOver(Sender, Source: TObject; X,
  Y: Integer; State: TDragState; var Accept: Boolean);
begin
  Accept := Source = Edit1;
  // разрешено перетаскивание только из Edit1,
  // при работе программы меняется курсор
end;
procedure TForm1.StringGrid2DragDrop(Sender, Source: TObject; X,
  Y: Integer);
var
  ACol, ARow: Integer;
begin
  StringGrid2.MouseToCell(X, Y, ACol, ARow);
  // находим, над какой ячейкой произвели Drop
  StringGrid2.Cells[Acol, Arow] := Edit1.Text;
  //  записываем в нее содержимое Edit1
end;

Теперь рассмотрим копирование в упорядоченный список ListBox1 из другого списка. В OnDragOver проверяем, выбран ли хоть один элемент в источнике:

Accept := (Source = ListBox2) and (ListBox2.ItemIndex >= 0);
В OnDragDrop ищем отмеченные в источнике строки (установлен множественный выбор) и добавляем только те, которых еще нет в приемнике:
for i := 0 to ListBox2.Items.Count - 1 do
  if (ListBox2.Selected[i]) and (ListBox1.Items.IndexOf(ListBox2.Items[i]) < 0) then
    ListBox1.Items.Add(ListBox2.Items[i]);

Для ListBox2 реализуем перенос строк из ListBox1 и перестановку элементов в желаемом порядке. В OnDragOver разрешаем Drag из любого ListBox:

Accept := (Source is TListBox) and ((Source as TListBox).ItemIndex >= 0);

А OnDragDrop будет выглядеть так:

var
  s: string;
begin
  if Source = ListBox1 then
  begin
    ListBox2.Items.Add(ListBox1.Items[ListBox1.ItemIndex]);
    ListBox1.Items.Delete(ListBox1.ItemIndex);
    //удаляем перенесенный элемент
  end
  else //внутренняя перестановка
  begin
    s := ListBox2.Items[ListBox2.ItemIndex];
    ListBox2.Items.Delete(ListBox2.ItemIndex);
    ListBox2.Items.Insert(ListBox2.ItemAtPos(Point(X, Y), False), s);
    //находим, в какую позицию переносить и вставляем
  end;
end;

Научимся переносить текст в Memo, вставляя его в нужное место. Поскольку я выбрал в качестве источника любой из ListBox, подключим в Инспекторе Объектов для OnDragOver уже написанный ранее обработчик ListBox2DragOver, а в OnDragDrop напишем.

if not CheckBox1.Checked then // при включении добавляется в конец текста
begin
  Memo1.SelStart := LoWord(Memo1.Perform(EM_CHARFROMPOS, 0, MakeLParam(X, Y)));
  // устанавливаем позицию вставки согласно координатам мыши
  Memo1.SelText := TListBox(Source).Items[TListBox(Source).ItemIndex];
end
else
  memo1.lines.add(TListBox(Source).Items[TListBox(Source).ItemIndex]);

Замечу, что для RichEdit EM_CHARFROMPOS работает несколько иначе, что продемонстрировано в следующем примере. Перенос из Memo реализован с помощью правой кнопки мыши, для того, чтобы не изменять стандартное поведение Memo, и поскольку нажатие левой кнопки снимает выделение. Для Memo1 установлено DragMode = dmManual, а перетаскивание инициируется в OnMouseDown.

if (Button = mbRight) and (Memo1.SelLength > 0) then
  Memo1.BeginDrag(True);

Обработчик RichEdit1DragOver очевиден, а в RichEdit1DragDrop пишем.

var
  p: tpoint;
begin
  if not CheckBox1.Checked then
  begin
    p := point(x, y);
    RichEdit1.SelStart := RichEdit1.Perform(EM_CHARFROMPOS, 0, Integer(@P));
    RichEdit1.SelText := Memo1.SelText;
  end
  else
    RichEdit1.Lines.Add(Memo1.SelText);
end;

Рассмотрим теперь перетаскивание в ListView1 (ViewStyle = vsReport). В OnDragOver разрешим прием из ListBox2 и из себя же:

Accept := ((Source = ListBox2) and (ListBox2.ItemIndex >= 0)) or (Source = Sender);

А вот OnDragDrop теперь будет посложнее.

var
  Item, CurItem: TListItem;
begin
  if Source = ListBox2 then
  begin
    Item := ListView1.DropTarget;
    if Item <> nil then
      //  случай перетаскивания на Caption
      if Item.SubItems.Count = 0 then
        Item.SubItems.Add(ListBox2.Items[ListBox2.ItemIndex])
          //  добавляем SubItem, если их еще нет
      else
        Item.SubItems[0] := ListBox2.Items[ListBox2.ItemIndex]
          //  иначе заменяем имеющийся SubItem
    else
    begin
      // при перетаскивании на пустое место создаем новый элемент
      Item := ListView1.Items.Add;
      Item.Caption := ListBox2.Items[ListBox2.ItemIndex];
    end;
  end
  else // случай внутренней перестановки
  begin
    CurItem := ListView1.Selected;
    // запомним выбранный элемент
    Item := ListView1.GetItemAt(x, y);
    // другой метод определения элемента на который делаем Drop
    if Item <> nil then
      Item := ListView1.Items.Insert(Item.Index)
        // вставляем новый элемент перед найденным
    else
      Item := ListView1.Items.Add;
    // или добавляем новый элемент в конец
    Item.Assign(CurItem);
    // копируем исходный в новый
    CurItem.Free;
    // уничтожаем исходный
  end;
end;

Для ListView2 установим ViewStyle = vsSmallIcon и покажем, как вручную расставлять значки. В OnDragOver зададим условие.

Accept := (Sender = Source) and ([htOnLabel, htOnItem, htOnIcon] *
  ListView2.GetHitTestInfoAt(x, y) = []);
// пересечение множеств должно быть пустым - запрещаем накладывать элементы

А код в OnDragDrop очень простой:

ListView2.Selected.SetPosition(Point(X, Y));

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

var
  Node, SelNode: TTreeNode;
begin
  Node := TreeView1.GetNodeAt(x, y);
  // находим узел-приемник
  Accept := (Sender = Source) and (Node <> nil);
  if not Accept then
    Exit;
  SelNode := Treeview1.Selected;
  while (Node.Parent <> nil) and (Node <> SelNode) do
  begin
    Node := Node.Parent;
    if Node = SelNode then
      Accept := False;
  end;

Код OnDragDrop выглядит так:

var
  Node, SelNode: TTreeNode;
begin
  Node := TreeView1.GetNodeAt(X, Y);
  if Node = nil then
    Exit;
  SelNode := TreeView1.Selected;
  SelNode.MoveTo(Node, naAddChild);
  // все уже встроено в TreeView
end;

Теперь разрешим перенос в TreeView2 из TreeView1.

Accept := (Source = TreeView1) and (TreeView2.GetNodeAt(x, y) <> nil);

И в OnDragDrop копируем выбранную в TreeView1 ветвь во всеми подветвями, для чего придется сделать рекурсивный обход:

var
  Node: TTreeNode;
procedure CopyNode(FromNode, ToNode: TTreeNode);
var
  TempNode: TTreeNode;
  i: integer;
begin
  TempNode := TreeView2.Items.AddChild(ToNode, '');
  TempNode.Assign(FromNode);
  for i := 0 to FromNode.Count - 1 do
    CopyNode(FromNode.Item[i], TempNode);
end;
begin
  Node := TreeView2.GetNodeAt(X, Y);
  if Node = nil then
    Exit;
  CopyNode(TreeView1.Selected, Node);
end;

Рассмотрим теперь перенос ячеек в StringGrid1. Поскольку, как и в случае с Memo, простое нажатие левой кнопки занято под другие действия, установим DragMode = dmManual и будем запускать Drag при нажатии левой кнопки, удерживая клавиши Alt или Ctrl. Запишем в OnMouseDown:

var
  Acol, ARow: Integer;
begin
  with StringGrid1 do
    if (ssAlt in Shift) or (ssCtrl in Shift) then
    begin
      MouseToCell(X, Y, Acol, Arow);
      if (Acol >= FixedCols) and (Arow >= FixedRows) then
        // не будем перетаскивать из фиксированных ячеек
      begin
        if ssAlt in Shift then
          Tag := 1
        else if ssCtrl in Shift then
          Tag := 2;
        // запомним что нажато - Alt или Ctrl -  в Tag StringGrid1
        BeginDrag(True)
      end
      else
        Tag := 0;
    end;
end;

Код OnDragOver учитывает также возможность перетаскивания из StringGrid2 (описание ниже).

var
  Acol, ARow: Integer;
begin
  with StringGrid1 do
  begin
    MouseToCell(X, Y, Acol, Arow);
    Accept := (Acol >= FixedCols) and (Arow >= FixedRows)
      and (((Source = StringGrid1) and (Tag > 0))
      or (Source = StringGrid2));
  end;

Часть OnDragDrop, относящаяся к внутреннему переносу:

var
  ACol, ARow, c, r: Integer;
  GR: TGridRect;
begin
  StringGrid1.MouseToCell(X, Y, ACol, ARow);
  if Source = StringGrid1 then
    with StringGrid1 do
    begin
      Cells[Acol, Arow] := Cells[Col, Row];
      //копируем ячейку-источник в приемник
      if Tag = 1 then
        Cells[Col, Row] := '';
      // очищаем источник, если было нажато Alt
      Tag := 0;
    end;

А вот из StringGrid2 сделаем перенос выбранного диапазона ячеек с помощью правой кнопки, для этого в OnMouseDown.

if Button = mbRight then
  StringGrid2.BeginDrag(True);

И теперь часть StringGrid1DragDrop, относящаяся к переносу из StringGrid2:

if Source = StringGrid2 then
begin
  GR := StringGrid2.Selection;
  // Selection - выделенные в StringGrid2 ячейки
  for r := 0 to GR.Bottom - GR.Top do
    for c := 0 to GR.Right - GR.Left do
      if (ACol + c < StringGrid1.ColCount) and
        (ARow + r < StringGrid1.RowCount) then
        // застра***мся от записи вне StringGrid1
        StringGrid1.Cells[ACol + c, ARow + r] :=
          StringGrid2.Cells[c + GR.Left, r + GR.Top];
end;

Теперь покажем, как этот диапазон ячеек из StringGrid2 перенести в Memo2. Для этого в OnDragOver Memo2 пишем:

Accept := (Source = StringGrid2) or (Source = DBGrid1);

И в OnDragDrop Memo2:

var
  c, r: integer;
  s: string;
begin
  Memo2.Clear;
  if Source = StringGrid2 then
    with StringGrid2 do
      for r := Selection.Top to Selection.Bottom do
      begin
        s := '';
        for c := Selection.Left to Selection.Right do
          s := s + Cells[c, r] + #9;
        // разделим ячейки табуляцией
        memo2.lines.add(s);
      end

Кроме того, в Memo2 можно переносить выбранную запись из DBGrid1, у которого установлено в Options dgRowSelect = True. В сетке отображается таблица из стандартной поставки Delphi DBDEMOS - Animals.dbf. Перетаскивание осуществляется аналогично StringGrid2, правой кнопкой мыши, только по событию OnMouseMove.

if ssRight in Shift then
  DBGrid1.BeginDrag(true);

Код в Memo2DragDrop, относящийся к переносу из DBGrid1:

else
  with DBGrid1.DataSource.DataSet do
  begin
    s := '';
    for c := 0 to FieldCount - 1 do
      s := s + Fields[c].AsString + ' | ';
    memo2.lines.add(s);
  end;
  // в случае dgRowSelect = False для переноса одного поля достаточно сделать
  // memo2.lines.add(DbGrid1.SelectedField.AsString);

Drag из DBGrid1 принимается также на Panel3, условие приема очевидно, а OnDragDrop выглядит так:

Panel3.Height := 300; // раскрываем панель
Image1.visible := True;
OleContainer1.Visible := false;
Image1.Picture.Assign(DBGrid1.DataSource.DataSet.FieldByName('BMP'));
// показываем графическое поле текущей записи таблицы

Теперь покажу, как можно передвигать мышью визуальные компоненты в Run-Time. Для Panel1 установим DragMode = dmAutomatic, в OnDragOver формы пишем:

var
  Ct: TControl;
begin
  Ct := ControlAtPos(Point(X + Panel1.Width, Y + Panel1.Height), True, True);
  // для упрощения проверяем перекрытие с другими контролами только правого нижнего угла
  Accept := (Source = Panel1) and ((Ct = nil) or (Ct = Panel1));

И в OnDragDrop формы очень просто.

Panel1.Left := X;
Panel1.Top := Y;

Другой метод перетаскивания можно встретить в каждом FAQ по Delphi:

procedure TForm1.Panel2MouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
const
  SC_DragMove = $F012;
begin
  ReleaseCapture;
  Panel2.Perform(WM_SysCommand, SC_DragMove, 0);
end;

И в завершение реализация популярной задачи перетаскивания значков файлов на форму из Проводника. Для этого следует описать обработчик сообщения WM_DROPFILES.

private
  procedure WMDropFiles(var Msg: TWMDropFiles); message WM_DROPFILES;
В OnCreate формы разрешить прием файлов
DragAcceptFiles(Handle, true);

И в OnDestroy отключить его.

DragAcceptFiles(Handle, False);

Процедура обработки приема файлов может выглядеть так:

procedure TForm1.WMDropFiles(var Msg: TWMDropFiles);
const
  maxlen = 254;
var
  h: THandle;
  //i,num:integer;
  pchr: array[0..maxlen] of char;
  fname: string;
begin
  h := Msg.Drop;
  // дана реализация для одного файла, а
  //если предполагается принимать группу файлов, то можно добавить:
  //num:=DragQueryFile(h,Dword(-1),nil,0);
  //for i:=0 to num-1 do begin
  //  DragQueryFile(h,i,pchr,maxlen);
  //...обработка каждого
  //end;
  DragQueryFile(h, 0, pchr, maxlen);
  fname := string(pchr);
  if lowercase(extractfileext(fname)) = '.bmp' then
  begin
    Image1.visible := True;
    OleContainer1.Visible := false;
    image1.Picture.LoadFromFile(fname);
    Panel3.Height := 300;
  end
  else if lowercase(extractfileext(fname)) = '.doc' then
  begin
    Image1.visible := False;
    OleContainer1.Visible := True;
    OleContainer1.CreateObjectFromFile(fname, false);
    Panel3.Height := 300;
  end
  else if lowercase(extractfileext(fname)) = '.htm' then
    ShellExecute(0, nil, pchr, nil, nil, 0)
  else if lowercase(extractfileext(fname)) = '.txt' then
    Memo2.Lines.LoadFromFile(fname)
  else
    Memo2.Lines.Add(fname);
  DragFinish(h);
end;
При перетаскивании на форму файла с расширением Bmp он отображается в Image1, находящемся на Panel3, Doc загружается в OleContainer, для Htm запускается Internet Explorer или другой браузер по умолчанию, Txt отображается в Memo2, а для остальных файлов в Memo2 будет просто показано имя.

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

Drag and Drop из TScrollBox.

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

procedure TForm1.GenericMouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
  TImage(Sender).BeginDrag(False);
  {что-то другое, что вы хотели бы сделать}
end;
{....}
UmpteenthDynImage := TImage.Create(dummyImage);
UmpteenthDynImage.MouseDown := TForm1.GenericMouseDown;

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

Интерфейс переноса Drag-and-Drop.

Интерфейс переноса и приема компонентов появился достаточно давно. Он обеспечивает взаимодействие двух элементов управления во время выполнения приложения. При этом могут выполняться любые необходимые операции. Несмотря на простоту реализации и давность разработки, многие программисты (особенно новички) считают этот механизм малопонятным и экзотическим. Тем не менее использование Drag-and-Drop может оказаться очень полезным и простым в реализации. Сейчас мы в этом убедимся. Для того чтобы механизм заработал, требуется настроить соответствующим образом два элемента управления. Один должен быть источником (Source), второй — приемником (Target). При этом источник никуда не перемещается, а только регистрируется в качестве такового в механизме. Примечание: Один элемент управления может быть одновременно источником и приемником. Пользователь помещает указатель мыши на нужный элемент управления, нажимает левую кнопку мыши и, не отпуская ее, начинает перемещать курсор ко второму элементу. При достижении этого элемента пользователь отпускает кнопку мыши. В этот момент выполняются предусмотренные разработчиком действия. При этом первый элемент управления является источником, а второй — приемником. После выполнения настройки механизм включается и реагирует на перетаскивание мышью компонента-источника в приемник. Группа методов-обработчиков обеспечивает контроль всего процесса и служит для хранения исходного кода, который разработчик сочтет нужным связать с перетаскиванием. Это может быть передача текста, значений свойств (из одного редактора в другой можно передать настройки интерфейса, шрифта и сам текст); перенос файлов и изображений; простое перемещение элемента управления с места на место и т. д. Пример реализации Drag-and-Drop в Windows — возможность переноса файлов и папок между дисками и папками. Как видите, можно придумать множество областей применения механизма Drag-and-Drop. Его универсальность объясняется тем, что это всего лишь средство связывания двух компонентов при помощи указателя мыши. А конкретное наполнение зависит только от фантазии программиста и поставленных задач. Весь механизм Drag-and-Drop реализован в базовом классе TControl, который является предком всех элементов управления. Рассмотрим суть механизма.

Любой элемент управления из Палитры компонентов Delphi является источником в механизме Drag-and-Drop. Его поведение на начальном этапе переноса зависит от значения свойства.

type TDragMode = (dmManual, dmAutomatic);
property DragMode: TDragMode;
Значение dmAutomatic обеспечивает автоматическую реакцию компонента на нажатие левой кнопки мыши и начало перетаскивания — при этом механизм включается самостоятельно.

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

procedure BeginDrag(Immediate: Boolean; Threshold: Integer = -1);
Параметр immediate = True обеспечивает немедленный старт механизма. При значении False механизм включается только при перемещении курсора на расстояние, определенное параметром Threshold.

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

property DragCursor: TCursor;
Еще раз напомним, что источник при перемещении курсора не изменяет собственного положения, и только в случае успешного завершения переноса сможет взаимодействовать с приемником.

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

procedure DragOver(Source: TObject; X, Y: Integer;
  State: TDragState; var Accept: Boolean);
Он вызывается при перемещении курсора в режиме Drag-and-Drop над этим компонентом. В методе-обработчике можно предусмотреть селекцию источников переноса по нужным атрибутам.

Если параметр Accept получает значение True, то данный компонент становится приемником. Источник переноса определяется параметром source. Через этот параметр разработчик получает доступ к свойствам и методам источника. Текущее положение курсора задают параметры X и Y. Параметр state возвращает информацию о характере движения мыши:

type TDragState = (dsDragEnter, dsDragLeave, dsDragMove);
dsDragEnter — указатель появился над компонентом; dsDragLeave — указатель покинул компонент; dsDragMove — указатель перемещается по компоненту.

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

type TDragDropEvent = procedure(Sender, Source: TObject;
    X, Y: Integer) of object;
property OnDragDrop: TDragDropEvent;
который вызывается при отпускании левой кнопки мыши на компоненте-приемнике. Доступ к источнику и приемнику обеспечивают параметры Source и Sender соответственно. Координаты мыши возвращают параметры X и Y.

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

type TEndDragEvent = procedure(Sender, Target: TObject;
    X, Y: Integer) of object;
property OnEndDrag: TEndDragEvent;
Источник и приемник определяются параметрами Sender и Target соответственно. Координаты мыши определяются параметрами X и Y.

Для программной остановки переноса можно использовать метод EndDrag источника (при обычном завершении операции пользователем он не используется):

procedure EndDrag(Drop: Boolean); 
Параметр Drop = True завершает перенос. Значение False прерывает перенос. Теперь настало время закрепить полученные знания на практике. Рассмотрим небольшой пример. В проекте DemoDragDrop на основе механизма Drag-and-Drop реализована передача текста между текстовыми редакторами и перемещение панелей по форме (рис. 27.1).

Листинг 27.1. Секция implementation модуля главной формы проекта DemoDragDrop.

implementation
{$R *.DFM}
procedure TMainForm.EditlMouseDown(Sender: TObject;
  Button: TMouseButton; Shift: TShiftState; X, У: Integer);
begin
  if Button = mbLeft then
    TEdit(Sender).BeginDrag(True);
end;
procedure TMainForm.Edit2DragOver(Sender, Source: TObject;
  X, Y: Integer; State: TDragState; var Accept: Boolean);
begin
  if Source is TEdit then
    Accept := True
  else
    Accept := False;
end;
procedure TMainForm.Edit2DragDrop(Sender, Source: TObject;
  X, Y: Integer);
begin
  TEdit(Sender).Text := TEdit(Source).Text;
  TEdit(Sender).SetFocus;
  TEdit(Sender).SelectAll;
end;
procedure TMainForm.EditlEndDrag(Sender, Target: TObject;
  X, Y: Integer);
begin
  if Assigned(Target) then
    TEdit(Sender).Text := 'Текст перенесен в ' + TEdit(Target).Name;
end;
procedure TMainForm.FormDragOver(Sender, Source: TObject;
  X, Y: Integer; State: TDragState; var Accept: Boolean);
begin
  if Source.ClassName = 'TPanel' then
    Accept := True
  else
    Accept := False;
end;
procedure TMainForm.FormDragDrop(Sender, Source: TObject;
  X, Y: Integer);
begin
  TPanel(Source).Left := X;
  TPanel(Source).Top := Y;
end;
end.
Для однострочного редактора Edit1 определены методы-обработчики источника. В методе EditiMouseDown обрабатывается нажатие левой кнопки мыши и включается механизм переноса. Так как свойство DragMode для Edit1 имеет значение dmManual, то компонент без проблем обеспечивает получение фокуса и редактирование текста. Метод EditiEndDrag обеспечивает отображение информации о выполнении переноса в источнике. Для компонента Edit2 определены методы-обработчики приемника. Метод Edit2DragOver проверяет класс источника и разрешает или запрещает прием. Метод Edit2DragDrop осуществляет перенос текста из источника в приемник. Примечание Обратите внимание, что оба компонента TEdit одновременно являются источниками и приемниками. Для этого каждый из них использует методы-обработчики другого. А исходный код методов настроен на обработку владельца как экземпляра класса TEdit. Форма, как приемник Drag-and-Drop, обеспечивает перемещение панели Panel2, которая выступает в роли источника. Метод FormDragOver запрещает прием любых компонентов, кроме панелей. Метод FormDragDrop осуществляет перемещение компонента.

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

Drag and Drop для TListBox на примере двойного списка.

Автор: Александр Малыгин Специально для Королевства Delphi Типичная задача перетаскивания мышью объектов из одного контрола в другой просто решается обработкой событий OnDragOver и OnDragDrop, при установленных свойствах DragMode := dmAutomatic и DragKind := dkDrag у всех участвующих компонентов. Первый обработчик предназначен для принятия решения - допускается ли сбросить объект в контрол, над которым находится мышь (параметр Sender), и выставить соответствующий курсор. Для этого передается параметр Source:TObject, представляющий собой тот компонент, с которого начали перетаскивание (источник), координаты курсора X,Y:integer, состояние процесса перетаскивания State:TDragState, и результат, который надо вернуть var Accept:boolean. Второй обработчик вызывается, когда пользователь отпускает кнопку мыши, и контрол Sender:TObject должен отработать прием переданной ему "посылки" Source:TObject, по координатам X,Y : integer. Предлагаемый пример DualListFrame реализует операции drag'n'drop над двумя списками TListBox, организованными в один фрейм с органами управления. Прототипом послужил стандартный шаблон-форма "Dual list box" из репозитория Delphi и аналогичный компонент-диалог из библиотеки RxLib. Первый не снабжен функциями drag'n'drop, а второй хоть и умеет это, но все равно является отдельным диалогом, что не всегда удобно. Данный пример благодаря технологии фреймов позволяет встроить двойной список "со всеми причиндалами" в любую форму проекта. Чтобы им воспользоваться, достаточно подключить модуль DualListFrame.pas к проекту. Для удобства при многократном использовании (в разных проектах) можно добавить его в репозиторий, скопировав предварительно в общую для шаблонов директорию (например, "Delphi5\ObjRepos"). В архиве вместе с кодом и ресурсом фрейма имеется пиктограмма для репозитория и вырезка из файла "Delphi5\Bin\delphi32.dro".

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

procedure TfrDualList.ListDragOver(Sender, Source: TObject; X,
  Y: Integer; State: TDragState; var Accept: Boolean);
var
  DragIndex: integer;
begin
  // из другого листбокса принимаем всегда
  if Source <> Sender then
    Accept := true
      // а если это мы сами - надо проверить возможность изменения порядка
    // в списке и позицию сбрасывания
  else
    with (Sender as TListBox) do
    begin
      Accept := False;
      if not Sorted and ((SelCount = 1) or (not MultiSelect)) then
      begin
        DragIndex := ItemAtPos(Point(X, Y), True);
        if (DragIndex >= 0) and (DragIndex <> ItemIndex) then
          Accept := True; // попали внутрь видимого списка
      end;
    end;
  // установка изображения курсора
  if State = dsDragLeave then
    (Source as TListBox).DragCursor := crDrag;
  if (State = dsDragEnter) and ((Source as TListBox).SelCount > 1) then
    (Source as TListBox).DragCursor := crMultiDrag;
end;
procedure TfrDualList.ListDragDrop(Sender, Source: TObject; X, Y: Integer);
begin
  if Source <> Sender then // перемещаем элементы из другого листбокса
    MoveItems(TListBox(Source), TListBox(Sender))
  else
    BoxMoveSel(TListBox(Sender), // перемещаем элемент внутри списка
      TListBox(Sender).ItemAtPos(Point(X, Y), True));
end;

Drag and Drop из RichEdit.

Разговор двух программистов: - Ты слышал, Костя умер. - А он записался?!!
 var
   Form1: TForm1;
   richcopy: string;
   transfering: boolean;
 implementation
 {$R *.DFM\}
 procedure TForm1.RichEdit1MouseDown(Sender: TObject; Button: TMouseButton;
   Shift: TShiftState; X, Y: Integer);
 begin
  if length(richedit1.seltext)>0 then begin
   richcopy:=richedit1.seltext;
   transfering:=true;
  end; //seltext
 end;
 procedure TForm1.ListBox1MouseMove(Sender: TObject; Shift: TShiftState; X,
   Y: Integer);
 begin
  if transfering then begin
   transfering:=false;
   listbox1.items.add(richcopy);
  end; //transfering
 end;

Drag and Drop TImage.

Вот рабочий пример. Расположите на форме панель побольше, скопируйте и измените приведенный код так, чтобы изображение загружалось из ВАШЕГО каталога Delphi.

procedure TForm1.Panel1DragDrop(Sender, Source: TObject; X, Y: Integer);
begin
  with Source as TImage do
  begin
    Left := X;
    Top := Y;
  end;
end;
procedure TForm1.Panel1DragOver(Sender, Source: TObject; X, Y: Integer;
  State: TDragState; var Accept: Boolean);
begin
  Accept := Source is TImage;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
  with TImage.Create(Self) do
  begin
    Parent := Panel1;
    AutoSize := True;
    Picture.LoadFromFile('D:\DELPHI\IMAGES\CHIP.BMP');
    DragMode := dmAutomatic;
    OnDragOver := Panel1DragOver;
    OnDragDrop := Panel1DragDrop;
  end;
end;

Drag and Drop несколько элементов в TListView.

{ ListView1.DragMode := dmAutomatic } 
procedure TForm1.ListView1DragDrop(Sender, Source: TObject; X, Y: Integer); 
var 
  DragItem, DropItem, CurrentItem, NextItem: TListItem; 
begin 
  if Sender = Source then 
    with TListView(Sender) do 
    begin 
      DropItem    := GetItemAt(X, Y); 
      CurrentItem := Selected; 
      while CurrentItem <> nil do 
      begin 
        NextItem := GetNextItem(CurrentItem, SdAll, [IsSelected]); 
        if DropItem = nil then DragItem := Items.Add 
        else 
          DragItem := Items.Insert(DropItem.Index); 
        DragItem.Assign(CurrentItem); 
        CurrentItem.Free; 
        CurrentItem := NextItem; 
      end; 
    end; 
end; 
procedure TForm1.ListView1DragOver(Sender, Source: TObject; X, Y: Integer; 
  State: TDragState; 
  var Accept: Boolean); 
begin 
  Accept := Sender = ListView1; 
end;

Пример Drag and Drop между двумя DBGrid.

Данный пример компонента и демонстрационный проект показывают простой путь осуществления операции "drag and drop" (перетащи и брось) между двумя полями различных табличных сеток. Запустите Delphi 3 (с незначительными изменениями данный код может работать и в Delphi 1-2). Активизируйте File|New|Unit. Скопируйте приведенный ниже модуль MyDBGrid во вновь созданный модуль. Сделайте File|Save As. Сохраните модуль как MyDBGrid.pas. Выберите пункт меню Component|Install Component. Переключитесь на страницу Info New Package. Поместите MyDBGrid.pas в поле редактирования "Unit file name" (имя файла модуля). Назовите модуль MyPackage.dpk. Ответьте Yes на вопрос Delphi 3 о необходимости сборки и установки пакета. Нажмите OK на сообщение Delphi 3 о необходимости включения VCL30.DPL. После этого пакет будет собран и установлен. Теперь компонент TMyDBGrid будет отображен в Палитре Компонентов в группе "Samples". Закройте редактор пакетов и сохраните пакет. Выберите пункт меню File|New Application. Щелкните правой кнопкой мыши на форме (Form1) и выберите View As Text. Скопируйте приведенный ниже исходный код формы GridU1 в Form1. Щелкните правой кнопкой мыши на форме и выберите View As Form. Убедитесь в активности ваших таблиц. Скопируйте расположенный ниже модуль GridU1 в ваш модуль Unit1. Выберите пункт меню File|Save Project As. Сохраните модуль как GridU1.pas. Сохраните проект как GridProj.dpr. Теперь запустите проект и наслаждайтесь функцией Drag and Drop между двумя табличными сетками.
// Модуль MyDBGrid
unit MyDBGrid;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
  Dialogs, Grids, DBGrids;
type
  TMyDBGrid = class(TDBGrid)
  private
    { Private declarations }
    FOnMouseDown: TMouseEvent;
  protected
    { Protected declarations }
    procedure MouseDown(Button: TMouseButton; Shift: TShiftState;
      X, Y: Integer); override;
  published
    { Published declarations }
    property Row;
    property OnMouseDown read FOnMouseDown write FOnMouseDown;
  end;
procedure Register;
implementation
procedure TMyDBGrid.MouseDown(Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
  if Assigned(FOnMouseDown) then
    FOnMouseDown(Self, Button, Shift, X, Y);
  inherited MouseDown(Button, Shift, X, Y);
end;
procedure Register;
begin
  RegisterComponents('Samples', [TMyDBGrid]);
end;
end.
// Модуль GridU1
unit GridU1;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
  Dialogs, Db, DBTables, Grids, DBGrids, MyDBGrid, StdCtrls;
type
  TForm1 = class(TForm)
    MyDBGrid1: TMyDBGrid;
    Table1: TTable;
    DataSource1: TDataSource;
    Table2: TTable;
    DataSource2: TDataSource;
    MyDBGrid2: TMyDBGrid;
    procedure MyDBGrid1MouseDown(Sender: TObject;
      Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
    procedure MyDBGrid1DragOver(Sender, Source: TObject;
      X, Y: Integer; State: TDragState; var Accept: Boolean);
    procedure MyDBGrid1DragDrop(Sender, Source: TObject;
      X, Y: Integer);
  private
    { Private declarations }
  public
    { Public declarations }
  end;
var
  Form1: TForm1;
implementation
{$R *.DFM}
var
  SGC: TGridCoord;
procedure TForm1.MyDBGrid1MouseDown(Sender: TObject;
  Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
var
  DG: TMyDBGrid;
begin
  DG := Sender as TMyDBGrid;
  SGC := DG.MouseCoord(X, Y);
  if (SGC.X > 0) and (SGC.Y > 0) then
    (Sender as TMyDBGrid).BeginDrag(False);
end;
procedure TForm1.MyDBGrid1DragOver(Sender, Source: TObject;
  X, Y: Integer; State: TDragState; var Accept: Boolean);
var
  GC: TGridCoord;
begin
  GC := (Sender as TMyDBGrid).MouseCoord(X, Y);
  Accept := Source is TMyDBGrid and (GC.X > 0) and (GC.Y > 0);
end;
procedure TForm1.MyDBGrid1DragDrop(Sender, Source: TObject;
  X, Y: Integer);
var
  DG: TMyDBGrid;
  GC: TGridCoord;
  CurRow: Integer;
begin
  DG := Sender as TMyDBGrid;
  GC := DG.MouseCoord(X, Y);
  with DG.DataSource.DataSet do
  begin
    with (Source as TMyDBGrid).DataSource.DataSet do
      Caption := 'Вы перетащили "' + Fields[SGC.X - 1].AsString + '"';
    DisableControls;
    CurRow := DG.Row;
    MoveBy(GC.Y - CurRow);
    Caption := Caption + ' в "' + Fields[GC.X - 1].AsString + '"';
    MoveBy(CurRow - GC.Y);
    EnableControls;
  end;
end;
end.
// Форма GridU1
object Form1: TForm1
  Left = 200
    Top = 108
    Width = 544
    Height = 437
    Caption = 'Form1'
    Font.Charset = DEFAULT_CHARSET
    Font.Color = clWindowText
    Font.Height = -11
    Font.Name = 'MS Sans Serif'
    Font.Style = []
    PixelsPerInch = 96
    TextHeight = 13
    object MyDBGrid1: TMyDBGrid
    Left = 8
      Top = 8
      Width = 521
      Height = 193
      DataSource = DataSource1
      Row = 1
      TabOrder = 0
      TitleFont.Charset = DEFAULT_CHARSET
      TitleFont.Color = clWindowText
      TitleFont.Height = -11
      TitleFont.Name = 'MS Sans Serif'
      TitleFont.Style = []
      OnDragDrop = MyDBGrid1DragDrop
      OnDragOver = MyDBGrid1DragOver
      OnMouseDown = MyDBGrid1MouseDown
  end
  object MyDBGrid2: TMyDBGrid
    Left = 7
      Top = 208
      Width = 521
      Height = 193
      DataSource = DataSource2
      Row = 1
      TabOrder = 1
      TitleFont.Charset = DEFAULT_CHARSET
      TitleFont.Color = clWindowText
      TitleFont.Height = -11
      TitleFont.Name = 'MS Sans Serif'
      TitleFont.Style = []
      OnDragDrop = MyDBGrid1DragDrop
      OnDragOver = MyDBGrid1DragOver
      OnMouseDown = MyDBGrid1MouseDown
  end
  object Table1: TTable
    Active = True
      DatabaseName = 'DBDEMOS'
      TableName = 'ORDERS'
      Left = 104
      Top = 48
  end
  object DataSource1: TDataSource
    DataSet = Table1
      Left = 136
      Top = 48
  end
  object Table2: TTable
    Active = True
      DatabaseName = 'DBDEMOS'
      TableName = 'CUSTOMER'
      Left = 104
      Top = 240
  end
  object DataSource2: TDataSource
    DataSet = Table2
      Left = 136
      Top = 240
  end
end

Drag and Drop - как использовать ItemAtPos для получения элемента DirListBox.

Просто сохраните результат функции ItematPos в переменной формы, и затем используйте эту переменную в обработчике ListBoxDragDrop. Пример:

FDragItem := ItematPos(X, Y, True);
if FDragItem >= 0 then
  BeginDrag(false);
...
procedure TForm1.ListBoxDragDrop(Sender, Source: TObject; X, Y: Integer);
begin
  if Source is TDirectoryListBox then
    ListBox.Items.Add(TDirectoryListBox(Source).GetItemPath(FDragItem));
end;

Drag and Drop со списками.

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

{ 
 This example shows how to drag&drop within a TListBox. 
 The Demo Program also shows how to implement an autoscroll-feature. 
 Dieses Beispiel zeigt, wie man drag & drop in einer TListBox realisiert. 
 Im Demo-Programm wird auch eine Autoscroll-Funktion gezeigt. 
}
 procedure TForm1.ListBox1DragOver(Sender, Source: TObject; X, Y: Integer;
   State: TDragState; var Accept: Boolean);
 begin
   Accept := Sender is TListBox;
 end;
 procedure TForm1.ListBox1DragDrop(Sender, Source: TObject; X, Y: Integer);
 var
   iTemp: Integer;
   ptTemp: TPoint;
   szTemp: string;
 begin
   { change the x,y coordinates into a TPoint record }
   ptTemp.x := x;
   ptTemp.y := y;
   { Use a while loop instead of a for loop due to items possible being removed 
   from listboxes this prevents an out of bounds exception }
   iTemp := 0;
    while iTemp <= TListBox(Source).Items.Count-1 do
   begin
     { look for the selected items as these are the ones we wish to move }
     if TListBox(Source).selected[iTemp] then
     begin
       { use a with as to make code easier to read }
       with Sender as TListBox do
       begin
       { need to use a temporary variable as when the item is deleted the 
        indexing will change }
         szTemp := TListBox(Source).Items[iTemp];
         { delete the item that is being dragged  }
         TListBox(Source).Items.Delete(iTemp);
       { insert the item into the correct position in the listbox that it 
       was dropped on }
         Items.Insert(itemAtPos(ptTemp, True), szTemp);
       end;
     end;
     Inc(iTemp);
   end;
 end;

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

Автор: Nomadic

Развлекался когда-то - вот, осталось:

unit Unit1;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  ShellAPI, Grids, StdCtrls;
type
  TForm1 = class(TForm)
    lb: TListBox;
    Memo1: TMemo;
    Button1: TButton;
    Button2: TButton;
    procedure FormCreate(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    procedure WMDropFiles(var M: TMessage); message WM_DROPFILES;
    { Private declarations }
  public
    { Public declarations }
  end;
var
  Form1: TForm1;
implementation
var
  CountFiles: integer;
  SizeName: integer;
  cch: integer;
var
  hDrop: integer;
  Point: TPoint;
  lpszFile: PChar;
{$R *.DFM}
procedure TForm1.WMDropFiles(var M: TMessage);
var
  i: integer;
begin
  hDrop := M.WParam;
  DragQueryPoint(hDrop, Point);
  CountFiles := DragQueryFile(hDrop, $FFFFFFFF, nil, cch);
  for i := 0 to CountFiles - 1 do
  begin
    SizeName := DragQueryFile(hDrop, i, nil, cch);
    GetMem(lpszFile, SizeName + 1);
    DragQueryFile(hDrop, i, lpszFile, SizeName + 1);
    lb.Items.Add(lpszFile);
    FreeMem(lpszFile, SizeName + 1);
  end;
  DragFinish(hDrop);
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
  DragAcceptFiles(Handle, True);
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
  lb.Items.Clear;
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
  ShellAbout(Handle, 'Anton Saburov', 'APSystems', 0);
end;
end.

Перемещение из DbGrid.

Что говорят мыши: Мышь квакера: «Слушай, кореш, давай ещё разок колесо, я кайф недоловила» Мышь фотошопера: “Так, вот в эту точку? Сейчас, блин, с тобой косоглазой станешь, да, вижу, вижу, что опять не то выделила? Да иди ты на хр@н, это у тебя руки дрожат!” Мышь програмиста: “Я, это, посплю децл, ок?” Мышь любителя DОSа: “It’s no fate”
Кто-нибудь пробовал перемещать что-либо из DbGrid методом перетащи и брось (drag and drop)? Вы сами можете создать потомка TDBGrid (или TDBCustomGrid) и добавить необходимую функциональность для достижения цели.

Скопируйте код из данного "Совета", сохраните его с именем DBGrid.pas и установите компонент в палитру. У Вас появится новый компонент EDBGrid с двумя новыми событиями: OnMouseDown и OnMouseUp. Я не считаю эту информацию конфиденциальной: это ошибка разработчиков Delphi! На самом деле эти два события должны быть частью компонента DBGrid.

unit Dbgrid;
interface
uses
  DBGrids, Controls, Classes;
type
  TEDBGrid = class(TDBGrid)
  private
    FOnMouseDown: TMouseEvent;
    FOnMouseUp: TMouseEvent;
  protected
    procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y:
      Integer); override;
    procedure MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y:
      Integer); override;
  published
    property OnMouseDown: TMouseEvent read FOnMouseDown write
      FOnMouseDown;
    property OnMouseUp: TMouseEvent read FOnMouseUp write FOnMouseUp;
  end;
procedure Register;
implementation
procedure Register;
begin
  RegisterComponents('Data Controls', [TEDBGrid]);
end;
procedure TEDBGrid.MouseDown(Button: TMouseButton; Shift: TShiftState;
  X, Y: Integer);
begin
  if Assigned(FOnMouseDown) then
    FOnMouseDown(Self, Button, Shift, X, Y);
  inherited MouseDown(Button, Shift, X, Y);
end;
procedure TEDBGrid.MouseUp(Button: TMouseButton; Shift: TShiftState; X,
  Y: Integer);
begin
  if Assigned(FOnMouseUp) then
    FOnMouseUp(Self, Button, Shift, X, Y);
  inherited MouseUp(Button, Shift, X, Y);
end;
end.

Drag and Drop c Win95 Explorer.

Крошка сын к отцу пришел И сказала кроха: Navigator - хорошо, а Explorer - плохо!
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
  Dialogs,
  ComCtrls;
type
  TForm1 = class(TForm)
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
    procedure FileIsDropped(var Msg: TMessage); message WM_DropFiles;
  public
    { Public declarations }
  end;
var
  Form1: TForm1;
implementation
uses
  shellapi;
{$R *.DFM}
procedure TForm1.FormCreate(Sender: TObject);
begin
  DragAcceptFiles(Handle, True);
end;
procedure TForm1.FileIsDropped(var Msg: TMessage);
var
  hDrop: THandle;
  fName: array[0..254] of CHAR;
  NumberOfFiles: INTEGER;
  fCounter: INTEGER;
  Names: string;
begin
  hDrop := Msg.WParam;
  NumberOfFiles := DragQueryFile(hDrop, -1, fName, 254);
  Names := '';
  for fCounter := 1 to NumberOfFiles do
  begin
    DragQueryFile(hDrop, fCounter, fName, 254);
    // Здесь вы получаете один к одному имя вашего файла
    Names := Names + #13#10 + fName;
  end;
  ShowMessage('Бросаем ' + IntToStr(NumberOfFiles) + ' файла(ов) : ' + Names);
  DragFinish(hDrop);
end;
end.

Drag and Drop c Win95 Explorer 2.

Автор: Aleksey.

{Так можно заставить окно принимать файлы, перетаскиваемые из проводника}
{ОБЯЗАТЕЛЬНО ПОМЕСТИТЕ В СЕКЦИЮ PRIVATE СТРОКИ
procedure CreateParams(var Params: TCreateParams); override;
procedure WMDropFiles(var Message: TWMDropFiles); message WM_DROPFILES;
и не забудьте - ShellAPI поместить в uses}
procedure TForm1.CreateParams(var Params: TCreateParams);
begin
  inherited
    CreateParams(Params);
  {сделаем окно способным принимать файлы}
  Params.ExStyle := Params.ExStyle or WS_EX_ACCEPTFILES;
end;
procedure TForm1.WMDropFiles(var Message: TWMDropFiles);
var
  aFile: array[0..255] of Char;
  //FilesCount : Integer;
begin
  inherited;
  {так можно узнать сколько файлов перетягивается}
  // FilesCount := DragQueryFile(Message.drop, $FFFFFFFF, nil, 0);
  begin
    {здесь можно поставить цикл открытия всех перетаскиваемых файлов
    for N := 0 to FilesCount - 1 do DragQueryFile(Message.drop, N, aFile, 256);
    а в данном случае открывается только первый файл в списке}
    DragQueryFile(Message.drop, 0, aFile, 256);
    Memo1.Lines.LoadFromFile(aFile);
  end;
  DragFinish(Message.Drop);
end;
procedure TForm1.FormCreate(Sender: TObject); {Form1.OnCreate}
begin
  {сделаем окно неравнодушным к пролетающим над ним файлам}
  DragAcceptFiles(Handle, True);
end;

Как перетаскивать (Drag and Drop) выделенный текст между компонентами Memo.

Данный способ позволяет не погружаясь глубоко в создание компонент осуществить операцию "drag and drop" выделенного текста.

Создайте новый компонент (TMyMemo), наследовав его от TMemo. И объявите его следующим образом:

type
  TMyMemo = class(TMemo)
  private
    FLastSelStart : Integer;
    FLastSelLength : Integer;
    procedure WMLButtonDown(var message: TWMLButtonDown); message WM_LBUTTONDOWN;
  published
    property LastSelStart : Integer read FLastSelStart write FLastSelStart;
    property LastSelLength : Integer read FLastSelLength write FLastSelLength;
end;

Добавьте обработчик WMLButtonDown:

procedure TMyMemo.WMLButtonDown(var message: TWMLButtonDown);
var
  Ch: Integer;
begin
  if SelLength > 0 then
  begin
    Ch := LoWord(Perform(EM_CHARFROMPOS, 0,
    MakeLParam(message.XPos, message.YPos)));
    LastSelStart := SelStart;
    LastSelLength := SelLength;
    if (Ch >= SelStart) and (Ch then
      BeginDrag(True)
    else
      inherited;
  end
  else
    inherited;
end;

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

procedure TForm1.MyMemo1DragOver(Sender, Source: TObject; X, Y: Integer;
State: TDragState; var Accept: Boolean);
begin
  Accept := Source is TMyMemo;
end;

Так же для них необходимо сделать обработчики событий OnDragDrop:

procedure TForm1.MyMemo1DragDrop(Sender, Source: TObject;
X, Y: Integer);
var
  Dst, Src : TMyMemo;
  Ch : Integer;
  Temp : string;
begin
  Dst := Sender as TMyMemo;
  Src := Source as TMyMemo;
  Ch := LoWord(Dst.Perform(EM_CHARFROMPOS,0,MakeLParam(X,Y)));
  if (Src = Dst) and (Ch >= Src.LastSelStart) and
  (Ch then
    Exit;
  Dst.Text := Copy(Dst.Text,1,Ch)+Src.SelText+
  Copy(Dst.Text,Ch+1,Length(Dst.Text)-Ch);
  Temp := Src.Text;
  Delete(Temp,Src.LastSelStart+1,Src.LastSelLength);
  Src.Text := Temp;
end;

Запустите приложение, поместите в поля memo какой-нибудь текст, и посмотрите что произойдёт, если перетащить текст между полями.

Drag and Drop между двумя компонентами ListBox.

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

Вот пересмотренный OnDragDrop, использующий source (источник) и sender (передатчик) вместо DstList и SrcList. Теперь, если вы установили SrcList и DstList для использования тех же методов OnDragOver и OnDragDrop и создали обработчик события OnDragDrop, то для операции Drag and Drop вы можете использовать оба решения.

procedure TDualListDlg.DstListDragDrop(Sender, Source: TObject; X,
  Y: Integer);
var
  droppedOnIndex: integer;
  anItem: integer;
  numberOfItems: integer;
begin
  if (Sender is TListbox) and (Source is TListBox) then
  begin
    droppedOnIndex := TListBox(Sender).ItemAtPos(Point(X, Y), false);
    numberOfItems := TListBox(Source).SelCount;
    anItem := 0;
    while numberOfItems > 0 do
    begin
      if TListBox(Source).Selected[anItem] = true then
      begin
        TListBox(Sender).Items.Insert(droppedOnIndex,
          TListBox(Source).Items[anItem]);
        TListBox(Source).Items.Delete(anItem);
        TListBox(Source).Update;
        TListBox(Sender).Update;
        numberOfItems := numberOfItems - 1;
      end
      else
        anItem := anItem + 1;
    end;
  end;
end;

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

if (Sender is TListBox) and (Source is TListBox) then
begin
  if TListBox(Sender).Name = TListBox(Source).Name then
    Accept := False
  else
    Accept := true;
end;

Перетаскивание элементов управления c рамкой контура.

Автор: NEil ...как перетаскивать элементы управления с контурной рамкой по их форме, "приклеенной" к курсору? Решение, найденное вами, работать не будет, поскольку таскаемая рамка не обязательно может находиться в пределах области компонента (а вы отрисовываете ее только на компоненте). В общих чертах, вы должны рисовать на всей поверхности формы и даже рабочего стола, для чего необходимо сделать растровую КОПИЮ окна или десктопа и рисовать на ней. Вот что нам нужно.

Начните со свеженькой формы. Бросьте на нее компонент Notebook и установите его свойство Align в alClient. Разработайте форму на первой странице компонента Notebook. Создайте вторую страницу в Notebook, поместите туда Paintbox и установите его свойство Align в alClient. Далее добавьте нижеследующие строчки в секцию Private вашей формы:

Img : TBitmap;
DragX, DragY, DragW, DragH, XOff, YOff : Integer;

В обработчике формы OnCreate:

Img := TBitmap.Create;

В общем, для всех перетаскиваемых компонентов, обработчике события OnMouseDown:

IF NOT (ssShift IN Shift) THEN Exit;
Img := GetFormImage;
Notebook1.PageIndex := 1;
WITH Sender AS TControl DO
BEGIN
DragW := Width;
DragH := Height;
XOff:= X;
YOff := Y;
BeginDrag(True);
END;

В общем, для всех перетаскиваемых компонентов, обработчике события EndDrag:

Notebook1.PageIndex := 0;
WITH Sender AS Tcontrol DO
BEGIN
Left := X-Xoff;
Top := Y-YOff;
END;

Поместите следующую строку в обработчик события OnPaint компонента PaintBox:

PaintBox1.Canvas.Draw(0, 0, Img);

И наконец, если вам еще это не надоело, поместите следующую строчку в обработчик OnDragOver компонента PaintBox:

IF (X=DragX) AND (Y=DragY) THEN Exit;
WITH PaintBox1.Canvas DO
BEGIN
DrawFocusRect(Bounds(DragX-XOff, DragY-YOff, DragW, DragH);
DragX := X; DragY := Y;
DrawFocusRect(Bounds(DragX-XOff, DragY-YOff, DragW, DragH);
END;
ФУ!! Но это работает! Я не хотел убирать в компонентах возможность перетаскивания их мышью обычным способом, поэтому для включения дополнительной характеристики необходимо при старте держать нажатой клавишу Shift. Попробуйте это! Я пытаюсь "потаскать" TPanel, используемую в качестве ToolBar и всегда почему-то получаю иконку с перечеркнутым кругом. Я понимаю, что это означает невозможность перетаскивания. К сожалению, в документации я ничего не нашел как решить эту проблему. Я пробовал и ручные, и автоматические настройки (DragMode = dmManual/dmAutomatic - В.О.), но все без толку. Иногда я вообще не могу "оторвать" TPanel!

Начнем с самого начала. Причина того, что вы получаете курсор "crNoDrop" в том, что под курсором элемент управления не готов принять перетаскиваемый компонент. Чтобы исправить эту ситуацию, дважды щелкните (в Инспекторе Объектов) на событии формы или компонента OnDragOver и установите параметр Accept в, например так:

procedure TForm1.FormDragOver(Sender, Source: TObject; X, Y: Integer;
State: TDragState; var Accept: Boolean);
begin
  Accept := true ;
end;
Благодарю за пример создания прямоугольника при перетаскивании. Ваши инструкции помогли мне первое время и я легко интегрировал ваш код в мое приложение. Но если вы не возражаете, я хотел бы получить другой небольшой совет .... есть ли возможность во время операции перетаскивания (PaintBox1DragOver) работать с элементами управления, находящимимя под PainBox с тем, чтобы они также изменяли курсор и также могли бы принимать перетаскиваемый элемент? Когда перетаскиваемый элемент выдает сообщение EndDrag, параметр Target должен быть PaintBox (логически). Можно как-то определить, с каким конкретно элементом управления, расположенным под PainBox, взаимодействует в данный момент перетаскиваемый элемент (для его акцептования)? Я опять что-то упустил, но я не знаю как это сделать.

Вы можете получить координаты в методе OnDragOver при сравнении BoundsRect с областью компонентов. Например, вы не хотите принимать перетаскиваемый компонент кнопкой, перекрывающей любую другую имеющуюся кнопку. В обработчике OnDragOver напишите примерно следующее:

FOR N := 0 TO ComponentCount-1 DO
  IF COmponents[N] IS TButton THEN
    IF IntersectRect(DummyRect, TControl(Components[N]).BoundsRect,
      (Bounds(X-XOff, Y-YOff, DragW, DragH)) >0 THEN
        Accept := False;

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

Drag and Drop с минимизированным приложением.

Автор: Neil J. Rubenking В ситуации, когда ваше приложение минимизировано, необходимо понимать, что окно главной формы НЕ работает. Фактически, если вы проверяете окно главной формы, и обнаруживаете, что оно имеет прежний размер, не удивляйтесь, оно просто невидимо. Иконка минимизированного Delphi-приложения принадлежит объекту Application, чей дескриптор окна - Application.Handle.

Вот некоторый код из моей программы, который с помощью компонента CheckBox проверяет возможность принятия перетаскиваемых файлов минимизированным приложением:

procedure TForm1.WMDropFiles(var Msg: TWMDropFiles);
{Вызывается только если TApplication НЕ получает drag/drop}
begin
  RecordDragDrop(Msg.Drop, False); {внутренняя функция}
  Msg.Result := 0;
end;
procedure TForm1.AppOnMessage(var Msg: TMsg; var Handled: Boolean);
{когда активно, получаем сообщения WM_DROPFILES, посылаемые
форме ИЛИ минимизированному приложению}
begin
  if Msg.message = WM_DROPFILES then
  begin
    RecordDragDrop(Msg.wParam, Msg.hWnd = Application.Handle);
    Handled := True;
  end;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
  DragAcceptFiles(Handle, True);
  DragAcceptFiles(Application.Handle, False);
  Application.OnMessage := nil;
end;

OK? Первоначально вызов DragAcceptFiles работает с дескриптором главной формы...

Рисование кривых по заданным точкам.

Автор: Andrus

Здесь я использую процедуру рисования кривой Безье между двумя точками. Можно задать кривизну кривой(20-35 лучше всего). Можно задать число отрезков между соседними точками, а если в процедуре DrawSlice убрать коментарий со строки.

//  num_slices:=trunc(sqrt(sqr(p1.x-p2.x)+sqr(p1.y-p2.y)));

То число отрезков между соседними точками будет расчитываться автоматически, исходя из растояния между ними. Если потребуются дополнительные коментарии, пишите по адресу andrus78@mail.ru.

unit u_bezier;
interface
uses Windows, Graphics, SysUtils;
type TArrayPoint = array of TPoint; //массив точек
const num_slices: integer = 20; //число отрезков между двумя точками
  krivizna: integer = 30; //кривизна кривой (длина плеча направляющей)
procedure DrawBezier(acanv: TCanvas; var ArrPoint: TArrayPoint);
/////////////////////////////////////////////////////////////////////
implementation
uses unit1;
type
  TBezierPoint = record //точка Безье
    x, y: integer; //основной узел
    xl, yl, //левая контрольная точка
      xr, yr: single; //правая контрольная точка
  end;
  TArrayBezierPoint = array of TBezierPoint; //массив точек Безье
const grad_to_rad = pi / 180; //перевод градусов в радианы
  rad_to_grad = 180 / pi; //перевод радиан в градусы
  rad_90 = 90 * grad_to_rad; //90 градусов в радианах
  rad_180 = 180 * grad_to_rad; //180 градусов в радианах
  rad_270 = 270 * grad_to_rad; //270 градусов в радианах
  rad_360 = 360 * grad_to_rad; //360 градусов в радианах
var Canvas: TCanvas; //рабочий холст, на котором происходит рисование
//определить угол в радианах между точкой и положительным направлением оси х
function GetAngle(dx, dy: single): single;
begin
  if dx = 0 then begin
    if dy = 0 then Result := 0
    else if dy < 0 then Result := rad_270
    else Result := rad_90;
    exit
  end;
  Result := arctan(abs(dy) / abs(dx));
  if dy < 0 then
    if dx < 0 then Result := rad_180 + Result
    else Result := rad_360 - Result
  else
    if dx < 0 then Result := rad_180 - Result
end;
//определить направляющие линии к точке p
procedure GetCooPerpendikular(a, o, b: TPoint; var p: TBezierPoint);
var alfa, beta, gamma, dx, dy, angle_napr: single;
  l1, l2: single;
begin
  dx := a.x - o.x; dy := a.y - o.y;
  alfa := GetAngle(dx, dy);
  l1 := sqrt(dx * dx + dy * dy) * (krivizna / 100); //растояние oa
  dx := b.x - o.x; dy := b.y - o.y;
  beta := GetAngle(dx, dy);
  l2 := sqrt(dx * dx + dy * dy) * (krivizna / 100); //растояние ob
  gamma := (alfa + beta) / 2; //биссектриса угла aob
  if alfa > beta then angle_napr := gamma + rad_90
  else angle_napr := gamma - rad_90;
  p.xl := o.x + l1 * cos(angle_napr);
  p.yl := o.y + l1 * sin(angle_napr);
  p.xr := o.x + l2 * cos(angle_napr + rad_180);
  p.yr := o.y + l2 * sin(angle_napr + rad_180)
end;
//вычислить координаты точки, лежащей на участке кривой между
//двумя точками Безье в пределах от 0 до 1
procedure BezierValue(P1, P2: TBezierPoint; t: single; var X, Y: integer);
var t_sq, t_cb, r1, r2, r3, r4: single;
begin
  t_sq := t * t;
  t_cb := t * t_sq;
  r1 := (1 - 3 * t + 3 * t_sq - t_cb);
  r2 := (3 * t - 6 * t_sq + 3 * t_cb);
  r3 := (3 * t_sq - 3 * t_cb);
  r4 := (t_cb);
  X := round(r1 * p1.x + r2 * p1.xr + r3 * p2.xl + r4 * p2.x);
  Y := round(r1 * p1.y + r2 * p1.yr + r3 * p2.yl + r4 * p2.y)
end;
//рисуй участок кривой между двумя точками Безье
procedure DrawSlice(p1, p2: TBezierPoint);
var i: integer;
  x, y: integer;
  r1, r2: TRect;
begin
//  если убрать комментарий, то количество отрезков между соседними
//  точками будет расчитываться исходя из растояния между ними
//  num_slices:=trunc(sqrt(sqr(p1.x-p2.x)+sqr(p1.y-p2.y)));
  Canvas.MoveTo(p1.x, p1.y);
  for i := 1 to num_slices - 1 do begin
    BezierValue(p1, p2, i / num_slices, x, y);
    Canvas.LineTo(x, y)
  end;
  Canvas.LineTo(p2.x, p2.y)
end;
//рисуй кривую на холсте acanv по точкам массива ArrPoint
procedure DrawBezier(acanv: TCanvas; var ArrPoint: TArrayPoint);
var ArrBezPoint: TArrayBezierPoint;
  i, num_point: integer;
  a, o, b: TPoint;
begin
  Canvas := acanv;
  num_point := high(ArrPoint) + 1;
  SetLength(ArrBezPoint, num_point);
  for i := 0 to num_point - 1 do begin
    ArrBezPoint[i].x := ArrPoint[i].x;
    ArrBezPoint[i].y := ArrPoint[i].y;
  end;
  ArrBezPoint[0].xr := ArrPoint[0].x;
  ArrBezPoint[0].yr := ArrPoint[0].y;
  ArrBezPoint[0].xl := ArrPoint[0].x;
  ArrBezPoint[0].yl := ArrPoint[0].y;
  for i := 1 to num_point - 2 do begin
    a := ArrPoint[i - 1];
    o := ArrPoint[i];
    b := ArrPoint[i + 1];
    GetCooPerpendikular(a, o, b, ArrBezPoint[i])
  end;
  ArrBezPoint[num_point - 1].xr := ArrPoint[num_point - 1].x;
  ArrBezPoint[num_point - 1].yr := ArrPoint[num_point - 1].y;
  ArrBezPoint[num_point - 1].xl := ArrPoint[num_point - 1].x;
  ArrBezPoint[num_point - 1].yl := ArrPoint[num_point - 1].y;
  for i := 1 to num_point - 1 do
    DrawSlice(ArrBezPoint[i - 1], ArrBezPoint[i])
end;
end.
// *********************************** //
// использовать этот модуль можно так: //
// *********************************** //
procedure TForm1.Button2Click(Sender: TObject);
var ArrPoint: TArrayPoint;
begin
  SetLength(ArrPoint, 5);
  ArrPoint[0].x := random(200); ArrPoint[0].y := random(200);
  ArrPoint[1].x := random(200); ArrPoint[1].y := random(200);
  ArrPoint[2].x := random(200); ArrPoint[2].y := random(200);
  ArrPoint[3].x := random(200); ArrPoint[3].y := random(200);
  ArrPoint[4].x := random(200); ArrPoint[4].y := random(200);
  num_slices := 10;
  krivizna := 30;
  DrawBezier(Form1.Canvas, ArrPoint)
end;
// нужно не забыть включить модуль в список используемых:
// implementation
// uses u_bezier;

Рисование без мерцания.

Автор: Mike Scott ...вот я и удивляюсь - почему я получаю мерцание, если я вызываю Repaint или Refresh, а не метод OnPaint напрямую? Или это просто "вариация на тему"? Имеются две фазы обновления окна. В первой фазе, при выводе окна, Windows посылает ему сообщение WM_ERASEBKGND, сообщающее о необходимости стирания фона перед процедурой рисования. Затем посылается сообщение WM_PAINT, служащее сигналом для закрашивания "переднего плана".

Тем не менее, вы можете пропустить первую фазу, которая вызывает мерцание, одним из двух способов: первый способ заключается в том, что вы форсируете обновление сами, с помощью вызова функции Windows API InvalidateRect. На входе он получает дескриптор окна, указатель на закрашиваемую область - передаем NIL, если вы хотите отрисовать всю область окна - и третий параметр, сообщающий о необходимости очистки фона. Вот как раз последний параметр и должен содержать значение FALSE, если вы сами будете в методе Paint полностью отрисовывать всю область:

InvalidateRect( Handle, NIL, FALSE ) ;
Handle должен быть дескриптором формы или элемента управления.

Описав первый способ, я скажу, что существует другое подходящее решение - использовать функциональность VCL. Вы можете указать VCL не стирать фон, добавляя [ csOpaque ] к значению свойства ControlStyle, как показано ниже:

ControlStyle := ControlStyle + [ csOpaque ] ;
Это ограничивает заполнение заднего фона, но вы все еще можете видеть процесс "наполнения" области изображением, т.е. процесс рисования. В этом случае вы можете отделаться от эффекта мельтешения, рисуя на TBitmap и выводя его затем на экран командой CopyRect.

Если вы хотите углубиться в тему дальше, то я отошлю вас к моей статье "Optimizing Display Updates in Delphi" (Оптимизация обновления экрана в Delphi), опубликованной в первом выпуске журнала "Delphi magazine".

Как вывести на Canvas надпись под углом.

Статьи по Delphi (006)…
{Create a rotated font based on the font object F}
function CreateRotatedFont(F : TFont; Angle : Integer) : hFont;
var
  LF: TLogFont;
begin
  FillChar(LF, SizeOf(LF), #0);
  with LF do
  begin
    lfHeight := F.Height;
    lfWidth := 0;
    lfEscapement := Angle*10;
    lfOrientation := 0;
    if fsBold in F.Style then
      lfWeight := FW_BOLD
    else
      lfWeight := FW_NORMAL;
    lfItalic := Byte(fsItalic in F.Style);
    lfUnderline := Byte(fsUnderline in F.Style);
    lfStrikeOut := Byte(fsStrikeOut in F.Style);
    lfCharSet := DEFAULT_CHARSET;
    StrPCopy(lfFaceName, F.name);
    lfQuality := DEFAULT_QUALITY;
    {everything else as default}
    lfOutPrecision := OUT_DEFAULT_PRECIS;
    lfClipPrecision := CLIP_DEFAULT_PRECIS;
    case F.Pitch of
      fpVariable: lfPitchAndFamily := VARIABLE_PITCH;
      fpFixed: lfPitchAndFamily := FIXED_PITCH;
      else
        lfPitchAndFamily := DEFAULT_PITCH;
    end;
  end;
  Result := CreateFontIndirect(LF);
end;
...
{create the rotated font}
if FontAngle <> 0 then
  Canvas.Font.Handle := CreateRotatedFont(Font, FontAngle);
...

Вращаются только векторные шрифты.

Отрисовка стрелки с заданными параметрами.

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

{ **** UBPFD *********** by delphibase.endimus.com ****
>> Отрисовка стрелки с заданными параметрами
// рисует стрелку с заданными параметрами
// X_Line, Y_Line - координаты "неподвижного" конца линии стрелки;
// Length_Line, Length_Arrow - длина линии и бокового ребра стрелки;
// Angle_Line, Angle_Arrow - углы между линией стрелки и горизонтальной
// осью и между линией стрелки и боковым ребром стрелки;
// IsAllTriangle - если True, то "подвижные" концы боковых рёбер стрелки
// соединяются отрезком прямой(и не рисуется высота равнобедренного треугольника стрелки);
// DrawSurface - поверхность на которой будет рисоваться стрелка
Зависимости: ничего неожиданного
Автор:       Александр
Copyright:   default
Дата:        14 февраля 2004 г.
***************************************************** }
procedure DrawArrow(X_Line, Y_Line, Length_Line, Length_Arrow: Integer;
  Angle_Line, Angle_Arrow: Extended;
  IsAllTriangle: Boolean; DrawSurface: TCanvas);
var
  XB, YB, XE, YE, XM, YM: Integer;
begin
  XB := Round(X_Line + Cos(Angle_Line) * Length_Line);
  YB := Round(Y_Line - Sin(Angle_Line) * Length_Line);
  XM := Round(XB - Cos(Angle_Arrow - Angle_Line) * Length_Arrow);
  YM := Round(YB - Sin(Angle_Arrow - Angle_Line) * Length_Arrow);
  XE := Round(XB + Sin(Angle_Arrow + Angle_Line - Pi / 2) * Length_Arrow);
  YE := Round(YB + Cos(Angle_Arrow + Angle_Line - Pi / 2) * Length_Arrow);
  DrawSurface.MoveTo(X_Line, Y_Line);
  if IsAllTriangle then
  begin
    DrawSurface.LineTo(Round((XM + XE) / 2), Round((YM + YE) / 2));
    DrawSurface.MoveTo(XB, YB);
  end
  else
    DrawSurface.LineTo(XB, YB);
  DrawSurface.LineTo(XM, YM);
  if IsAllTriangle then
    DrawSurface.LineTo(XE, YE)
  else
    DrawSurface.MoveTo(XE, YE);
  DrawSurface.LineTo(XB, YB)
end;

Как нарисовать кривую Безье.

Автор: Даниил Карапетян WEB сайт: http://program.dax.ru

Как нарисовать кривую Безье. Именно она применяется для построения гладких кривых во всех графических программах - от PaintBrush до CorelDraw и PhotoShop. Для задания кривой Безье n-ной степени (чем больше степень, тем более кривой может быть линия; кривая первой степени - отрезок) нужно указать n+1 точку. Первая и последняя точки будут началом и концом кривой, а остальные точки задаю ее поведение на других участках. В частности, первая и n-ая точки задают касательные и кривизну кривой на ее концах. В большинстве программ используются кривые Безье третьего порядка. Начиная с Delphi5 такую кривую можно нарисовать при помощи функции PolyBezier. Кривая Безье задается параметрически (x=x(t), y=y(t)). Это позволяет ей вести себя абсолютно произвольно. Если бы она задавалась, как y(x), она не смогла бы даже сделать поворот на 180 градусов. Функции x(t) и y(t) выглядят так: x(t)= Cn0 * t0 * (1-t)n * x0 + Cn1 * t1 * (1-t)n-1 * x1 + Cn2 * t2 * (1-t)n-2 * x2 + ... + Cnn * tn * (1-t)0 * xn y(t)= Cn0 * t0 * (1-t)n * y0 + Cn1 * t1 * (1-t)n-1 * y1 + Cn2 * t2 * (1-t)n-2 * y2 + ... + Cnn * tn * (1-t)0 * yn где n - порядок кривой, Cni - коэффициенты в разложении бинома Ньютона, t - параметр, меняющийся от 0 до 1, xi, yi - координаты опорных точек. Эта программа строит кривую Безье n-ного порядка. n задается в SpinEdit1. Все узлы можно перемещать по полю мышью. Для создания нового узла нужно нажать мышью на пустое место на поле или увеличить порядок кривой. Скачать необходимые для компиляции файлы проекта можно на http://program.dax.ru

uses Math;
const
  RectSize = 5;
  MaxN = 128;
var
  n: integer = -1;
  pt: array[0..MaxN] of TPoint;
  C: array[0..MaxN] of single;
  bm: TBitMap;
function GetBinomialCoefficient(m, i: integer): single;
  function Factorial(x: integer): double;
  var
    i: integer;
  begin
    result := 1;
    for i := 2 to x do
      result := result * i;
  end;
begin
  result := Factorial(m) / (Factorial(i) * Factorial(m - i));
end;
procedure DrawBezier(Canvas: TCanvas; Count: integer);
type
  TPointArray = array[word] of TPoint;
  PPointArray = ^TPointArray;
var
  p: PPointArray;
  Step, qx, qy, t, q: single;
  i, j: integer;
begin
  GetMem(p, sizeof(TPoint) * (Count + 1));
  Step := 1.0 / Count;
  for i := 0 to Count do
  begin
    t := i * Step;
    qx := 0;
    qy := 0;
    for j := 0 to n do
    begin
      q := C[j] * IntPower(1 - t, j) * IntPower(t, n - j);
      qx := qx + q * pt[j].x;
      qy := qy + q * pt[j].y;
    end;
    p[i] := Point(round(qx), round(qy));
  end;
  Canvas.Polyline(Slice(p^, Count + 1));
  FreeMem(p);
end;
procedure DrawLines(canvas: TCanvas; const pt: array of TPoint);
var
  i: integer;
begin
  Canvas.Pen.Color := clGreen;
  Canvas.Pen.Width := 1;
  Canvas.MoveTo(pt[0].x, pt[0].y);
  for i := 0 to n do
  begin
    Canvas.Rectangle(Bounds(pt[i].x - RectSize, pt[i].y - RectSize,
      2 * RectSize, 2 * RectSize));
    Canvas.LineTo(pt[i].x, pt[i].y);
  end;
end;
procedure Redraw;
begin
  with Form1.PaintBox1 do
  begin
    bm.Canvas.FillRect(Bounds(0, 0, Width, Height));
    if Form1.CheckBox1.Checked then
      DrawLines(bm.Canvas, pt);
    bm.Canvas.PolyBezier(pt);
    bm.Canvas.Pen.Color := clRed;
    bm.Canvas.pen.Width := Form1.SpinEdit3.Value;
    DrawBezier(bm.Canvas, Form1.SpinEdit2.Value);
    Canvas.Draw(0, 0, bm);
  end;
end;
var
  moving: integer = -1;
  oldr: TRect;
procedure FillRandom(NewN: integer);
var
  i: integer;
begin
  randomize;
  for i := n + 1 to NewN do
    pt[i] := Point(random(Form1.PaintBox1.Width - 20) + 10,
      random(Form1.PaintBox1.Height - 20) + 10);
  n := NewN;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
  bm := TBitmap.Create;
  bm.Width := PaintBox1.Width;
  bm.Height := PaintBox1.height;
  SpinEdit1.MinValue := 1;
  SpinEdit1.MaxValue := MaxN;
  SpinEdit1.Value := 3;
  SpinEdit2.MinValue := 6;
  SpinEdit2.MaxValue := MaxN * 4;
  SpinEdit2.Value := 50;
  SpinEdit2.OnChange := PaintBox1.OnPaint;
  SpinEdit3.MinValue := 1;
  SpinEdit3.MaxValue := 8;
  SpinEdit3.Value := 3;
  SpinEdit3.OnChange := PaintBox1.OnPaint;
  CheckBox1.Checked := true;
  CheckBox1.OnClick := PaintBox1.OnPaint;
end;
procedure TForm1.PaintBox1MouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
var
  i: integer;
  r: TRect;
begin
  if Button <> mbLeft then
    Exit;
  for i := 0 to n do
    if (abs(X - pt[i].x) <= RectSize) and (abs(Y - pt[i].y) <= RectSize) then
    begin
      moving := i;
      r.TopLeft := Form1.ClientToScreen(PaintBox1.BoundsRect.TopLeft);
      r.BottomRight := Form1.ClientToScreen(PaintBox1.BoundsRect.BottomRight);
      GetClipCursor(oldr);
      ClipCursor(@r);
      Exit;
    end;
  if moving < 0 then
  begin
    SpinEdit1.Value := SpinEdit1.Value + 1;
    pt[n] := Point(X, Y);
    Redraw;
  end;
end;
procedure TForm1.PaintBox1MouseMove(Sender: TObject; Shift: TShiftState; X, Y:
  Integer);
begin
  if moving < 0 then
    Exit;
  pt[moving] := Point(X, Y);
  Redraw;
end;
procedure TForm1.PaintBox1MouseUp(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
  if (Button = mbLeft) and (moving >= 0) then
  begin
    moving := -1;
    ClipCursor(@oldr);
  end;
end;
procedure TForm1.SpinEdit1Change(Sender: TObject);
var
  i: integer;
begin
  FillRandom(SpinEdit1.Value);
  SpinEdit2.MinValue := n * 2;
  for i := 0 to n do
    C[i] := GetBinomialCoefficient(n, i);
  Redraw;
end;
procedure TForm1.PaintBox1Paint(Sender: TObject);
begin
  Redraw;
end;

Отрисовка битового образца.

Статьи по Delphi (006)…
unit aplanes_;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs;
type
  TForm1 = class(TForm)
    procedure FormPaint(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;
var
  Form1: TForm1;
  sky, aplane: TBitMap; // битовые образы: небо и самолет
implementation
{$R *.DFM}
procedure TForm1.FormPaint(Sender: TObject);
begin
     // создать битовые образы
  sky := TBitMap.Create;
  aplane := TBitMap.Create;
     // загрузить картинки
  sky.LoadFromFile('sky.bmp');
  aplane.LoadFromFile('aplane.bmp');
  Form1.Canvas.Draw(0, 0, sky); // отрисовка фона
  Form1.Canvas.Draw(20, 20, aplane); // отрисовка левого самолета
  aplane.Transparent := True;
     // теперь элементы рисунка, цвет которых совпадает с цветом
     // левой нижней точки битового образа, не отрисовываются
  Form1.Canvas.Draw(120, 20, aplane); // отрисовка правого самолета
     // освободить память
  sky.free;
  aplane.free;
end;
end.
Скачать весь проект

Программа рисует на форме календарь на 2002 год.

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

Автор: Даниил Карапетян WEB сайт: http://program.dax.ru
Девушка флиртует с программистом (сисадмином, электронщиком). Задает вопрос: - О чем думает компьютер? Получает ответ: - Он думает о том дебиле, который написал для него программу о том что нужно думать.
В связи с наступающим Новым годом я решил посвятить выпуск календарю. Ниже приведенная программа рисует на форме календарь на 2002 год. Для каждого месяца сначала выводится его название (используется глобальная переменная LongMonthNames модуля SysUtils), далее выводятся сокращенные названия дней недели (глобальная переменная ShortDayNames модуля SysUtils) и, наконец, выводятся сами числа. Количество дней в месяце записано в массиве months. Чтобы определить, високосный это год или нет, используется функция IsLeapYear.

Скачать необходимые для компиляции файлы проекта можно на program.dax.ru

const year = 2002; // Год календаря
var months: array [1..12] of byte;
procedure TForm1.FormCreate(Sender: TObject);
begin
  Form1.Caption := 'Календарь на ' + IntToStr(year) + ' год';
  Form1.Color := clWhite;
  // Длины месяцев:
  months[1] := 31;
  months[2] := 28 + ord(IsLeapYear(year));
  months[3] := 31;
  months[4] := 30;
  months[5] := 31;
  months[6] := 30;
  months[7] := 31;
  months[8] := 31;
  months[9] := 30;
  months[10] := 31;
  months[11] := 30;
  months[12] := 31;
end;
procedure TForm1.FormPaint(Sender: TObject);
const // Настройки размеров календаря:
  MonthDX = 150;
  MonthDY = 135;
  DayDX = 20;
  DayDY = 15;
  MonthH = 20;
var
  month, i: integer;
  day: integer;
  s: string[2];
begin
  with Form1.Canvas do for month := 1 to 12 do begin
    // Вывод названия месяца:
    Font.Name := 'Times';
    Font.Size := 13;
    TextOut((month - 1) mod 3 * MonthDX, (month - 1) div 3 * MonthDY,
      LongMonthNames[month]);
    Font.Name := 'Courier';
    Font.Size := 8;
    // Вывод названий дней недели:
    for day := 1 to 7 do
      TextOut((month - 1) mod 3 * MonthDX,
        day mod 7 * DayDY + (month - 1) div 3 * MonthDY + MonthH,
        ShortDayNames[(day + 1) mod 7 + 1]);
    // Определение дня недели первого числа месяца:
    day := DayOfWeek(EncodeDate(year, month, 1)) - 2;
    if day < 0 then inc(day, 7);
    // Вывод чисел:
    for i := 1 to months[month] do begin
      str(i: 2, s);
      TextOut(day div 7 * DayDX + (month - 1) mod 3 * MonthDX + DayDX,
        day mod 7 * DayDY + (month - 1) div 3 * MonthDY + MonthH, s);
      inc(day);
    end;
  end;
end;

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

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

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

procedure TForm1.Button1Click(Sender: TObject);
var
  logfont: TLogFont;
  font: Thandle;
  count: integer;
begin
  LogFont.lfheight := 20;
  logfont.lfwidth := 20;
  logfont.lfweight := 750;
  LogFont.lfEscapement := -200;
  logfont.lfcharset := 1;
  logfont.lfoutprecision := out_tt_precis;
  logfont.lfquality := draft_quality;
  logfont.lfpitchandfamily := FF_Modern;
  font := createfontindirect(logfont);
  SelectObject(Form1.canvas.handle, font);
  SetTextColor(Form1.canvas.handle, rgb(0, 0, 200));
  SetBKmode(Form1.canvas.handle, transparent);
  for count := 1 to 10 do
  begin
    Canvas.TextOut(Random(form1.width), Random(form1.height), 'Delphi World');
    SetTextColor(form1.canvas.handle, rgb(Random(255), Random(255), Random(255)));
  end;
  DeleteObject(font);
end;

Как вывести цветную надпись на StatusBare.

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

Статусбар, это стандартный элемент управления Windows и как все отображает шрифт, заданный в параметре clBtnText, который устанавливается через Панель управления. Поумолчанию этот цвет чёрный, но он может менятся в зависимоти пользовательской темы. StatusBar и связанные с ним панели имеют возможность самостоятельной перерисовки (owner-draw), позволяющей рисовать текст различными цветами. Для этого необходимо в TStatusBar.Panels установить свойство Style в OwnerDraw.

procedure TForm1.StatusBar1DrawPanel(StatusBar: TStatusBar;
          Panel: TStatusPanel; const Rect: TRect);
begin
  if Panel = StatusBar.Panels[0] then
  begin
    StatusBar.Canvas.Font.Color := clRed;
    StatusBar.Canvas.TextOut(Rect.Left, Rect.Top, 'Panel - 0')
  end
  else
  begin
    StatusBar.Canvas.Font.Color := clGreen;
    StatusBar.Canvas.TextOut(Rect.Left, Rect.Top, 'Panel - 1');
  end;
end; 

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

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

{ 
  This tip might be useful if you want to program 
  a screen capture tool and show a bounding box 
  around a control or for a spy tool like winsight to 
  highlight a object on the screen. 
} 
var 
 hOldWnd :HWND; 
procedure FrameWindow(Wnd: HWnd); 
var 
  Rect: TRect; 
  DC: hDC; 
  OldPen, Pen: hPen; 
  OldBrush, Brush: hBrush; 
  X2, Y2: Integer; 
begin 
  { Get the target window's rect and DC } 
  GetWindowRect(Wnd, Rect); 
  DC := GetWindowDC(Wnd); 
  { Set ROP appropriately for highlighting } 
  SetROP2(DC, R2_NOT); 
  { Select brush and pen } 
  Pen := CreatePen(PS_InsideFrame, 4, 0); 
  OldPen := SelectObject(DC, Pen); 
  Brush := GetStockObject(Null_Brush); 
  OldBrush := SelectObject(DC, Brush); 
  { Set dimensions of highlight } 
  X2 := Rect.Right - Rect.Left; 
  Y2 := Rect.Bottom - Rect.Top; 
  { Draw highlight box } 
  Rectangle(DC, 0, 0, X2, Y2); 
  { Clean up } 
  SelectObject(DC, OldBrush); 
  SelectObject(DC, OldPen); 
  ReleaseDC(Wnd, DC); 
  { Do NOT delete the brush, because it was a stock object } 
  DeleteObject(Pen); 
end; 
procedure TForm1.Timer1Timer(Sender: TObject); 
var 
  hNewWnd: HWnd; 
begin 
  hNewWnd := WindowFromPoint(Mouse.CursorPos); 
  { To avoid flickering, remove the old frame ONLY if moved to new window } 
  if hNewWnd <> hOldWnd then 
  begin 
    if hOldWnd <> 0 then 
      FrameWindow(hOldWnd); 
    if hNewWnd <> 0 then 
      FrameWindow(hNewWnd); 
    hOldWnd := hNewWnd; 
  end; 
end;

Рисование КРИВЫХ в Delphi.

Автор: Dmitry Streblechenko В: У кого-нибудь есть исходный код или какая-либо информация для рисования кривых Безье? Я должен использовать их в своем компоненте. Пожалуйста используйте для ответа мой адрес электронной почты. О: Я делал это недавно; мне было лениво разбираться с тем, как рисовать кривые Безье с помощью Win API, поэтому я использовал функцию Polyline().

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

PBezierPoint = ^TBezierPoint;
TBezierPoint = record
  X, Y: double;   // основной узел
  Xl, Yl: double; // левая контрольная точка
  Xr, Yr: double; // правая контрольная точка
end;
// P1 и P2 - две точки TBezierPoint, расположенные между 0 и 1:
// когда t=0 X=P1.X, Y=P1.Y; когда t=1 X=P2.X, Y=P2.Y;
procedure BezierValue(P1, P2: TBezierPoint; t: double; var X, Y: double);
var
  t_sq, t_cb, r1, r2, r3, r4: double;
begin
  t_sq := t * t;
  t_cb := t * t_sq;
  r1 := (1 - 3 * t + 3 * t_sq - t_cb) * P1.X;
  r2 := (3 * t - 6 * t_sq + 3 * t_cb) * P1.Xr;
  r3 := (3 * t_sq - 3 * t_cb) * P2.Xl;
  r4 := (t_cb) * P2.X;
  X := r1 + r2 + r3 + r4;
  r1 := (1 - 3 * t + 3 * t_sq - t_cb) * P1.Y;
  r2 := (3 * t - 6 * t_sq + 3 * t_cb) * P1.Yr;
  r3 := (3 * t_sq - 3 * t_cb) * P2.Yl;
  r4 := (t_cb) * P2.Y;
  Y := r1 + r2 + r3 + r4;
end;

Для рисования кривой Безье разделяем интервал между P1 и P2 на несколько отрезков (их количество влияет на точность воспроизведения кривой, 3 - 4 точки вполне достаточно), затем в цикле создаем массив точек, используем описанную выше процедуру с параметром t от 0 до 1 и рисуем данный массив точек, используя функцию polyline().

Рисование КРИВЫХ в Delphi 2.

В: У кого-нибудь есть исходный код или какая-либо информация для рисования кривых Безье? Я должен использовать их в своем компоненте. Пожалуйста используйте для ответа мой адрес электронной почты. Я решил ответить на этот крик души - причина?: 1. Не первый раз вижу подобный вопрос, 2. Задача настолько избита, что я без труда нашел ответ в своем архиве. (BTW: У меня есть более старые решения, чем это ;-P)

Тем не менее эта технология жива до сих пор и приносит свои плоды:

(********************************************************************)
(*                         GRAPHIX TOOLBOX 4.0                      *)
(*       Copyright (c) 1985, 87 by  Borland International, Inc.     *)
(********************************************************************)
unit GShell;
interface
{------------------------------ вырезано --------------------------}
procedure Bezier(A: PlotArray; MaxContrPoints: integer;
  var B: PlotArray; MaxIntPoints: integer);
implementation
{------------------------------ вырезано --------------------------}
procedure Bezier {(A : PlotArray; MaxContrPoints : integer;
var B : PlotArray; MaxIntPoints : integer)};
const
  MaxControlPoints = 25;
type
  CombiArray = array[0..MaxControlPoints] of Float;
var
  N: integer;
  ContrPoint, IntPoint: integer;
  T, SumX, SumY, Prod, DeltaT, Quot: Float;
  Combi: CombiArray;
begin
  MaxContrPoints := MaxContrPoints - 1;
  DeltaT := 1.0 / (MaxIntPoints - 1);
  Combi[0] := 1;
  Combi[MaxContrPoints] := 1;
  for N := 0 to MaxContrPoints - 2 do
    Combi[N + 1] := Combi[N] * (MaxContrPoints - N) / (N + 1);
  for IntPoint := 1 to MaxIntPoints do
  begin
    T := (IntPoint - 1) * DeltaT;
    if T <= 0.5 then
    begin
      Prod := 1.0 - T;
      Quot := Prod;
      for N := 1 to MaxContrPoints - 1 do
        Prod := Prod * Quot;
      Quot := T / Quot;
      SumX := A[MaxContrPoints + 1, 1];
      SumY := A[MaxContrPoints + 1, 2];
      for N := MaxContrPoints downto 1 do
      begin
        SumX := Combi[N - 1] * A[N, 1] + Quot * SumX;
        SumY := Combi[N - 1] * A[N, 2] + Quot * SumY;
      end;
    end
    else
    begin
      Prod := T;
      Quot := Prod;
      for N := 1 to MaxContrPoints - 1 do
        Prod := Prod * Quot;
      Quot := (1 - T) / Quot;
      SumX := A[1, 1];
      SumY := A[1, 2];
      for N := 1 to MaxContrPoints do
      begin
        SumX := Combi[N] * A[N + 1, 1] + Quot * SumX;
        SumY := Combi[N] * A[N + 1, 2] + Quot * SumY;
      end;
    end;
    B[IntPoint, 1] := SumX * Prod;
    B[IntPoint, 2] := SumY * Prod;
  end;
end; { Bezier }
end. { GShell }

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

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

Используй вызов DrawTextEx, установив в параметре dwDTFormat значение DT_PATH_ELLIPSIS.

procedure TForm1.FormPaint(Sender: TObject);
var
  r: TRect;
begin
  r := Rect(20, 20, 110, 70);
  // DT_PATH_ELLIPSIS or DT_WORD_ELLIPSIS or DT_END_ELLIPSIS
  DrawTextEx(Form1.Canvas.Handle, 'Delphi World - это круто!!!',
   25, r, DT_WORD_ELLIPSIS, nil);
end;

Рисовать неактивный текст.

Статьи по Delphi (006)…
function DrawDisabledText (Canvas : tCanvas; Str: PChar; Count: Integer;
  var Rect: TRect; Format: Word): Integer;
begin
  SetBkMode(Canvas.Handle, TRANSPARENT);
  OffsetRect(Rect, 1, 1);
  Canvas.Font.color := ClbtnHighlight;
  DrawText(Canvas.Handle, Str, Count, Rect, Format);
  Canvas.Font.Color := ClbtnShadow;
  OffsetRect(Rect, -1, -1);
  DrawText(Canvas.Handle, Str, Count, Rect, Format);
end;

Хочу прорисовать границу ячейки в Excel.

Статьи по Delphi (006)…
Cells[i,j].Borders.LineStyle := xlContinuous; 

Вывод шрифтов в списке в виде самих шрифтов.

Статьи по Delphi (006)…
- Что общего между Биллом Гейтсом и Россией? - Одинаковый валютный запас
unit Fontlist;
interface
uses
  Windows, Classes, Graphics, Forms, Controls, StdCtrls;
type
  TForm1 = class(TForm)
    ListBox1: TListBox;
    Label1: TLabel;
    FontLabel: TLabel;
    procedure FormCreate(Sender: TObject);
    procedure ListBox1Click(Sender: TObject);
    procedure DrawItem(Control: TWinControl; index: Integer; Rect: TRect;
      State: TOwnerDrawState);
    procedure ListBox1MeasureItem(Control: TWinControl; index: Integer;
      var Height: Integer);
  private
    { Private declarations }
  public
    { Public declarations }
end;
var
  Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.FormCreate(Sender: TObject);
begin
  Listbox1.Items := Screen.Fonts;
end;
procedure TForm1.ListBox1Click(Sender: TObject);
begin
  FontLabel.Caption := ListBox1.Items[ListBox1.ItemIndex];
end;
procedure TForm1.DrawItem(Control: TWinControl; index: Integer;
  Rect: TRect; State: TOwnerDrawState);
begin
  with ListBox1.Canvas do
  begin
    FillRect(Rect);
    Font.name := ListBox1.Items[index];
    Font.Size := 0; // use font's preferred size
    TextOut(Rect.Left+1, Rect.Top+1, ListBox1.Items[index]);
  end;
end;
procedure TForm1.ListBox1MeasureItem(Control: TWinControl; index: Integer;
  var Height: Integer);
begin
  with ListBox1.Canvas do
  begin
    Font.name := Listbox1.Items[index];
    Font.Size := 0; // use font's preferred size
    Height := TextHeight('Wg') + 2; // measure ascenders and descenders
  end;
end;
end.

Как нарисовать фрактал.

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

Статьи по Delphi (006)…
procedure DrawMandelbrot(ACanvas: TCanvas; X, Y, au, bu: Double; X2, Y2:
  Integer);
var
  c1, c2, z1, z2, tmp: Double;
  i, j, Count: Integer;
begin
  c2 := bu;
  for i := 10 to X2 do
  begin
    c1 := au;
    for j := 0 to Y2 do
    begin
      z1 := 0;
      z2 := 0;
      Count := 0;
      {count is deep of iteration of the mandelbrot set
       if |z| >=2 then z is not a member of a mandelset}
      while (((z1 * z1 + z2 * z2 < 4) and (Count <= 90))) do
      begin
        tmp := z1;
        z1 := z1 * z1 - z2 * z2 + c1;
        z2 := 2 * tmp * z2 + c2;
        Inc(Count);
      end;
      //the color-palette depends on TColor(n*count mod t)
{$IFDEF LINUX}
      ACanvas.Pen.Color := (16 * Count mod 255);
      ACanvas.DrawPoint(j, i);
{$ELSE}
      ACanvas.Pixels[j, i] := (16 * Count mod 255);
{$ENDIF}
      c1 := c1 + X;
    end;
    c2 := c2 + Y;
  end;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
  R: TRect;
  au, ao: Integer;
  dX, dY, bo, bu: Double;
begin
  // Initialize Mandelbrot
  R.Left := 0;
  R.Right := 200;
  R.Top := 0;
  R.Bottom := 205;
  ao := 1;
  au := -2;
  bo := 1.5;
  bu := -1.5;
  //direct scaling cause of speed
  dX := (ao - au) / (R.Right - R.Left);
  dY := (bo - bu) / (R.Bottom - R.Top);
  DrawMandelbrot(Self.Canvas, dX, dY, au, bu, R.Right, R.Bottom);
end;

Как нарисовать график функции.

procedure TForm1.Button3Click(Sender: TObject);
var
  x, y: array[1..50] of double;
  i: integer;
  scalex, scaley, ymin, ymax, xmin, xmax: double;
begin
  for i := 1 to 50 do
  begin
    y[i] := sin(i * 0.5);
    x[i] := i;
  end;
  xmin := x[1];
  xmax := x[1];
  ymin := y[1];
  ymax := y[1];
  for i := 2 to 50 do
  begin // или используйте ymin:=MinValue(y); и т.д.
    if y[i] < ymin then
      ymin := y[i];
    if y[i] > ymax then
      ymax := y[i];
    if x[i] < xmin then
      xmin := x[i];
    if x[i] > xmax then
      xmax := x[i];
  end;
  scalex := paintbox1.Width / (xmax - xmin);
  scaley := paintbox1.Height / (ymax - ymin);
  with paintbox1.canvas do
  begin
    moveto(trunc(scalex * (x[1] - xmin)), paintbox1.height - trunc(scaley * (y[1]
      - ymin)));
    for i := 2 to 50 do
      Lineto(trunc(scalex * (x[i] - xmin)), paintbox1.height - trunc(scaley *
        (y[i] - ymin)));
  end;
end;

Как нарисовать график функции 2.

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

Забавная штука синусы:

for i := 1 to 500 do
  paintbox1.Canvas.Pixels[round(sin(i * 5) * 10 + 50), round(sin(i * 10) * 10 +
    50)] := RGB(0, 0, 0);

Рисование графов.

...вы могли бы использовать объект TCanvas, чем рисовать самому. В вашем случае сгодится компонент TImage, он имеет bitmap и свойство canvas, на котором очень удобно рисовать.

Пример: (Создайте новую форму, добавьте к ней Image и Button. Добавьте следующий код к обработчику события нажатия кнопки).

var
  x, l: Integer;
  y, a: Double;
begin
  Image1.Picture.Bitmap := TBitmap.Create;
  Image1.Picture.Bitmap.Width := Image1.Width;
  Image1.Picture.Bitmap.Height := Image1.Height; {Эти три строчки могут быть
  размещены в обработчике Form1.Create}
  l := Image1.Picture.Bitmap.Width;
  for x := 0 to l do
  begin
    a := (x / l) * 2 * Pi; {Преобразуем позицию по оси X к углу между 0 & 2Pi}
    y := Sin(a); {Ваша функция должна находиться здесь}
    y := y * (Image1.Picture.Bitmap.Height / 2); {Масштабируем по оси Y}
    y := y * -1; {Инвертируем Y, верх экрана это 0 !}
    y := y + (Image1.Picture.Bitmap.Height / 2);
      {Добавляем компенсацию для среднего 0}
    Image1.Picture.Bitmap.Canvas.Pixels[Trunc(x), Trunc(y)] := clBlack;
  end;
end;

Я обнаружил, что лучшим решением будет рисование на холсте. Предпочтительно делать это в отдельной процедуре, которая принимает в качестве параметров TCanvas и TRect. Таким способом мы может передать в качестве параметров холст вашего окна и клиентскую область для рисования на экране, и холст принтера и область клиента для ее позиционирования и печати. Чтобы посмотреть доступные для рисования подпрограммы, взгляните на методы холста.

Вывести полупрозрачный текст.

Статьи по Delphi (006)…
procedure TForm1.FormPaint(Sender: TObject);
var
  x, y: integer;
  bm: TBitMap;
begin
  Form1.ClientWidth := 200;
  Form1.ClientHeight := 100;
  randomize;
  for x := 0 to 199 do
    for y := 0 to 99 do
      if random(3) = 1 then
        Form1.Canvas.Pixels[x,y] := clGreen
      else
        Form1.Canvas.Pixels[x,y] := clLime;
  bm := TBitMap.Create;
  bm.Width := 200;
  bm.Height := 100;
  with bm.Canvas do
  begin
    Brush.Color := clGreen;
    FillRect(ClipRect);
    Font.name := 'Arial';
    Font.Size := 50;
    Font.Color := clGray;
    Font.Style := [fsBold];
    TextOut((bm.Width - TextWidth('Text')) div 2,
    (bm.Height - TextHeight('Text')) div 2, 'Text');
  end;
  Form1.Canvas.CopyMode := cmSrcPaint;
  Form1.Canvas.CopyRect(bm.Canvas.ClipRect, bm.Canvas,
  bm.Canvas.ClipRect);
  bm.Destroy;
end;

Рисование иконки в TImage.

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

DrawIcon(Image1.Canvas.Handle, 0, 0, LoadCursor(0, IDC_ARROW));

Как рисовать картинки в пунктах меню (через OwnerDraw).

unit DN_Win;
interface
uses
  SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
  Forms, Dialogs, Menus, StdCtrls,
type
  TDNForm = class(TForm)
    MainMenu1: TMainMenu;
    cm_MainExit: TMenuItem;
    procedure FormCreate(Sender: TObject);
    procedure cm_MainExitClick(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
    BM: TBitmap;
    procedure WMDrawItem(var Msg:TWMDrawItem); message wm_DrawItem;
    procedure WMMeasureItem(var Msg:TWMMeasureItem); message wm_MeasureItem;
end;
var
  DNForm: TDNForm;
implementation
{$R *.DFM}
var
  Comm, yMenu: word;
procedure TDNForm.FormCreate(Sender: TObject);
begin
  yMenu:=GetSystemMetrics(SM_CYMENU);
  comm:=cm_MainExit.Command;
  ModifyMenu(MainMenu1.Handle,0,mf_ByPosition or mf_OwnerDraw,comm,'Go');
end;
procedure TDNForm.cm_MainExitClick(Sender: TObject);
begin
  DNForm.Close;
end;
procedure TDNForm.WMMeasureItem(var Msg:TWMMeasureItem);
begin
  with Msg.MeasureItemStruct^ do
    if ItemID=comm then
    begin
      ItemWidth:=yMenu;
      Itemheight:=yMenu;
    end;
end;
procedure TDNForm.WMDrawItem(var Msg: TWMDrawItem);
var
  MemDC:hDC;
  BM:hBitMap;
  mtd:longint;
begin
  with Msg.DrawItemStruct^ do
  begin
    if ItemID=comm then
    begin
      BM:=LoadBitMap(hInstance,'dver');
      MemDC:=CreateCompatibleDC(hDC);
      SelectObject(MemDC,BM);
      if ItemState=ods_Selected then
        mtd:=NotSrcCopy
      else
        mtd:=SrcCopy;
      StretchBlt(hDC,rcItem.left, rcItem.top, yMenu, yMenu, MemDC,
      0, 0, 24, 23, mtd);
      DeleteDC(MemDC);
      DeleteObject(BM);
    end;
  end;
end;
end.

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

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

Автор: William E Murto

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

unit Lines; {от Bill Murto, CIS 73730,2505}
interface
uses
  SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls, Forms;
type
  TLineOrigin = (loTopLeft, loTopRight);
  TLine = class(TGraphicControl)
  private
    { Private declarations }
    fOrigin: TLineOrigin;
    fPen: TPen;
    procedure SetOrigin(Value: TLineOrigin);
    procedure SetPen(Value: TPen);
  protected
    { Protected declarations }
    procedure Paint; override;
  public
    { Public declarations }
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
  published
    { Published declarations }
    property Origin: TLineOrigin read fOrigin write SetOrigin default
      loTopLeft;
    property Pen: TPen read fPen write SetPen;
    property Height default 33;
    property Width default 33;
    procedure StyleChanged(Sender: TObject);
  end;
procedure Register;
implementation
procedure Register;
begin
  RegisterComponents('Samples', [TLine]);
end;
procedure TLine.SetOrigin(Value: TLineOrigin);
begin
  if fOrigin <> Value then
  begin
    fOrigin := Value;
    Invalidate;
  end;
end;
procedure TLine.SetPen(Value: TPen);
begin
  fPen.Assign(Value);
end;
procedure TLine.StyleChanged(Sender: TObject);
begin
  Invalidate;
end;
constructor TLine.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  Height := 33;
  Width := 33;
  fPen := TPen.Create;
  fPen.OnChange := StyleChanged;
  if csOpaque in ControlStyle then
    ControlStyle := ControlStyle - [csOpaque];
end;
procedure TLine.Paint;
begin
  with Canvas do
  begin
    Pen := fPen;
    if (Width <= Pen.Width) or (Height <= Pen.Width) then
    begin
      if Width <= Pen.Width then
      begin
        MoveTo(0, 0);
        LineTo(0, Height);
      end;
      if Height <= Pen.Width then
      begin
        MoveTo(0, 0);
        LineTo(Width, 0);
      end;
    end
    else
      case fOrigin of
        loTopLeft:
          begin
            MoveTo(0, 0);
            LineTo(Width, Height);
          end;
        loTopRight:
          begin
            MoveTo(Width, 0);
            LineTo(0, Height);
          end;
      end;
  end;
end;
destructor TLine.Destroy;
begin
  fPen.Free;
  inherited Destroy;
end;
end.

Нарисовать линию, не используя функции LineTo.

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

{ 
  Enables you do draw a line if for some reason you 
  cannot use the delphi LineTo procedure. 
  For example, for drawing higher resolution lines 
  or drawing lines in 2D arrays. 
}
 procedure DrawLine(APoint1, APoint2: TPoint; ACanvas: TCanvas);
 var
   Lpixel, LMaxAxisLength: integer;
   LRatio: Real;
 begin
   LMaxAxisLength := Max(abs(APoint1.X - APoint2.X), abs(APoint1.Y - APoint2.Y));
   for Lpixel := 0 to LMaxAxisLength do
    begin
     LRatio := Lpixel / LMaxAxisLength;
     ACanvas.Pixels[APoint1.X + Round((APoint2.X - APoint1.X) * LRatio),
       APoint1.Y + Round((APoint2.Y - APoint1.Y) * LRatio)] :=
       ACanvas.Pen.Color;
   end;
 end;
 // Draw a double resolution line 
procedure DrawLineDouble(APoint1, APoint2: TPoint; ACanvas: TCanvas);
 var
   Lpixel, LMaxAxisLength: integer;
   LRatio: Real;
   LPoint: TPoint;
 begin
   LMaxAxisLength := max(abs(APoint1.X - APoint2.X), abs(APoint1.Y - APoint2.Y));
   for Lpixel := 0 to LMaxAxisLength do
    begin
     LRatio := Lpixel / LMaxAxisLength;
     LPoint.X := APoint1.X + Round((APoint2.X - APoint1.X) * LRatio);
     LPoint.Y := APoint1.Y + Round((APoint2.Y - APoint1.Y) * LRatio);
     with ACAnvas do
      begin
       Pixels[LPoint.X * 2, LPoint.Y * 2] := clBlack;
       Pixels[(LPoint.X * 2) + 1, LPoint.Y * 2] := clBlack;
       Pixels[LPoint.X * 2, (LPoint.Y * 2) + 1] := clBlack;
       Pixels[(LPoint.X * 2) + 1, (LPoint.Y * 2) + 1] := clBlack;
     end;
   end;
 end;

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

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

Не забудь установить свойство Style у твоего ListBox в lbOwnerDrawFixed или в lbOwnerDrawVariable.

procedure TForm1.ListBox1DrawItem(Control: TWinControl; index: Integer;
Rect: TRect; State: TOwnerDrawState);
begin
  with ListBox1 do
  begin
    if odSelected in State then
      Canvas.Brush.Color:=clTeal { твой цвет }
    else
      Canvas.Brush.Color:=clWindow;
    Canvas.FillRect(Rect);
    Canvas.TextOut(Rect.Left+2,Rect.Top,Items[index]);
  end;
end;

Рисование квадрата мышкой.

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

private
   { Private declarations }
   AnchorX, AnchorY,
   CurX, CurY: Integer;
   Bounding: Boolean;
 end;
 implementation
 procedure TForm1.FormMouseDown(Sender: TObject; Button: TMouseButton;
   Shift: TShiftState; X, Y: Integer);
 begin
   AnchorX := X;
   CurX := X;
   AnchorY  := Y;
    CurY := Y;
   Bounding := True;
 end;
 procedure TForm1.FormMouseMove(Sender: TObject; Shift: TShiftState; X,
   Y: Integer);
 begin
   if Bounding then
    begin
     Canvas.Pen.Mode := pmNot;
     Canvas.Pen.Width := 2;
     Canvas.Brush.Style := bsClear;
     Canvas.Rectangle(AnchorX, AnchorY, CurX, CurY);
     CurX := X;
      CurY := Y;
     Canvas.Rectangle(AnchorX, AnchorY, CurX, CurY);
   end;
 end;
 procedure TForm1.FormMouseUp(Sender: TObject; Button: TMouseButton;
   Shift: TShiftState; X, Y: Integer);
 begin
   if Bounding then
    begin
     Bounding := False;
     Canvas.Pen.Mode := pmNot;
     Canvas.Brush.Style := bsClear;
     Canvas.Rectangle(AnchorX, AnchorY, CurX, CurY);
   end;
 end;

Как рисовать линии (или ещё что-нибудь) на экране (TDesktopCanvas).

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

// Пример рисует две горизонтальные линии на экране используя TDesktopCanvas.
program TrinitronTraining;
uses
  Messages, Windows, Graphics, Forms;
type
  TDesktopCanvas = class(TCanvas)
  private
    DC : hDC;
    function GetWidth:Integer;
    function GetHeight:Integer;
  public
    constructor Create;
    destructor Destroy; override;
  published
    property Width: Integer read GetWidth;
    property Height: Integer read GetHeight;
end;
{ Объект TDesktopCanvas }
function TDesktopCanvas.GetWidth:Integer;
begin
  Result:=GetDeviceCaps(Handle,HORZRES);
end;
function TDesktopCanvas.GetHeight:Integer;
begin
  Result:=GetDeviceCaps(Handle,VERTRES);
end;
constructor TDesktopCanvas.Create;
begin
  inherited Create;
  DC := GetDC(0);
  Handle := DC;
end;
destructor TDesktopCanvas.Destroy;
begin
  Handle := 0;
  ReleaseDC(0, DC);
  inherited Destroy;
end;
const
  YCount = 2;
var
  desktop : TDesktopCanvas;
  dx,dy : Integer;
  i : Integer;
  F : array[1..YCount] of TForm;
function CreateLine(Y : Integer) : TForm;
begin
  Result := TForm.Create(Application);
  with Result do begin
    Left := 0;
    Top := y;
    Width := dx;
    Height := 1;
    BorderStyle := bsNone;
    FormStyle := fsStayOnTop;
    Visible := True;
  end;
end;
procedure ProcessMessage;
var
  Msg : TMsg;
begin
  if PeekMessage(Msg, 0, 0, 0, PM_REMOVE) then
    if Msg.message = WM_QUIT then
      Application.Terminate;
end;
begin
  desktop := TDesktopCanvas.Create;
  try
    dx := desktop.Width;
    dy := desktop.Height div (YCount+1);
  finally
    desktop.free;
  end;
  for i:=1 to YCount do
    F[i]:=CreateLine(i*dy);
  Application.NormalizeTopMosts;
  ShowWindow(Application.Handle, SW_Hide);
  for i:=1 to YCount do
    SetWindowPos(F[i].Handle, HWND_TOPMOST, 0,0,0,0, SWP_NOACTIVATE+SWP_NOMOVE+SWP_NOSIZE);
  {
  следующие строки используются для того, чтобы не останавливаться
  repeat
  ProcessMessage;
  until false;
  }
  Sleep(15000);
  for i:=1 to YCount do
    F[i].Free;
end.

Можно ли рисовать на рамке формы.

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

Обрабатывайте событие WM_NCPAINT. В примере рамка обводится красной линией толщиной в 1 пиксель.

В частных объявлениях объявляем процедуру обработки сообщение WMNCPaint:

private
  { Private declarations }
  procedure WMNCPaint(var Msg : TWMNCPaint); message WM_NCPAINT;

В области реализации [после слова implemantation] пишем:

procedure TForm1.WMNCPaint(var Msg: TWMNCPaint);
var
  dc: hDc;
  Pen: hPen;
  OldPen: hPen;
  OldBrush: hBrush;
begin
  inherited;
  dc := GetWindowDC(Handle);
  msg.Result := 1;
  Pen := CreatePen(PS_SOLID, 1, RGB(255, 0, 0));
  OldPen := SelectObject(dc, Pen);
  OldBrush := SelectObject(dc, GetStockObject(NULL_BRUSH));
  Rectangle(dc, 0,0, Form1.Width, Form1.Height);
  SelectObject(dc, OldBrush);
  SelectObject(dc, OldPen);
  DeleteObject(Pen);
  ReleaseDC(Handle, Canvas.Handle);
end;

Рисование на минимизированной иконке.

Автор: Nick Hodges (Monterey, CA) Есть ли у кого пример рисования на иконке минимизированного приложения с помощью Delphi?

Когда Delphi-приложение минимизировано, иконка, которая вы видите - реальное главное окно, объект TApplication, поэтому вам необходимо использовать переменную Application. Таким образом, чтобы удостовериться что приложение минимизировано, вызовите IsIconic(Application.Handle). Если функция возвратит True, значит так оно и есть. Для рисования на иконке создайте обработчик события Application.OnMessage. Здесь вы можете проверять наличие сообщения WM_Paint и при его нахождении отрисовывать иконку. Это должно выглядеть приблизительно так:

...
{ private declarations }
  procedure AppOnMessage(var Msg: TMsg; var Handled: Boolean);
...
procedure TForm1.AppOnMessage(var Msg: TMsg; var Handled: Boolean);
var
  DC: hDC;
  PS: TPaintStuff;
begin
  if (Msg.Message = WM_PAINT) and IsIconic(Application.Handle) then
  begin
    DC := BeginPaint(Application.Handle, PS);
    ...осуществляем отрисовку с помощью вызовов Windows GDI...
    EndPaint(Application.Handle, PS);
    Handled := True;
  end;
end;
procedure TForm1.OnCreate(Sender: TObject);
begin
  Application.OnMessage := AppOnMessage;
end;

Код создан на основе алгоритма Neil Rubenking.

Можно ли из Delphi рисовать в любой части экрана или в чужом окне.

Автор: Зайцев О.В. WEB-сайт: http://www.sources.ru

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

function GetDC(Wnd: HWnd): HDC;

Где Wnd - указатель на нужное окно, или 0 для получения контекста всего экрана. И далее, пользуясь функциями API, нарисовать все что надо.

procedure DrawOnScreen;
var
  ScreenDC: hDC;
begin
  ScreenDC := GetDC(0); {получить контекст экрана}
  Ellipse(ScreenDC, 0, 0, 200, 200); {нарисовать}
  ReleaseDC(0, ScreenDC); {освободить контекст}
end;

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

Как рисовать на органе управления, например, на TPanel.

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

У всех компонентов, порожденных от TCustomControl, имеется свойство Canvas типа TCanvas. Грубо говоря, это аналог TDC из OWL. Те операции, которые нельзя выполнить с помощью методов TCanvas, можно выполнить с помощью WinAPI. Для этого у обьектов класса TCanvas имеется свойство Handle - это и есть Хэндл Дисплейного Контекста ОС Windows (HDC), который необходим графическим функциям WinAPI. Если свойство Canvas недоступно, Вы можете достучаться до него созданием потомка и переносом этого свойства в раздел Public.

{ Example. We recommend You to create this component
through Component Wizard. In Delphi 1 it can be found
as 'File|New Component...', and can be found as
'Component|New Component...' in Delphi 2 or above. }
type
  TcPanel = class(TPanel)
public
  property Canvas;
end;

А если у объекта нет свойства Canvas (у TDBEdit, вроде-бы нет), по крайней мере в D3 можно использовать класс TControlCanvas. Примерное использование:

var
  cc: TControlCanvas;
...
  cc := TControlCanvas.Create;
  cc.Control := youControl;
...

И далее как обычно можно использовать методы Canvas.

Вывод текста на родительском элементе управления.

Свойство Canvas в TCustomControl существует, но оно защищено. Поскольку свойство canvas инкапсулирует windows HDC (Canvas.Handle), вы можете создавать объект TCanvas и назначать через свойство Handle контекст устройства элементу управления, на котором вы хотите рисовать.

Для примера:

procedure AControl.DrawLabel(ACaption: TCaption);
var
  ACanvas: TCanvas;
  DC: HDC;
begin
  ACanvas := TCanvas.Create;
  try
    WindowHandle := parent.Handle;
    DC := GetDeviceContext(WindowHandle);
    ACanvas.Handle := DC;
    with ACanvas do
    begin
    end;
    ACanvas.Handle := 0;
    ReleaseDC(WindowHandle, DC);
  finally
    ACanvas.free;
  end;
end;

Как рисовать на экране.

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

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

Я покажу как рисовать на экране на примере разлиновки:

Сначала объявите глобальную переменную.

Scr: TCanvas;

Затем по событию OnCreate() для формы напишите такой код:

Scr := TCanvas.Create;
Scr.Handle := GetDC(HWND_DESKTOP);

По событию OnDestroy() такой:

Scr.Free;

Обработчик события по нажатию на кнопку пусть выглядит так:

procedure TForm1.Button1Click(Sender: TObject);
var
  i: integer;
begin
  i := 0;
  while i do
  begin
    with Scr do
    begin
      MoveTo(i, 0);
      LineTo(i, 768);
      i := i + 10;
    end;
  end;
  i := 0;
  while i do
  begin
    with Scr do
    begin
      MoveTo(0, i);
      LineTo(1024, i);
      i := i + 10;
    end;
  end;
  Button1.Refresh;
end;

Как нарисовать что-нибудь на TMemo.

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

Опять-таки... составляю тут резюме... первым делом, есессно, натыкаюсь на пункт "ФИО" и пытаюсь ввести свой ник... затем все же заставляю себя заполнить данный пункт, несколько ошалело глядя на собственные _реальные_ фамилию и имя... настолько привыкнув за время пребывания в Сети к осторожности скрытности, что в первый момент жутко ругаю себя, что что ж это я так уже себя не контролирую... При достижении пунта "e-mail" и не менее послушном заполнении ужас просто-таки начинает раздирать - как - я - оставляю свое мыло... недремлющий спаммер... через некоторое время все-таки приходит осознание ситуации и понимание необходимости ввести свой пол и возраст, а также телефон :))), ...который чудом не был заменен номером Аськи...

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

type
  TMyMemo = class(TMemo)
  protected
    procedure WMPaint(var message: TWMPaint); message WM_PAINT;
end;

А теперь добавьте реализацию этой процедуры:

procedure TMyMemo.WMPaint(var message: TWMPaint);
var
  MCanvas: TControlCanvas;
  DrawBounds : TRect;
begin
  inherited;
  MCanvas:=TControlCanvas.Create;
  // Работаем с временной записью TRect.
  DrawBounds := ClientRect;
  try
    MCanvas.Control:=Self;
    with MCanvas do
    begin
      Brush.Color := clBtnFace;
      FrameRect( DrawBounds );
      InflateRect( DrawBounds, -1, -1);
      FrameRect( DrawBounds );
      FillRect ( DrawBounds );
      MoveTo ( 33, 0 );
      Brush.Color := clWhite;
      LineTo ( 33, ClientHeight );
      PaintImages;
    end;
  finally
    MCanvas.Free;
  end;
end;

Процедура PaintImages рисует картинки на канвасе Memo.

procedure TMyMemo.PaintImages;
var
  MCanvas: TControlCanvas;
  DrawBounds : TRect;
  i, j : Integer;
  OriginalRegion : HRGN;
  ControlDC : HDC;
begin
  MCanvas:=TControlCanvas.Create;
  // Работаем с временной записью TRect.
  DrawBounds := ClientRect;
  try
    MCanvas.Control:=Self;
    ControlDC := GetDC(Handle);
    MCanvas.Draw(0, 1, Application.Icon);
  finally
    MCanvas.Free;
  end;
end;

Теперь мы имеем собственноручно нарисованный memo.

Рисование прямоугольника на изображении.

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

Билл Гейтс помер и попал на небеса, а там вместо апостола Петра машину с Windows поставили. И, значит, в окне на мониторе надпись: "Уважаемый Билл, за Ваши заслуги перед человечеством мы разрешаем Вам самому выбрать, куда Вы попадете - в ад или в рай Выбирайте - в ад или в рай?". И ниже три кнопки с вариантами ответов: "Yes, No,Cancel"...
Я хочу узнать как можно позволить пользователю нарисовать на изображении прямоугольник, чтобы в дальнейшем выбранную часть, к примеру, масштабировать. Я хотел бы достичь такого же эффекта, как в других программах, где с помощью мышки можно при нажатой левой кнопке мыши выделить какую-либо область изображения.

Создайте логическое поле ImageMouse, для рисования прямоугольника ImageRect используйте метод холста drawfocusrect. Следующий код работает одинаково для левой и правой кнопок мыши:

procedure TForm1.Image1MouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
  ImageMouse := True;
  ImageRect.Left := X;
  ImageRect.Top := Y;
  ImageRect.Right := X;
  ImageRect.Bottom := Y;
  Image1.Canvas.DrawFocusRect(ImageRect);
end;
procedure TForm1.Image1MouseMove(Sender: TObject; Shift: TShiftState; X, Y:
  Integer);
var
  NewRect: tRect;
begin
  if ImageMouse then
    if (X > ImageRect.Left) and (Y > ImageRect.Top) then
    begin
      {Восстанавливаем фон}
      Image1.Canvas.DrawFocusRect(ImageRect);
      {Меняем прямоугольник}
      ImageRect.Right := X;
      ImageRect.Bottom := Y;
      {Рисуем прямоугольник фокуса}
      Image1.Canvas.DrawFocusRect(ImageRect);
    end;
end;
procedure TForm1.Image1MouseUp(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
  {Восстанавливаем фон}
  if ImageMouse then
  begin
    ImageRect.Right := X;
    ImageRect.Bottom := Y;
    Image1.Canvas.DrawFocusRect(ImageRect);
    ImageMouse := False;
    Image1.Canvas.CopyRect(Image1.Canvas.ClipRect,
      Image1.Canvas, ImageRect)
  end;
end;

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

Разговаривают два программиста: - А что он собой представляет? - Очень общительный и доброжелательный, даже не скажешь, что программист.

Если Вы хотите раскрасить выбранную строку DBGrid, но не хотите использовать опцию dgRowSelect, так как хотели бы редактировать данные, то можно воспользоваться следующей технологией в событии DBGrid.OnDrawColumnCell:

type
  TCustomDBGridCracker = class(TCustomDBGrid);
procedure TForm1.DBGrid1DrawColumnCell(Sender: TObject;
  const Rect: TRect; DataCol: Integer; Column: TColumn;
  State: TGridDrawState);
begin
  with Cracker(Sender) do
    if DataLink.ActiveRecord = Row - 1 then
      Canvas.Brush.Color := clRed
    else
      Canvas.Brush.Color := clWhite;
  DefaultDrawColumnCell(Rect, DataCol, Column, State);
end;

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

Статьи по Delphi (006)…
procedure TForm2.DrawShadows(WDepth, HDepth : Integer);
var
  Dst, RgnBox: TRect;
  hOldDC: HDC;
  OffScreen: TBitmap;
  Pattern: TBitmap;
  Bits: array [0..7] of WORD;
begin
  Bits[0]:=$0055;
  Bits[1]:=$00aa;
  Bits[2]:=$0055;
  Bits[3]:=$00aa;
  Bits[4]:=$0055;
  Bits[5]:=$00aa;
  Bits[6]:=$0055;
  Bits[7]:=$00aa;
  hOldDC:=Canvas.Handle;
  Canvas.Handle:=GetWindowDC(Form1.Handle);
  OffsetRgn(ShadeRgn, WDepth, HDepth);
  GetRgnBox(ShadeRgn, RgnBox);
  Pattern:=TBitmap.Create;
  Pattern.ReleaseHandle;
  Pattern.Handle:=CreateBitmap(8, 8, 1, 1, @(Bits[0]));
  Canvas.Brush.Bitmap:=Pattern;
  OffScreen:=TBitmap.Create;
  OffScreen.Width:=RgnBox.Right-RgnBox.Left;
  OffScreen.Height:=RgnBox.Bottom-RgnBox.Top;
  Dst:=Rect(0, 0, OffScreen.Width, OffScreen.Height);
  OffsetRgn(ShadeRgn, 0, -RgnBox.Top);
  FillRgn(OffScreen.Canvas.Handle, ShadeRgn, Canvas.Brush.Handle);
  OffsetRgn(ShadeRgn, 0, RgnBox.Top);
  // BitBlt работает быстрее CopyRect
  BitBlt(OffScreen.Canvas.Handle, 0, 0, OffScreen.Width, OffScreen.Height,
  Canvas.Handle, RgnBox.Left, RgnBox.Top, SRCAND);
  Canvas.Brush.Color:=clBlack;
  FillRgn(Canvas.Handle, ShadeRgn, Canvas.Brush.Handle);
  BitBlt(Canvas.Handle, RgnBox.Left, RgnBox.Top, OffScreen.Width,
  OffScreen.Height, OffScreen.Canvas.Handle, 0, 0, SRCPAINT);
  OffScreen.Free;
  Pattern.Free;
  OffsetRgn(ShadeRgn, -WDepth, -HDepth);
  ReleaseDC(Form1.Handle, Canvas.Handle);
  Canvas.Handle:=hOldDC;
end;

Комментарии: Функция рисует тень сложной формы на форме Form2 (извиняюсь за стиль). Для определения формы тени используется регион ShadeRgn, который был создан где-то раньше (например в OnCreate). Относительно регионов см. Win32 API.

Изобразить эффект разбрызгивания.

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

procedure Spray(Canvas: TCanvas; x, y, r: Integer; Color: TColor);
 var
   rad, a: Single;
   i: Integer;
 begin
   for i := 0 to 100 do
   begin
     a   := Random * 2 * pi;
     rad := Random * r;
     Canvas.Pixels[x + Round(rad * Cos(a)), y + Round(rad * Sin(a))] := Color;
   end;
 end;
 procedure TForm1.Image1MouseMove(Sender: TObject; Shift: TShiftState; X,
   Y: Integer);
 begin
   if ssLeft in Shift then Spray(Image1.Canvas, x, y, 40, clRed);
 end;
 procedure TForm1.Image1MouseDown(Sender: TObject; Button: TMouseButton;
   Shift: TShiftState; X, Y: Integer);
 begin
   if ssLeft in Shift then Spray(Image1.Canvas, x, y, 40, clRed);
 end;

Отображение текста с тегами форматирования.

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

{ **** UBPFD *********** by delphibase.endimus.com ****
>> Отображение текста с тегами форматирования
Рисует строку текста, содержащую теги форматирования, походие на теги HTML.
Поддерживаются следующие теги:
<b>..</b> Полужирный
<i>..</i> Наклонный
<u>..</u> Подчёркнутый
<s>..</s> Перечёркнутый
<big[ n]>..</big> Увеличить ращмер шрифта на n единиц (по умолчанию 1)
<small[ n]>..</small> Уменьшить шрифт на n единиц (по умолчанию 1)
<sub>..</sub> Нижний индекс
<sup>..</sup> Верхний индекс
Для правильного отображения внутри тегов <sub> и <sup> не должно располагаться других тегов
<font name="Имя шрифта" size="Размер" color="Цвет" charset="Кодовая страница">..</font> 
Установка параметров шрифта.
Размер шрифта указывается как для свойства TFont.Size, а не как в HTML. 
В качестве цвета можно указывать либо константы clXXXX либо числа в формате #RRGGBB, 
где RR GG и ВВ соответственно шестнадцатеричные значения 00..FF красной, 
зелёной и синей составляющей, HTML цвета не поддерживаются. В параметре charset 
указываются константы XXXX_CHARSET, например RUSSIAN_CHARSET или ANSI_CHARSET
Зависимости: Windows, SysUtils, Classes, Graphics, Dim
Автор:       Dimka Maslov, mainbox@endimus.com, ICQ:148442121, Санкт-Петербург
Copyright:   Dimka Maslov
Дата:        17 марта 2004 г.
***************************************************** }
unit HtDraw;
interface
uses Windows, SysUtils, Classes, Graphics, Dim;
type
  TTag = class;
  THtDraw = class(TObject)
  private
    FTag: TTag;
    FText: TString;
    procedure SetText(Value: TString);
  public
    function Draw(Canvas: TCanvas; X, Y: Integer): Integer;
    // Canvas - устройство для отображения текста.
    // в свойстве Canvas.Font задаются начальные параметры шрифта
    // X, Y - начальные координаты
    // Функция возвращает сумму X и ширины выведенного текста
    property Text: TString read FText write SetText;
    // Задаёт текст для отображения. В отличие от свойств компонентов,
    // присвоение значения этого свойства не приводит к автоматической
    // перерисовке. Необходимо вызывать метод Draw
    constructor Create(const AText: TString);
    destructor Destroy; override;
  end;
Полный текст модулей располагается по адресам: HtDraw: http://downloads.endimus.com/htdraw.zip Dim : http://downloads.endimus.com/dimpas.zip

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

var
  HtDraw: THtDraw;
begin
  HtDraw :=
    THtDraw.Create('<b>T</b><i>e</i><u>s</u><s>t</s>');
  try
    HtDraw.Draw(Canvas, 10, 10);
  finally
    HtDraw.Free;
  end;
end;

Ввыводим текст и графику, используя DelphiX.

Встречаются два квакера. - Ну как жизнь? - Да так, процентов на тридцать.

Итак, цель нашего первого урока - вывести текст и графику. Сначала определимся, куда же пихать эти самые текст и графику. Сначала поставьте на форму DXDraw, при этом в свойствах, в разделе "Options" измените "doFullScreen" и "doFlip" на "True", чтобы при запуске программа заполняла весь экран. Запустите и проверьте. Если вы видите черный экран, все нормально. Выходить придется Ctrl-Alt-Dеl'ом, так как процедуры выхода из программы мы еще не написали. "Рабочий стол" готов.

Теперь нарисуйте какую-нибудь картинку, по размерам экрана. Я возьму "Космос"( черный экран и белые точки, рисуется распылителем в Paint'e), сохраняем, как "cosmos.bmp". Вроде этого:

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

Теперь нарисуем то, что должно будет двинаться по "космосу" в следующих уроках, а пока это самое будет просто отображаться. Я опять же нарисую в Раint'у летающую тарелку(жирный овал,"tarelka.bmp"), вроде этого:

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

Копируем эти рисунки, в папку с Дельфи-проектом. Ставим на форму DXSpriteEngine и DXImageList. Выбираем в их свойстве "DXDraw" "DXDraw1", уже сконфигурированный нами в предыдущем абзаце. В свойствах Имаг-листа(DXImageList) выбираем "Items". В раскрывшемся окне создаем два объекта, назовем их "cosmos" и "tarelka", в свойстве "Picture" укажем путь к "cosmos.bmp" и "tarelka.bmp" соответственно.

Теперь самое сложное: поскольку ДельфИкс создавал какой-то, хоть и очень умный, но один человек, а не корпорация, нам придется ВРУЧНУЮ прописывать класс спрайтов(рисунков). Открываем редактор кода, в части "Implementation", после "Var" пишем "type" и с новой строчки:

TCosmos = class(TImageSprite)
public
  constructor Create(AParent: TSprite); override;
end;
TTarelka = class(TImageSprite)
public
  constructor Create(AParent: TSprite); override;
end;

Разбираемся: "Tcosmos" - имя будущего класса, "Class(TImageSprite)" - указывает, что будущий класс относится к ImаgеSрritе'ам, "Public" - указывает, что следующий раздел глобальный, "Constructor" - указывает, что следующая процедура относится к подклассу конструкторов, "Create(AParent: TSprite); Override;" - надо заучить как молитву, т.к. эта процедурка будет создавать ВСЕ спрайты в ваших программах, "End;" обозначает, что описание класса завершено.

Но процедуры мало Описать, их надо ещё и ПРОписать. В этом же разделе, после описания всех классов, пишем:

constructor Tcosmos.Create(AParent: TSprite);
begin
  inherited Create(AParent);
  Image := Form1.ImageList1.Items.Find('cosmos');
  X := x;
  Y := x;
  Z := z;
  Width := Image.Width;
  Height := Image.Height;
  PixelCheck := True;
end;

Учить точно так же, как и описание этого самого конструктора. Изменятся будут только подчеркнутые элементы. "Form1.ImageList1.Items.Find('cosmos')" - путь к спрайту "cosmos", меняться будут толко имена спрайтов. "x" - координата по горизонтали, у всех фоновых рисунков равна единице. "y" - координата по вертикали, у всех фоновых рисунков также равна единице. "z" - глубина рисунка, при наслоении двух спрайтов отображается тот, у которого "z" больше, у всех фоновых рисунков равна минус единице. Также пишем и тарелку:

constructor Ttarelka.Create(AParent: TSprite);
begin
  inherited Create(AParent);
  Image := Form1.ImageList1.Items.Find('tarelka');
  X := 50;
  Y := 50;
  Z := 1;
  Width := Image.Width;
  Height := Image.Height;
  PixelCheck := True;
end;

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

Теперь перейдем непосредственно к выводу на экран. Создаем DXTimer, в событие onTimer пишем:

if not DXDraw.CanDraw then
  exit;
DXDraw.Surface.Fill(0);
SpriteEngine.Move(1000 div 10);
SpriteEngine.Dead;
SpriteEngine.Draw;
with DXDraw.Surface.Canvas do
begin
  Brush.Style := bsClear;
  Font.Color := clWhite;
  Font.Size := 8;
  Textout(20, 3, 'Так выводится текст под ДельфИкс);
  Release;
End;
DXDraw.Flip;

Разбираем: а разбирать тут и нечего, учить надо, это процедура инициализации DirectX! Второй абзац - процедура вывода текста, последняя строка отрисовывает все объекты. Теперь смотрив события формы и создаем обработчик события onCreate. Пишем:

Ttarelka.create(SpriteEngine.Engine);
Tcosmos.create(SpriteEngine.Engine);

Запускаем, и видим тарелку на звездном фоне!

Если не видим, то тут три возможности:

обратиться к офтальмологу включить монитор проверить формат рисунков: ДельфИкс поддерживает только .bmp и .dib, если у вас рисунок другого формата, нужно пойти на хитрость: в Picture Editor'e надо сначала нажать "copy", а потом "paste", тогда оба рисунка сконвертируются в формат .bmp

Готовую программу можно скачать здесь или на сайте автора http://instrumentari.narod.ru и свериться, если что-то не работает.

Рисование текста в DBGrid.

Решение всех жизненных проблем находится на интернете. Надо только уметь хорошо искать.

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

procedure TForm1.DBGrid1DrawDataCell(Sender: TObject; const Rect:
  TRect; Field: TField; State: TGridDrawState);
  { ПРИМЕЧАНИЕ: Свойство DefaultDrawing компонента
  Grid должно быть установлено в False }
begin
  { если имя поля - "NAME" }
  if Field.FieldName = 'NAME' then
    { изменяем цвет шрифта на красный }
    (Sender as TDBGrid).Canvas.Font.Color := clRed;
  { выводим текст в табличной сетке }
  (Sender as TDBGrid).Canvas.TextRect(Rect, Rect.Left + 2,
    Rect.Top + 2, Field.AsString);
end;

Рисование текста в DBGrid 2.

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

Вам необходимо обработать событие OnDrawDataCell, например так:

procedure TForm1.DBGrid1DrawDataCell(Sender: TObject; const 
  Rect: TRect; Field: TField; State: TGridDrawState);
begin
  if Field.FieldName = 'SERIAL' then
    if (Field as TStringField).Value = 'НЕИЗВЕСТНО' then
      with (Sender as TDBGrid).Canvas do 
      begin
        Brush.Color := clRed;
        Font.Style := [fsItalic];
        Font.Color := clAqua;
        FillRect(Rect);
        TextOut(Rect.Left, Rect.Top, Field.AsString);
      end;
end;

....BTW, выключите DefaultDrawing.

Вывод текста на канве картинки.

При использовании BitBtn Caption(текст) и картинка(bitmap) из файла не видны одновременно. Почему?

Это может происходить если картинка слишком велика. Класс TBitBtn сначала рисует картинку, а затем выводит текст над, под, слева или справа от картинки (в завивимости от свойства Layout). Если размер картинки такой же как у всей кнопки для вывода текста просто не остается места. Если Вам нужно получить кнопку такого же размера как Ваша картинка и видеть при этом надпись на кнопке Вам придется выводить текст надписи непосредственно на канву картинки.

var
  bm : TBitmap;
  OldBkMode : integer;
begin
  bm := TBitmap.Create;
  bm.Width := BitBtn1.Glyph.Width;
  bm.Height := BitBtn1.Glyph.Height;
  bm.Canvas.Draw(0, 0, BitBtn1.Glyph);
  OldBkMode := SetBkMode(bm.Canvas.Handle, Transparent);
  bm.Canvas.TextOut(0, 0, 'The Caption');
  SetBkMode(bm.Canvas.Handle, OldBkMode);
  BitBtn1.Glyph.Assign(bm);
end; 

Как нарисовать Bitmap с прозрачностью.

procedure DrawTransparentBmp(Cnv: TCanvas; x,y: Integer; Bmp: TBitmap; clTransparent: TColor);
var
  bmpXOR, bmpAND, bmpINVAND, bmpTarget: TBitmap;
  oldcol: Longint;
begin
  try
    bmpAND := TBitmap.Create;
    bmpAND.Width := Bmp.Width;
    bmpAND.Height := Bmp.Height;
    bmpAND.Monochrome := True;
    oldcol := SetBkColor(Bmp.Canvas.Handle, ColorToRGB(clTransparent));
    BitBlt(bmpAND.Canvas.Handle, 0, 0, Bmp.Width, Bmp.Height, Bmp.Canvas.Handle, 0, 0, SRCCOPY);
    SetBkColor(Bmp.Canvas.Handle, oldcol);
    bmpINVAND := TBitmap.Create;
    bmpINVAND.Width := Bmp.Width;
    bmpINVAND.Height := Bmp.Height;
    bmpINVAND.Monochrome := True;
    BitBlt(bmpINVAND.Canvas.Handle, 0, 0, Bmp.Width, Bmp.Height, bmpAND.Canvas.Handle, 0, 0, NOTSRCCOPY);
    bmpXOR := TBitmap.Create;
    bmpXOR.Width := Bmp.Width;
    bmpXOR.Height := Bmp.Height;
    BitBlt(bmpXOR.Canvas.Handle, 0, 0, Bmp.Width, Bmp.Height, Bmp.Canvas.Handle, 0, 0, SRCCOPY);
    BitBlt(bmpXOR.Canvas.Handle, 0, 0, Bmp.Width, Bmp.Height, bmpINVAND.Canvas.Handle, 0, 0, SRCAND);
    bmpTarget := TBitmap.Create;
    bmpTarget.Width := Bmp.Width;
    bmpTarget.Height := Bmp.Height;
    BitBlt(bmpTarget.Canvas.Handle, 0, 0, Bmp.Width, Bmp.Height, Cnv.Handle, x, y, SRCCOPY);
    BitBlt(bmpTarget.Canvas.Handle, 0, 0, Bmp.Width, Bmp.Height, bmpAND.Canvas.Handle, 0, 0, SRCAND);
    BitBlt(bmpTarget.Canvas.Handle, 0, 0, Bmp.Width, Bmp.Height, bmpXOR.Canvas.Handle, 0, 0, SRCINVERT);
    BitBlt(Cnv.Handle, x, y, Bmp.Width, Bmp.Height, bmpTarget.Canvas.Handle, 0, 0, SRCCOPY);
  finally
    bmpXOR.Free;
    bmpAND.Free;
    bmpINVAND.Free;
    bmpTarget.Free;
  end;
end;

Как с помощью функции Textout вывести на канве прозрачный текст.

Вот небольшой участок кода из купленного мною CD-ROM "How To Book". Файл с именем "HowUtils.Pas" содержит реализацию алгоритма "потухания" текста и обратного ему эффекта на холсте, откуда вы можете почерпнуть необходимую вам информацию.

function TFadeEffect.FadeInText(Target: TCanvas; X, Y: integer; FText: string):
  TRect;
var
  Pic: TBitmap;
  W, H: integer;
  PicRect, TarRect: TRect;
begin
  Pic := TBitmap.Create;
  Pic.Canvas.Font := Target.Font;
  W := Pic.Canvas.TextWidth(FText);
  H := Pic.Canvas.TextHeight(FText);
  Pic.Width := W;
  Pic.Height := H;
  PicRect := Rect(0, 0, W, H);
  TarRect := Rect(X, Y, X + W, Y + H);
  Pic.Canvas.CopyRect(PicRect, Target, TarRect);
  SetBkMode(Pic.Canvas.Handle, Transparent);
  Pic.Canvas.TextOut(0, 0, FText);
  FadeInto(Target, X, Y, Pic);
  Pic.Free;
  FadeInText := TarRect;
end;
procedure TFadeEffect.FadeOutText(Target: TCanvas; TarRect: TRect; Orig:
  TBitmap);
var
  Pic: TBitmap;
  PicRect: TRect;
begin
  Pic := TBitmap.Create;
  Pic.Width := TarRect.Right - TarRect.Left;
  Pic.Height := TarRect.Bottom - TarRect.Top;
  PicRect := Rect(0, 0, Pic.Width, Pic.Height);
  Pic.Canvas.CopyRect(PicRect, Orig.Canvas, TarRect);
  FadeInto(Target, TarRect.Left, TarRect.Top, Pic);
  Pic.Free;
end;

Драйвера на Delphi.

Автор: Павел Компиляция данного примера возможна только с Delphi 3. Delphi 2 не был опробован в связи с его отсутствием, объектные фалы созданные Delphi 4 отвергаются Microsoft® Linker 5.12.8181 как файлы неизвестного формата. При написании данного материала были использованы Microsoft® Macro Assembler ver. 6.11d и Microsoft® Incremental Linker ver. 5.12.8181 из поставки Windows 98DDK Введение VxD драйвера делятся два типа: Статические Загружаются в память при загрузке операционной системы и присутствуют в памяти постоянно Динамические Загружаются в память при первом обращении к драйверу и могут быть выгружены при закрытии последнего дескриптора VxD. Практически возможно создание драйвера поддерживающего оба типа загрузки. Нам необходим динамически загружаемый VxD драйвер (далее "VxD") т.к. такой драйвер можно будет без перезагрузки Windows загружать из Win32® приложений используя процедуру CreateFile(). Когда Win32 приложение открывает дескриптор для VxD, виртуальное устройство VWIN32 используя сервис LoadDevice загружает VxD-драйвер в память и посылает информационное сообщение W32_DEVICEIOCONTROL загруженному VxD. Таким образом для построения VxD необходимо обрабатывать как минимум три системных сообщения: SYS_DYNAMIC_DEVICE_INIT SYS_DYNAMIC_DEVICE_EXIT W32_DEVICEIOCONTROL. Собщение SYS_DYNAMIC_DEVICE_INIT посылается при попытке динамической загрузки VxD, SYS_DYNAMIC_DEVICE_EXIT посылается при попытке динамической выгрузке. Из обработчиков сообщений для подтверждения успеха необходимо вернуть VXD_SUCCESS в регистре AX Сообщение W32_DEVICEIOCONTROL имеет следующие значения для параметра dwService DIOC_OPEN - посылается посылается при открытии дескриптора VxD функцией CreateFile() только после SYS_DYNAMIC_DEVICE_INIT. В случае успеха необходимо вернуть NO_ERROR (0); DIOC_CLOSEHANDLE - посылается при закрытии дескриптора VxD функцией API CloseHandle() и только перед SYS_DYNAMIC_DEVICE_EXIT (Значение > 0) - Номер функции, заданный в параметре dwIoControlCode при обращении к VxD функцией API DeviceIoControl Загрузочный модуль (vxdmain.asm)

При обращении к процедурам находящимся в модулях Delphi надо учесть для fastcall-процедур к имени добавляется в начале символ "@".

...
extrn SysDynamicDeviceInit :PROC
extrn SysDynamicDeviceExit :PROC
extrn W32DeviceIoControl   :PROC
...
Control_0	proc
	cmp	eax, SYS_DYNAMIC_DEVICE_INIT
	jnz	short chkSysDynExit
	call	SysDynamicDeviceInit
	cmp	eax, 1
	retn	
;-------------
chkSysDynExit:
	cmp	eax, SYS_DYNAMIC_DEVICE_EXIT
	jnz	short chkDevIOCtl
	call	SysDynamicDeviceExit
	cmp	eax, 1
	retn	
;-------------
chkDevIOCtl:
	cmp	eax, W32_DEVICEIOCONTROL
	jnz	short loc_ret
	push	esi
	push	edx
	push	ebx
	push	ecx
	call	W32DeviceIoControl
	cmp	eax, 1
	retn	
;-------------
loc_ret:
	clc	
	retn	
Control_0	endp
...

Delphi создает код для инициализации/деинициализации модулей, обращаясь к внешним процедурам HandleFinaly и initialization даже если блоки initilization и finalization в модуле отсутствуют. Создадим пустую "заглушку" для этих процедур и объявим их доступными для внешних модулей.

...
    Public  @@HandleFinally
    Public  @initialization
...
@@HandleFinally:
@initialization:
	ret
...
end.
Процедурный модуль (vxdProcs.pas)
procedure ShellMessage(Handle, Flags: integer; const Message, Caption: PChar;
  Callback, ReferenceData: pointer); stdcall; assembler;
asm
  mov ebx, Handle  // virtual machine handle
  mov eax, Flags  // message box flags
  mov ecx, Message  // address of message text
  mov edi, Caption  // address of caption text
  mov esi, Callback  // address of callback
  mov edx, ReferenceData  // reference data for callback
  int 20H   // VxDCall
  dd  170004h   // Shell_Message
end;
const
  Copyright: PChar = '(c) 1999 Emil Biserov, ' +
  'fatty777@mail.ru, http://dinfo.da.ru';
function SysDynamicDeviceInit: INTEGER;
begin
  ShellMessage(0, $10, Copyright, 'SysDynInit: Hello from Delphi VxD !!!', nil,
    nil);
  Result := VXD_SUCCESS;
end;
function SysDynamicDeviceExit: INTEGER;
begin
  ShellMessage(0, $10, Copyright, 'SysDynDevExit: Bye from Delphi VxD !!!', nil,
    nil);
  Result := VXD_SUCCESS;
end;
function W32DeviceIoControl(dwService: INTEGER;
  dwDDB: INTEGER;
  hDevice: INTEGER;
  lpDIOCParms: pointer): INTEGER;
begin
  ShellMessage(0, $10, Copyright, 'W32DevIOCtl', nil, nil);
  if (dwService = DIOC_OPEN) then
  begin
    Result := NO_ERROR;
  end
  else if (dwService = DIOC_CLOSEHANDLE) then
  begin
    Result := VXD_SUCCESS;
  end
  else if (dwService > MAX_PASVXD_W32_API) then
  begin
    Result := ERROR_NOT_SUPPORTED;
  end
  else
  begin
    Result := VXD_SUCCESS;
  end;
end;
Инструмент для загрузки/выгрузки VxD Представляет из себя простую форму с двумя кнопками. Приведу лишь методы для открытия и закрытия VxD драйвера.
const
  VxDName = '\\.\DELPHIIO.VXD';
  ...
function TVxDTestForm.OpenVxDDriver: boolean;
begin
  HVxDHandle := CreateFile(VxDName, 0, 0, nil, 0, FILE_FLAG_DELETE_ON_CLOSE, 0);
  Result := HVxDHandle <> INVALID_HANDLE_VALUE;
end;
procedure TVxDTestForm.CloseVxDDriver;
begin
  if HVxDHandle <> INVALID_HANDLE_VALUE then
  begin
    CloseHandle(HVxDHandle);
    HVxDHandle := INVALID_HANDLE_VALUE;
  end;
end;
Выгрузку неиспользуемого модуля можно производить автоматически, указав в параметрах CreateFile(,,,,,FILE_FLAG_DELETE_ON_CLOSE,). В данном случае система при каждом открытии дескриптора к VxD будет увеличивать внутренний счетчик использования на 1 и вычитать 1 при закрытии дескриптора. При значении счетчика равном нулю VxD будет автоматически выгружен. Выводы Драйвера на Delphi писать можно. Но вот только стоит ли?

Использование VxD имеет смысл только для использования Windows 3.1 и Windows 95. Да и доступ к аппаратным ресурсам в них гораздо прозрачнее чем Windows NT/2000. Windows 98, Windows NT и Windows2000 поддерживают новую модель драйверов WDM 1.0/2.0, использующую формат PE (Portable Executable) и (предположительно) проблем будет меньше, чем с VxD и его LE (Linear Executable) форматом драйверов.

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

Автор: Xavier Pacheco

unit MainFrm;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, FileCtrl;
type
  TMainForm = class(TForm)
    btnGetDriveTypes: TButton;
    lbDrives: TListBox;
    lblSectPerClust2: TLabel;
    lblBytesPerSector2: TLabel;
    lblNumFreeClusters2: TLabel;
    lblTotalClusters2: TLabel;
    lblSectPerCluster: TLabel;
    lblBytesPerSector: TLabel;
    lblNumFreeClust: TLabel;
    lblTotalClusters: TLabel;
    lblFreeSpace2: TLabel;
    lblTotalDiskSpace2: TLabel;
    lblFreeSpace: TLabel;
    lblTotalDiskSpace: TLabel;
    procedure btnGetDriveTypesClick(Sender: TObject);
    procedure lbDrivesClick(Sender: TObject);
  end;
var
  MainForm: TMainForm;
implementation
{$R *.DFM}
procedure TMainForm.btnGetDriveTypesClick(Sender: TObject);
var
  i: Integer;
  C: string;
  DType: Integer;
  DriveString: string;
begin
  { Loop from A..Z to determine available drives }
  for i := 65 to 90 do
  begin
    // Format a string to represent the root directory.
    C := chr(i) + ':\';
    { Call the GetDriveType() function which returns an integer
      value representing one of the types shown in the case statement
      below }
    DType := GetDriveType(PChar(C));
    { Based on the drive type returned, format a string to add to
      the listbox displaying the various drive types. }
    case DType of
      0: DriveString := C + ' The drive type cannot be determined.';
      1: DriveString := C + ' The root directory does not exist.';
      DRIVE_REMOVABLE: DriveString :=
        C + ' The drive can be removed from the drive.';
      DRIVE_FIXED: DriveString :=
        C + ' The disk cannot be removed from the drive.';
      DRIVE_REMOTE: DriveString :=
        C + ' The drive is a remote (network) drive.';
      DRIVE_CDROM: DriveString := C + ' The drive is a CD-ROM drive.';
      DRIVE_RAMDISK: DriveString := C + ' The drive is a RAM disk.';
    end;
    // Only add drive types that can be determined.
    if not ((DType = 0) or (DType = 1)) then
      lbDrives.Items.AddObject(DriveString, Pointer(i));
  end;
end;
procedure TMainForm.lbDrivesClick(Sender: TObject);
var
  RootPath: string; // Holds the drive root path
  SectorsPerCluster: DWord; // Sectors per cluster
  BytesPerSector: DWord; // Bytes per sector
  NumFreeClusters: DWord; // Number of free clusters
  TotalClusters: DWord; // Total clusters
  DriveByte: Byte; // Drive byte value
  FreeSpace: Int64; // Free space on drive
  TotalSpace: Int64; // Total drive space.
begin
  with lbDrives do
  begin
    { Convert the ascii value for the drive letter to a valid drive number:
        1 = A, 2 = B, etc. by subtracting 64 from the ascii value. }
    DriveByte := Integer(Items.Objects[ItemIndex]) - 64;
    { First create the root path string }
    RootPath := chr(Integer(Items.Objects[ItemIndex])) + ':\';
    { Call GetDiskFreeSpace to obtain the drive information }
    if GetDiskFreeSpace(PChar(RootPath), SectorsPerCluster,
      BytesPerSector, NumFreeClusters, TotalClusters) then
    begin
      { If this function is successful, then update the labels to
        display the disk information. }
      lblSectPerCluster.Caption := Format('%.0n', [SectorsPerCluster * 1.0]);
      lblBytesPerSector.Caption := Format('%.0n', [BytesPerSector * 1.0]);
      lblNumFreeClust.Caption := Format('%.0n', [NumFreeClusters * 1.0]);
      lblTotalClusters.Caption := Format('%.0n', [TotalClusters * 1.0]);
      // Obtain the available disk space
      FreeSpace := DiskFree(DriveByte);
      TotalSpace := DiskSize(DriveByte);
      lblFreeSpace.Caption := Format('%.0n', [FreeSpace * 1.0]);
      { Calculate the total disk space }
      lblTotalDiskSpace.Caption := Format('%.0n', [TotalSpace * 1.0]);
    end
    else
    begin
      { Set labels to display nothing }
      lblSectPerCluster.Caption := 'X';
      lblBytesPerSector.Caption := 'X';
      lblNumFreeClust.Caption := 'X';
      lblTotalClusters.Caption := 'X';
      lblFreeSpace.Caption := 'X';
      lblTotalDiskSpace.Caption := 'X';
      ShowMessage('Cannot get disk info');
    end;
  end;
end;
end.

Свой формат для TDateTimePicker.

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

uses
   ComCtrls;
 const
   DTM_SETFORMAT = $1005;
 procedure TForm1.Button1Click(Sender: TObject);
 var
   sFormat: string;
 begin
   DateTimePicker1.kind := dtkTime;
   // Don't show the seconds, Sekunden nicht anzeigen 
  SendMessage(DateTimePicker1.Handle, DTM_SETFORMAT, 0, Longint(PChar('hh:mm')));
   // To show AM/PM 
  SendMessage(DateTimePicker1.Handle, DTM_SETFORMAT, 0, Longint(PChar('hh:mm tt')));
   // 24-hour clock: Be sure to set Kind to dtkTime 
  DateTime_SetFormat(DateTimePickerTest.Handle, pChar('H:mm:ss'));
      // To show Date and Time, Datum und Zeit anzeigen 
  // Note that you can only edit the date or the time depending 
  // on the Kind (dtkTime or dtkDate). 
  SendMessage(DateTimePicker1.Handle, DTM_SETFORMAT, 0, Longint(PChar('dd/MM/yyyy hh:ss')));
   // You could also use the DateTime_SetFormat macro: 
  DateTime_SetFormat(DateTimePicker1.Handle, PChar('M/d/yyy'));
      // Instead of using the DateTime_SetFormat function, you can 
  // send a message to the control directly: 
  sFormat := 'dd-MMMM-yyyy';
   DateTimePicker1.Perform(DTM_SETFORMAT, 0, DWORD(sFormat));
 end;
 // Show the Week Number in a TDateTimePicker 
procedure DateToWeek(dtDate: TDateTime; var AWeek, AYear: Word);
 const
   FIRST_WEEKDAY: Integer = 2;
   FIRST_WEEKDATE: Integer = 4;
 var
   wMonth, wDay: Word;
 begin
   dtDate := dtDate - ((DayOfWeek(dtDate) - FIRST_WEEKDAY + 7) mod 7) + 7 - FIRST_WEEKDATE;
   DecodeDate(dtDate, AYear, wMonth, wDay);
   AWeek := (Trunc(dtDate - EncodeDate(AYear, 1, 1)) div 7) + 1;
 end;
 procedure TForm1.DateTimePicker1Change(Sender: TObject);
 var
   sFormat: string;
   wWeek, wYear: Word;
 begin
   DateToWeek(DateTimePicker1.date, wWeek, wYear);
   sFormat := 'dd/MM/yy Week:' + IntToStr(wWeek) + '';
   DateTimePicker1.Perform(DTM_SETFORMAT, 0, DWORD(sFormat));
 end;
 procedure TForm1.FormShow(Sender: TObject);
 begin
   DateTimePicker1Change(Self);
 end;

Автоматически открыть TDateTimePicker.

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

{ 
  There's no possibility to automatically 
  open a TDateTimePicker than to send a F4 to it.}
 { 
  Es gibt't keine Moglichkeit, einen TDateTimePicker automatisch zu offnen, 
  als F4 ihm zu senden 
}
 procedure TForm1.Button1Click(Sender: TObject);
 begin
   DateTimePicker1.Perform(WM_KEYDOWN, VK_F4, 0);
   DateTimePicker1.Perform(WM_KEYUP, VK_F4, 0);
 end;

Очистить дату в TDateTimePicker.

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

{ 
 Use the DateTime_SetFormat API macro (put CommCtrl in the uses clause) to set 
 the date to blank or a message. 
 Then in the DateTimePicker OnCloseUp event handler set the format to what you 
 want when a date has been selected, and the DTP will show the selected date. 
 Attach an OnClick handler if you want it to open when you 
 click on the edit control. 
}
 { 
 Verwende das DateTime_SetFormat API Makro von der CommCtrl Unit, um den TDateTimePicker 
 anfangs leer anzuzeigen oder um einen beliebigen String zu setzen. 
 Wenn das OnCloseUp Ereignis vom TDateTimePicker aufgerufen wird, kannst du ein 
 beliebiges Datums-Format setzen und der DateTimePicker zeigt es dann an. 
 Damit man mit einem Click auf das Edit-Control den TDateTimePicker цffnеn kann, kann man 
 zиsдtz1iсh einen OnClick Handler hinzиfьgеn, wo man dann den TDateTimePicker цffnеt. 
}
 uses
   CommCtrl;
 var
   FDTMDateEmpty: Boolean;
 procedure TForm1.FormCreate(Sender: TObject);
 begin
   DateTime_SetFormat(DateTimePicker1.Handle,  '''Choose a date''');
   FDTMDateEmpty := True;
 end;
 procedure TForm1.DateTimePicker1CloseUp(Sender: TObject);
 begin
   DateTime_SetFormat(DateTimePicker1.Handle,  PChar('dd.MM.yy'));
 end;
 procedure TForm1.DateTimePicker1Click(Sender: TObject);
 begin
   if FDTMDateEmpty then
   begin
     DateTimePicker1.Perform(WM_KEYDOWN, VK_F4, 0);
     DateTimePicker1.Perform(WM_KEYUP, VK_F4, 0);
   end;
 end;
 procedure TForm1.DateTimePicker1Change(Sender: TObject);
 begin
   FDTMDateEmpty := False;
 end;

Удалить отображение текущей даты из TDateTimePicker.

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

uses
   CommCtrl;
 procedure TForm1.DateTimePicker1DropDown(Sender: TObject);
 var
   wnd: HWND;
   Style: Integer;
 begin
   wnd := DateTime_GetMonthCal(DateTimePicker1.Handle);
   if wnd <> 0 then
   begin
     Style := GetWindowLong(wnd, GWL_STYLE);
     SetWindowLong(wnd, GWL_STYLE, Style or MCS_NOTODAY or MCS_NOTODAYCIRCLE);
   end;
 end;
 { 
  The calendar will still highlite the current day but the circle and the 
  today display at the bottom are gone. 
}

Показывать номера недели в TDateTimePicker.

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

uses
   CommCtrl;
 type
   THackCommonCalendar = class(TCommonCalendar);
   {...}
 procedure TForm1.DateTimePicker1DropDown(Sender: TObject);
 var
   Style: Integer;
   ReqRect: TRect;
   MaxTodayWidth: Integer;
 begin
   with THackCommonCalendar(Sender as TDateTimePicker) do
   begin
     // set style to include week numbers 
    Style := GetWindowLong(CalendarHandle, GWL_STYLE);
     SetWindowLong(CalendarHandle, GWL_STYLE, Style or MCS_WEEKNUMBERS);
     FillChar(ReqRect, SizeOf(TRect), 0);
     // get required rect 
    Win32Check(MonthCal_GetMinReqRect(CalendarHandle, ReqRect));
     // get max today string width 
    MaxTodayWidth := MonthCal_GetMaxTodayWidth(CalendarHandle);
     // adjust rect width to fit today string 
    if MaxTodayWidth > ReqRect.Right then
       ReqRect.Right := MaxTodayWidth;
     // set new height & width 
    SetWindowPos(CalendarHandle, 0, 0, 0, ReqRect.Right, ReqRect.Bottom,
       SWP_NOACTIVATE or SWP_NOMOVE or SWP_NOZORDER);
   end;
 end;

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

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

// For Win9x: 
//------------------------------------------- 
uses
   CommCtrl,
   IPCThrd; (from your Delphi\Demos\Ipcdemos directory)
 function GetDesktopListViewHandle: THandle;
 var
   S: String;
 begin
   Result := FindWindow('ProgMan', nil);
   Result := GetWindow(Result, GW_CHILD);
   Result := GetWindow(Result, GW_CHILD);
   SetLength(S, 40);
   GetClassName(Result, PChar(S), 39);
   if PChar(S) <> 'SysListView32' then Result := 0;
 end;
 procedure TForm1.Button1Click(Sender: TObject);
  type
    PInfo = ^TInfo;
    TInfo = packed record
      infoPoint: TPoint;
      infoText: array[0..255] of Char;
      infoItem: TLVItem;
      infoFindInfo: TLVFindInfo;
    end;
 var
    r : TRect;
    hWnd : THandle;
    i, iCount : Integer;
    Info: PInfo;
    SharedMem: TSharedMem;
 begin
   hWnd := GetDesktopWindow();
   GetWindowRect(hWnd,r);
   Memo.Lines.Add('Bottom: ' +  IntToStr(r.Bottom));
   Memo.Lines.Add('Right: ' + IntToStr(r.Right));
   hWnd := GetDesktopListViewHandle;
   iCount := ListView_GetItemCount(hWnd);
   Memo.Lines.Add('# Icons: ' + IntToStr(iCount));
   SharedMem := TSharedMem.Create('', SizeOf(TInfo));
   Info := SharedMem.Buffer;
    with Info^ do
    try
      infoItem.pszText := infoText;
      infoItem.cchTextMax := 255;
      infoItem.mask := LVIF_TEXT;
      try
        begin
          for i := 0 to iCount - 1 do
          begin
            infoItem.iItem := i;
            try
              ListView_GetItem(hWnd, infoItem);
              ListView_GetItemPosition(hWnd, I, infoPoint);
              Memo.Lines.Add('Icon: ' + infoText);
              Memo.Lines.Add('   X: ' + IntToStr(infoPoint.X));
              Memo.Lines.Add('   Y: ' + IntToStr(infoPoint.Y));
            except
            end;
          end;
        end;
      finally
      end;
    finally
      SharedMem.Free;
    end;
 end;
 // For NT, Win2k, XP: 
//------------------------------------------- 
// Unit to save/restore the positions of desktop icons to/from the registry) 
unit dipsdef;
 interface
 uses
   Windows, CommCtrl;
 const
   RegSubKeyName = 'Software\LVT\Desktop Item Position Saver';
 procedure RestoreDesktopItemPositions;
 procedure SaveDesktopItemPositions;
 implementation
 uses
   uvirtalloc, registry;
 procedure SaveListItemPosition(LVH : THandle; RemoteAddr : Pointer);
 var
   lvi : TLVITEM;
   lenlvi : integer;
   nb : integer;
   buffer : array [0..MAX_PATH] of char;
   Base : Pointer;
   Base2 : PByte;
   i, ItemsCount : integer;
   Apoint : TPoint;
   key : HKEY;
   Dummy : integer;
 begin
   ItemsCount := SendMessage(LVH, LVM_GETITEMCOUNT, 0, 0);
   Base := RemoteAddr;
   lenlvi := SizeOf(lvi);
   FillChar(lvi, lenlvi, 0);
   lvi.cchTextMax := 255;
   lvi.pszText := Base;
   inc(lvi.pszText, lenlvi);
   WriteToRemoteBuffer(@lvi, Base, 255);
   Base2 := Base;
   inc(Base2, Lenlvi);
   RegDeleteKey(HKEY_CURRENT_USER, RegSubKeyName);
   RegCreateKeyEx(HKEY_CURRENT_USER,
     PChar(RegSUbKeyName),
     0,
     nil,
     REG_OPTION_NON_VOLATILE,
     KEY_SET_VALUE,
     nil,
     key,
     nil);
   for i := 0 to ItemsCount - 1 do
   begin
     nb := SendMessage(LVH, LVM_GETITEMTEXT, i, LParam(Base));
     ReadRemoteBuffer(Base2, @buffer, nb + 1);
     FillChar(Apoint, SizeOf(Apoint), 0);
     WriteToRemoteBuffer(@APoint, Base2, SizeOf(Apoint));
     SendMessage(LVH, LVM_GETITEMPOSITION, i, LParam(Base) + lenlvi);
     ReadRemoteBuffer(Base2, @Apoint, SizeOf(Apoint));
     RegSetValueEx(key, @buffer, 0, REG_BINARY, @Apoint, SizeOf(APoint));
   end;
   RegCloseKey(key);
 end;
 procedure RestoreListItemPosition(LVH : THandle; RemoteAddr : Pointer);
 type
   TInfo = packed record
     lvfi : TLVFindInfo;
     Name : array [0..MAX_PATH] of char;
   end;
 var
   SaveStyle : Dword;
   Base : Pointer;
   Apoint : TPoint;
   key : HKey;
   idx : DWord;
   info : TInfo;
   atype : Dword;
   cbname, cbData : Dword;
   itemidx : DWord;
 begin
   SaveStyle := GetWindowLong(LVH, GWL_STYLE);
   if (SaveStyle and LVS_AUTOARRANGE) = LVS_AUTOARRANGE then
     SetWindowLong(LVH, GWL_STYLE, SaveStyle xor LVS_AUTOARRANGE);
   RegOpenKeyEx(HKEY_CURRENT_USER, RegSubKeyName, 0, KEY_QUERY_VALUE, key);
   FillChar(info, SizeOf(info), 0);
   Base := RemoteAddr;
   idx := 0;
   cbname := MAX_PATH;
   cbdata := SizeOf(APoint);
   while (RegEnumValue(key, idx, info.Name, cbname, nil, @atype, @Apoint, @cbData) <>
     ERROR_NO_MORE_ITEMS) do
   begin
     if (atype = REG_BINARY) and (cbData = SizeOf(Apoint)) then
     begin
       info.lvfi.flags := LVFI_STRING;
       info.lvfi.psz := Base;
       inc(info.lvfi.psz, SizeOf(info.lvfi));
       WriteToRemoteBuffer(@info, Base, SizeOf(info.lvfi) + cbname + 1);
       itemidx := SendMessage(LVH, LVM_FINDITEM, - 1, LParam(Base));
       if itemidx > -1 then
         SendMessage(LVH, LVM_SETITEMPOSITION, itemidx, MakeLong(Apoint.x, Apoint.y));
     end;
     inc(idx);
     cbname := MAX_PATH;
     cbdata := SizeOf(APoint);
   end;
   RegCloseKey(key);
   SetWindowLong(LVH, GWL_STYLE, SaveStyle);
 end;
 function GetSysListView32: THandle;
 begin
   Result := FindWindow('Progman', nil);
   Result := FindWindowEx(Result, 0, nil, nil);
   Result := FindWindowEx(Result, 0, nil, nil);
 end;
 procedure SaveDesktopItemPositions;
 var
   pid : integer;
   rembuffer : PByte;
   hTarget : THandle;
 begin
   hTarget := GetSysListView32;
   GetWindowThreadProcessId(hTarget, @pid);
   if (hTarget = 0) or (pid = 0) then
     Exit;
   rembuffer := CreateRemoteBuffer(pid, $FFF);
   if Assigned(rembuffer) then
   begin
     SaveListItemPosition(hTarget, rembuffer);
     DestroyRemoteBuffer;
   end;
 end;
 procedure RestoreDesktopItemPositions;
 var
   hTarget : THandle;
   pid : DWord;
   rembuffer : PByte;
 begin
   hTarget := GetSysListView32;
   GetWindowThreadProcessId(hTarget, @pid);
   if (hTarget = 0) or (pid = 0) then
     Exit;
   rembuffer := CreateRemoteBuffer(pid, $FFF);
   if Assigned(rembuffer) then
   begin
     RestoreListItemPosition(hTarget, rembuffer);
     DestroyRemoteBuffer;
   end;
 end;
 end.
 {----------------------------------------------------------}
 unit uvirtalloc;
 interface
 uses
   Windows, SysUtils;
 function CreateRemoteBuffer(Pid : DWord; Size: Dword): PByte;
 procedure WriteToRemoteBuffer(Source : PByte;
                                Dest : PByte;
                                Count : Dword);
 function ReadRemoteBuffer (Source : PByte;
                             Dest : PByte;
                             Count : Dword): Dword;
 procedure DestroyRemoteBuffer;
 implementation
 var
   hProcess : THandle;
   RemoteBufferAddr: PByte;
   BuffSize : DWord;
 function CreateRemoteBuffer;
 begin
   RemoteBufferAddr := nil;
   hProcess := OpenProcess(PROCESS_ALL_ACCESS, FALSE, Pid);
   if (hProcess = 0) then
     RaiseLastWin32Error;
   Result := VirtualAllocEx(hProcess,
                             nil,
                             Size,
                             MEM_COMMIT,
                             PAGE_EXECUTE_READWRITE);
   Win32Check(Result <> nil);
   RemoteBufferAddr := Result;
   BuffSize := Size;
 end;
 procedure WriteToRemoteBuffer;
 var
   BytesWritten: Dword;
 begin
  if hProcess = 0 then
    Exit;
  Win32Check(WriteProcessMemory(hProcess,
                                 Dest,
                                 Source,
                                 Count,
                                 BytesWritten));
 end;
 function ReadRemoteBuffer;
 begin
   Result := 0;
   if hProcess = 0 then
      Exit;
   Win32Check(ReadProcessMemory(hProcess,
                                 Source,
                                 Dest ,
                                 Count,
                                 Result));
 end;
 procedure DestroyRemoteBuffer;
 begin
    if (hProcess > 0)  then
      begin
        if Assigned(RemoteBufferAddr) then
          Win32Check(Boolean(VirtualFreeEx(hProcess,
                                           RemoteBufferAddr,
                                           0,
                                           MEM_RELEASE)));
        CloseHandle(hProcess);
      end;
 end;
 end.
 {----------------------------------------------------------}
 Other Source for NT, Win2k, XP only:
 http://www.luckie-online.de/programme/luckiedipssfx.exe 
(Complete demo to save/restore the positions of desktop icons, nonVCL)

Выделить родительский компонент, когда он невидим, в Design Time.

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

When sie z.B. einen TPanel auf einen Formular auf alClient GгцЯе eingestellt haben, dann кцnnеn Sie das Formular Objekt darunter nicht mehr mit der Maus selektieren. Aber so geht''s: 1. Panel anclicken 2. ESC dгьскеn ESC selektiert automatisch die Eltern-Komponent des aktiven Objekts, also das TForm in diesem Fall. Auch bei "verschachtelte Komponente" кцnnеn Sie wiederholt auf ESC dгьскеn, um jeweils eine Ebene "hцhег" zu landen bzw. zu selektieren. Es ist auch manchmal schneller als die Component im IDE Explorer zu suchen... {**************************************************************} Maybe you have a Panel on your Form with Alignement:=alClient. You cannot select the underlying Form with the Mouse. The quick way to select it: 1. select the panel 2. Press ESC It works with all Child components. Also many times in a row. ESC always selects the Parent of the activ component. This''s sometimes faster than looking for the control in the IDE Explorer...

Сохранить значение TDateTime в реестре.

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

uses
   Registry;
 // Write TDateTime to Registry 
procedure Reg_WriteDateTime(dwRootKey: DWord; const sKey: string; const sField: string; aDate: TDateTime);
 begin
   with TRegistry.Create do
   try
     RootKey := dwRootKey;
     if OpenKey(sKey, True) then
     begin
       try
         WriteBinaryData(sField, aDate, SizeOf(aDate));
       finally
         CloseKey;
       end;
     end;
   finally
     Free;
   end;
 end;
 // Read TDateTime from Registry 
function Reg_ReadDateTime(dwRootKey: DWord; const sKey: string; const sField: string) : TDateTime;
 begin
   Result := 0; // default Return value 
  with TRegistry.Create do
   begin
     RootKey := dwRootKey;
     if OpenKey(sKey, False) then
     begin
       try
         ReadBinaryData(sField, Result, SizeOf(Result));
       finally
         CloseKey;
       end;
     end;
     Free;
   end;
 end;
 // Example: 
// Write DateTimePicker1's DateTime to Registry 
procedure TForm1.Button1Click(Sender: TObject);
 begin
    Reg_WriteDateTime(HKEY_CURRENT_USER, 'Software\TestXYZ\','DateTime',DateTimePicker1.DateTime);
 end;
 // Set DateTimePicker1's DateTime from Registry 
procedure TForm1.Button2Click(Sender: TObject);
 var
  ATime: TDateTime;
 begin
   ATime := Reg_ReadDateTime(HKEY_CURRENT_USER, 'Software\TestXYZ\','DateTime');
   if ATime <> 0 then
     DateTimePicker1.DateTime := TDateTime(ATime);
 end;

Преобразовать TDateTime в Unix TimeStamp.

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

{ 
  Sometimes you want to communicate with mySQL or other databases using 
  the unix timestamp. To solve this difference you may want to convert your 
  TDateTime to the unix timestamp format and vice versa. 
  Um den in einigen Datenbanken verwendeten Unix Timestamp fur die Kommunikation 
  mit Delphi nutzbar zu machen, kann man mit dieser Unit das TDateTime Format 
  in den Unix Timestamp und umgekehrt umwandeln. 
}
 unit unix_utils;
 interface
 implementation
 const
   // Sets UnixStartDate to TDateTime of 01/01/1970 
  UnixStartDate: TDateTime = 25569.0;
 function DateTimeToUnix(ConvDate: TDateTime): Longint;
 begin
   //example: DateTimeToUnix(now); 
  Result := Round((ConvDate - UnixStartDate) * 86400);
 end;
 function UnixToDateTime(USec: Longint): TDateTime;
 begin
   //Example: UnixToDateTime(1003187418); 
  Result := (Usec / 86400) + UnixStartDate;
 end;
 end.

Получение координат области Рабочего стола, не скрытой Панелью задач.

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

{ **** UBPFD *********** by delphibase.endimus.com ****
>> Получение координат области Рабочего стола, не скрытой Панелью задач
Зависимости: Windows
Автор:       Dimka Maslov, mainbox@endimus.ru, ICQ:148442121, Санкт-Петербург
Copyright:   Dimka Maslov
Дата:        4 ноября 2002 г.
***************************************************** }
function GetDesktopRect: TRect;
var
  RgnDesktop, RgnTrayWnd: HRGN;
  function CreateWindowRgn(Handle: HWND): HRGN;
  var
    R: TRect;
  begin
    GetWindowRect(Handle, R);
    with R do
      Result := CreateRectRgn(Left, Top, Right, Bottom);
  end;
begin
  RgnDesktop := CreateWindowRgn(GetDesktopWindow);
  try
    RgnTrayWnd := CreateWindowRgn(FindWindow('Shell_TrayWnd', ''));
    try
      CombineRgn(RgnDesktop, RgnDesktop, RgnTrayWnd, RGN_DIFF);
      GetRgnBox(RgnDesktop, Result);
    finally
      DeleteObject(RgnTrayWnd);
    end;
  finally
    DeleteObject(RgnDesktop);
  end;
end;

9 live by DuoMark SETUP.

Оформил: DeeCo Автор: http://www.cracklab.narod.ru

Взят на диске "Хакер №10".

Вот попалась мне эта прога, которая автоматически следит за изменениями в системных файлах и если эти изменения были зделаны несознательно и это, недай бог, привело к каким-то проблемам, то эта программка восстанавливает файлы так, что даже намёка на глюки не будет. Так вот, запустил я инсталяцию (setup.exe) и увидел душераздирающую надпись: "Извините, но триал этого сетапа истёк! сорри!". Ну ни хера себе, подумал я. Мало того, что сама прога триальная, так ещё и установка её триальная. "Я увидел это всё и стал подругам звонить..." Короче говоря, после недолгих раздумий я решил установить таки прогу (судя по виду установки - прога не крепкая). Первым делом я дизассемблировал прогу WDasm'om и начал искать сообщение об ошибке в String Reference, но ничего дельного там не нашел :( Затем по привычке открыл айс и ввёл такой вот бряк:

bpx GetLocalTime
и соответственно запустил прогу, послен нажатия ОК в первом окне, а как я уже говорил, после этого нажатия выскакивает окно о конце триальности, и выскочил в айсе, после чего нажал F11 (чтобы очутиться не в kernel32.dll, а в setup.exe) и вот что увидел:
call [kernel32!GetLocalTime]
lea ecx, [esp+00]
push ecx
call [kernel32!GetSytemTime]
mov dx, [esp+0A]
....
....
Это всё не то, и до процедуры реальной проверки ещё нужно скролить и скролить, поэтому нажимаем F10 до тех пор, пока не увидим это место:
add edx, ecx
cmp edx, -05 ; проверяем результаты проверок
jl 00401589  ; если они негативные - прыгаем
cmp edx, 5A  ; опять проверим
jge 00401589 ; опять прыгнем туда же :)
cmp edx, 50  ; ещё проверка
jl 00401517  ; ещё прыжок.
Я сначала хотел убрать эти проверки, после чего у меня выскакивало таки то окно, но при нажатии ОК, выскакивало другое, что триальная установка ещё действует, но мне смотреть на эти галимые окна не захотелось и я начал думать дальше. Потом я снова проделал шаги, описанные выше и обнаружил, что чуть ниже последнего листинга (около 3 строчек вниз есть четрые подряд call'a:
call 00405470 ;загрузка текста для первого message
...
...
call 00405670 ;вызов первого сообщения
...
...
call 00405470 ;загрузка текста для второго message
...
...
call 00405670 ;вызов второго сообщения
:0040152C mov bl,01
я подумал и придумал вот что: а если убрать все проверки, о которых я писал выше, и вместо них зделать прыжок (jmp) сразу за вызовы сообщений об окончании триальной установки и о его волшебном возобновлении :) и правда, когда я заменил первую строку проверок (cmp edx, -05) на (jmp 0040152C), то после этих изменений никаких сообщений не осталось (кроме приветственного) и потом сразу же начиналась установка. На таком простом примере я показал, как бороться с "триальными" Sеtuр'ами (хотя я чесно говоря с таким родом защиты столкнулся впервые, чтобы триальную прогу да ещё и триальным сетапом :) А для особо ленивых вот готовый крак:
----------вырезать тут--------
9live's SETUP CRACK by vallkor//PTDS(vallkor@chat.ru)
you can install it when you want!
setup.exe
000008EB: 83 EB
000008EC: FA 3F
000008ED: FB 90
----------вырезать тут--------
исследовал и наваял туториал: vallkor //PTDS (vallkor@chat.ru) {Комманда PTDS нуждается в новых членах! Если вы считаете, что способны на многое и хотите поделиться своими знаниями и взамен получить новые пишите мне}.

Дубликат Paradox или dBase записи.

Автор: Eryk Bottomley
Встречаются два администратора: (1)-Юникса, (2)-Винды (1) - Вот, на работе скукотища - делать совсем нечего. Никто не жалуется... (2) - А у меня всегда работа есть - каждые полчаса систему переставляю. (1) - Ну ни фига себе - ну ты крут. Как ты добился, чтобы винда 30 минут работала??? (2) - Да нет, это она 30 минут переставляется...
Существует ли какое-либо простое решение для Delphi-приложения, позволяющее прочесть запись из первого поля таблицы Paradox (первичный ключ) и потом обратно добавить ее в таблицу в виде новой записи?

Вот одно "кривое" решение:

procedure TForm1.Button1Click(Sender: TObject);
var
  p : CURProps;
begin
  Table2.Insert;
  DbiGetCursorProps(Table1.Handle,p);
  Move(Table1.ActiveBuffer^,Table2.ActiveBuffer^,p.iRecBufSize);
  Table2.FieldByName('ID').AsInteger := Table1.FieldByName('ID').AsInteger + 1;
  Table2.Post;
end;

...это работает на том "основании", что как Table1, так и Table2 ссылаются на один и тот же табличный файл. Вы могли бы осуществить это и с единственным TTable, если сохраните содержание в активном буфере (ActiveBuffer).

Ошибка дублирования идентификатора ресурса.

Повесился как-то программист, и в кармане у него нашли записку: Critical error. System halted.
У вас есть исходный код VCL? Если да, то в этом случае ее можно всю перекомпилировать, добавив каталог к вашему библиотечному пути (Library path) в опциях среды (Environment Options | Library). Я думаю это нужно сделать, чтобы отделаться от этой ошибки. При другом способе необходимо вычислить вызывающую проблему директиву $R, временно удалить ее, и осуществить перекомпиляцию. Временно выключить директиву $R можно добавлением '.' перед $ (но это не единственный путь выключить ее).

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

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

На ум сразу приходит операция присваивания значения свойству (стоящему с левой стороны от ':='), при которой Delphi в своих недрах вызывает метод 'write' и передает ему в виде единственного параметра все то, что находится в правой части выражения. Если свойство не имеет метода write, оно предназначено только для чтения. Вот определение свойства FieldDefs объекта TDataSet в файле DB.PAS:

property FieldDefs: TFieldDefs read FFieldDefs write SetFieldDefs;

Как вы можете видеть, у него есть метод write. Следовательно, код:

Destination.FieldDefs := Source.FieldDefs;

В действительности делает такую операцию:

Destination.SetFieldDefs(Source.FieldDefs);
(за исключением того, что вы не можете использовать эту строку, поскольку SetFieldDefs определен в секции Private.)

Вот определение свойства IndexDefs объекта TTable в файле DBTABLES.PAS file:

property IndexDefs: TIndexDefs read FIndexDefs;

В этом случае метод write отсутствует, поэтому свойство имеет атрибут только для чтения. Тем не менее, для самого объекта TIndexDefs существует метод Assign. Следовательно, следующий код должен работать:

Source.IndexDefs.Update;
Destination.IndexDefs.Assign(Source.IndexDefs); 
Перед вызовом Assign для Source.IndexDefs вызывайте метод Update, чтобы быть уверенным в том, что вы получите то, что хотите. Метод SetFieldDefs является процедурой с одной строкой кода, в которой вызывается метод FieldDefs Assign.

Также можно проверить, определен ли реально индекс, и, если нет, то при вызове IndexDefs.Assign вы можете получить исключение типа "List Index Out Of Bounds" (или что-то типа этого). Например, так:

if Source.IndexDefs.Count > 0 then... 

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

Дублируем TTable со всеми индексами.

type 
  TForm1 = class(TForm) 
    tbSource: TTable; 
    tbTarget: TTable;  
  end; 
implementation 
procedure TForm1.Button1Click(Sender: TObject); 
begin 
  tbSource.TableName := 'Source.DB';  // The name of your tables which you want to copy from 
  tbTarget.TableName := 'Target.DB';  // The name of your tables which you will to copy to 
                                      // You Can  set the tbSource.DataBaseName to an existing path/Alias 
                                      //    where you store your DB 
                                      // You Can  set the tbTarget.DataBaseName to an existing path/Alias 
                                      //    where you want to store the duplicate DB 
  tbSource.StoreDefs := True; 
  tbTarget.StoreDefs := True; 
  tbSource.FieldDefs.Update; 
  tbSource.IndexDefs.Update; 
  tbTarget.FieldDefs := tbSource.FieldDefs; 
  tbTarget.IndexDefs := tbSource.IndexDefs; 
  tbTarget.CreateTable; 
end;

Экономия памяти с дублирующими значениями в таблице.

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

{ 
  Often there are lots of duplicate strings in a program, 
  for example take the following database: 
  Author    Title 
  aaa        xxx 
  aaa        yyy 
  aaa        zzz 
  Suppose you want to read it into your program and 
  create one record or class per line: 
}
 while not Table.EOF do
 begin
   author := Table['Author'];
   title := Table['Title'];
   MyClass := TMyClass(author, title);
   StoreItSomewhere(MyClass);
   Table.Next;
 end;
 { 
  You end up with three different strings containting the same author. 
  No problem with that as long as there are only three of them. 
  Now suppose you have got a database of 100,000 entries and lots of 
  duplicate author names. 
  If you do the above again you will end up with a huge memory overhead 
  due to fragmentation of the free memory on the heap. 
  In addition to that freeing the objects will result in a noticable delay 
  of the program while the memory manager is busy merging free memory blocks. 
  On the other hand, Delphi's AnsiStrings use reference counting so it 
  would be possible to assign the same string to many string variables 
  without copying the contents: 
}
 Author := 'aaa';
 for i:=0 to 100000 do
 begin
   MyClass := TMyClass(Author);
   StoreItSomewhere(MyClass);
 end;
 { 
  This will create 100000 strings containting 'aaa', 
  but store the actual contents only once, 
  because they are assigned the same string constant. 
  It would be nice to do the same with the above database example, 
  wouldn't it? But how do we get Delphi to realize that 
  the content is really the same? 
  Answer: A StringList 
}
 authors := TStringList.Create;
 authors.Sorted := true;
 authors.Duplicates := dupIgnore;
 while not Table.EOF do
 begin
   author := Table['Author'];
   title := Table['Title'];
   authors.Add(author);
   authors.Search(author, Idx);
   author := authors[Idx];
   MyClass := TMyClass(author, title);
   StoreItSomewhere(MyClass);
   Table.Next;
 end;
 authors.free;
 { 
  This will only keep one string of each author's name and assign 
  it to all other occurences, even after the StringList has been freed again. 
  The wonders of reference counting! 
  I admit that the above example looks a bit odd. Nobody would load 
  such a database into memory. But I had a similar problem today and by using 
  the trick above I managed to reduce the memory usage of my program from 120 megabytes to 10 
  (basically nothing, given that it started out with 8 mb). 
}

DirectX для начинающих. Часть пятая.

Оформил: DeeCo

Автор: Виктор Кода.

В этот раз мой новый урок будет посвящён азам использования интерфейса Direct3D - наиболее сложного и объёмного из состава DirectX8 SDK.

Я не собираюсь рассказывать, как построить объёмный кубик или классический чайник в пространстве, нет. Вместо этого я решал затронуть довольно интересную тему - как организовать вывод 2D-графики посредством Direct3D. Я решил заняться этим вопросом, т. к. всё чаще API трёхмерного моделирования используются для создания плоских изображений. Соответсвенно и я собираюсь перевести свои программы на новы рельсы.

Вообще, интерфейсы IDirectDraw7 и IDirectDrawSurface7 - последние в составе SDK, которые предназначены для вывода двумерной графики путём битовых переносов. Эти интерфейсы были введены в DirectX версии 7 и по сути, уже ничего принципиально нового не содержали. Единственное отличие состяло в более тесной интеграции DirectDraw с Direct3D, но и это уже в прошлом. С началом разработки DirectX 8 отдел Microsoft принял решение навсегда остановить развитие этой компоненты, а Direct3D сделать полностью самодостаточным и единственным средством вывода графики в DirectX. Теперь он так и называется - DirectXGraphics. Впрочем, в составе SDK до сих пор присутствует файл ddraw.h, чтобы пользователи имели возможность и дальше работать с DirectDraw, и примеры по его использованию. Это как намёк на то, что не следут на 100% отказываться от старых приёмов работы с графикой.

По-видимому, решение об остановке исходило из невозможности добавить в DirectDraw ещё сколь-нибудь существенных деталей. Аппаратно эффекты DirectDraw не поддерживаются и планов таких у производителей видеокарт не намечалось. В тоже время многие программисты уже начали применять Direct3D для вывода 2D-графики, игнорируя DirectDraw. Взвесив все "за" и "против", было решено доработать Direct3D как средство построения плоских изображений, а DirectDraw объявить бесперспективным. Вот так старейший компонент, когда-то служивший в DirectX единственным средством представления графики, теперь является анахронизмом...

Соответственно, и нам нет смысла долго "сидеть" на нём, а надо потихоньку перебираться в Direct3D, догонять технологии, так сказать. Но прежде всего необходимо оговорить ограничения, связанные с использованием этого интерфейса: Для вывода графики необходимо, чтобы компьютер был оснащён простейшим, но всё же 3D-ускорителем. И хотя вывод будет осуществляться и на обычной видеокарте, картинка будет искажена, а скорость вывода будет неприемлемо мала. Впрочем, в теперешнюю эпоху навороченных акселераторов простейшие их модели стоят копейки. Текстуры, содержащие изображение, по размерам должны быть кратны степени двойки. Т. е. 64Х64 или 256Х512 - подходит, а 63х255 - нет. Это вроде как на руку аппаратной части. А теперь приступим непосредственно к воплощению замысла на практике. Для этого сперва необходимо подключить два заголовочных файла, в которых энтузиасты JEDI разместили всё, что необходимо для полноценной работы с Direct3D версии 8:
uses
  directxgraphics,
  d3dx8;
Эти два файла представляют собой объединение многих других файлов из SDK для C/C++ (их названия приведены в самом начале). Первый файл содержит описание основных интерфейсов Direct3D, а второй - дополнительных интерфейсов Direct3DX. Это вспомогательная библиотека, содержащая интерфейсы высокого уровня и множество функций разного назначения, дополнение к основному комплексу. Обратите своё внимание на такую строчку из файла d3dx8.pas:
const
  d3dx8dll = 'D3DX8ab.dll';
Это означает, что все функции экспортируются из указанной динамической библиотеки. Хотя она и поставляется в архиве с остальными файлами *.pas, но не входит в стандартный run-time от Microsoft. Т. е. ваши приложения, основанные на Direct3D, не смогут выполняться на компьютерах других пользователей, если только вы не будете каждый раз прикреплять к своим файлам нестандартную библиотеку. Откуда её выкопали JEDI - Бог его знает, однако нестандартный run-time - это плохо. Решением может служить изменение значения константы:
const
  d3dx8dll = 'd3dx8d.dll';

После такой замены всё работает вроде как нормально, во всяком случае, я не встретил ещё ни одной ошибки. Файл d3dx8d.dll я нашёл у себя в директории Windows/System, он входит в стандартный пакет DirectX 8. В любом случае, могу посоветовать такой приём: компилируйте все свои Direct3D-приложения с использованием d3dx8ab.dll, а когда возникнет необходимость передать программу другим пользователям, замените имя библиотеки и перекомпилируйте программу. Если всё работает нормально, можете смело распространять своё творение.

Также нужно заметить, что размер "пустого" выполняемого файла получается около 40 кб, а не 17-20. Причина этого кроется в том, что модуль d3dx8.pas подключает модуль sysutils.pas. Самое "смешное" в том, что с увеличенным размером приходится мириться из-за вызова единственной функции CompareMem() (из функции D3DXMatrixEqual()). Если вы такие же щепетильные в этом вопросе, как и я, то могу посоветовать такое решение: скопируйте функцию CompareMem() из sysutils.pas в d3dx8.pas, и удалите ссылку на модуль из списка uses. Размер минимальной программы уменьшится до стандартных для Delphi 17 кб. Разобравшись с заголовочными файлами, объявим далее необходимые переменные:
var
  g_pD3D: IDirect3D8 = nil; // общий интерфейс Direct3D
  g_device: IDirect3DDevice8 = nil; // наша видеокарта
  g_pTexture: IDirect3DTexture8 = nil; // текстура
  g_pSprite: ID3DXSprite = nil; // спрайт
Теперь необходимо провести инициализацию Direct3D:
// проверяем правильность динамической компоновки
if @_Direct3DCreate8 = nil then
  exit;
// получаем интерфейс IDirect3D8
g_pD3D := Direct3DCreate8(D3D_SDK_VERSION);
if g_pD3D = nil then
  exit;

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

При инициализации графического устройства нам обязательно понадобится текущий видеоформат. Его можно задать самостоятельно, но так как он может не поддерживаться установленной видеокартой, а проверять это в маленьком примере накладно, лучше воспользоваться форматом текущего видеорежима:
// получаем формат текущего видеорежима
if FAILED(g_pD3D.GetAdapterDisplayMode(D3DADAPTER_DEFAULT, d3ddm)) then
  exit;
После этого необходимо заполнить поля структуры TD3DPRESENT_PARAMETERS:
// подготавливаем структуру TD3DPRESENT_PARAMETERS
ZeroMemory(@d3dpp, sizeof(d3dpp));
d3dpp.Windowed := BOOL(FALSE); // полноэкранный режим
d3dpp.BackBufferWidth := SCREEN_WIDTH; // высота и
d3dpp.BackBufferHeight := SCREEN_HEIGHT; // ширина заднего буфера
d3dpp.BackBufferFormat := d3ddm.Format;
  // формат буфера - как и у текущего экрана
d3dpp.SwapEffect := D3DSWAPEFFECT_DISCARD;
  // тип сброса содержимого буфера в окно

Замечу, что поле Windowed поределяет, будем ли мы работать в оконном или полноэкранном режиме. Замените FALSE на TRUE, и получите вывод в окно.

Теперь необходимо инициализировать графическое устройство:
// создаём устройство Direct3D
if FAILED(g_pD3D.CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd,
  D3DCREATE_SOFTWARE_VERTEXPROCESSING, d3dpp,
  g_device)) then
begin
  exit;
end;
Создаваемое устройство может быть различных типов. Поясню только самое общее. D3DDEVTYPE_HAL - это аппаратное устройство, где все операции выполняются графическим процессором видеокарты. Как раз это нам и нужно. D3DDEVTYPE_REF - программное устройство, все операции выполняет центральный процессор. Крайне медленно, зато можно использовать любые возможности Direct3D, вроде пиксельных и вертексных шейдероов. В основном этот параметр нужен для производителей видеокарт, чтобы они подгоняли результат аппаратного рендеринга к эталонному программному. D3DCREATE_SOFTWARE_PROCESSING - обработка вершин производится ЦП. Не очень быстро, так что в серьёзных программах лучше использовать другие флаги (их можно легко узнать из заголовочного файла), но для простоты я выбрал этот. Кстати, некоторые видеокарты могут работать только с указанием этого флага.

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

Для простоты здесь же созданим и объект, который будет служить спрайтом:
result := SUCCEEDED(D3DXCreateSprite(g_device, g_pSprite));

Интерфейс ID3DXSprite как раз и служит для вывода простых спрайтов в Direct3D. Использовать его очень легко, т. к. этот интерфейс - просто надстройка над более низкоуровневымы операциями Direct3D. Я пробовал рисовать и вторым способом, но он более объёмен и сложен, так что для примера я выбрал первый. Как и всякое простое решение, он не позволит сделать всего, что может API. Если вам со временем станет чего-то не хватать, можно прибегнуть к явному программированию.

После инициализации Direct3D следующим шагом является загрузка текстуры:
hr := D3DXCreatewTextureFromFileEx(g_device, pchar(strFileName), 0, 0,
  D3DX_DEFAULT, 0, D3DFMT_UNKNOWN,
  D3DFMT_UNKNOWN, D3DPOOL_DEFAULT,
  D3DXFILTER_NONE, D3DXFILTER_NONE,
  D3DCOLOR_XRGB(0, 255, 0), nil, nil,
  g_pTexture);
Функция D3DXCreateTextureFromFileEx() является частью комплекса Direct3DX, и позволяет нам загружать текстуры из файлов разного формата. Результатом является корректный указатель на интерфейс IDirect3Dtexture8. Существует более простая реализация функции (без приставки Ex), но она не позволят задать цветовой ключ для текстуры, поэтому пришлось прибегнуть к этой. Не обращайте внимание на обилие параметров - половина из них взята "по умолчанию". Всегда можно написать собственную промежуточную функцию, и избавить код от захламления при загрузке множества текстур.

С цветовым ключом мы уже давно знакомы, но здесь есть два нюанса. Первый - это то, что цветовой ключ один для всей текстуры. Это может вызвать некоторые неудобства при размещении разных изображений в одной текстуре (что часто и делается), но они вполне решаемы с помощью какого-нибудь фотошопа. Второй - если необходимо задать чёрный цветовой ключ, не пишите вместо D3DCOLOR_XRGB( 0, 255, 0 ) просто 0 - это ошибка, т. к. данное значение сигнализирует о том, что цветовой ключ не требуется. Надо писать именно D3DCOLOR_XRGB( 0, 0, 0 ),или $FF000000, что то же самое.

Если теперь открыть bmp-файл, то сразу видно, что я разместил там два изображения. Как я уже говорил, размеры текстур должны равняться степени двойки. Поэтому мы не можем разместить изображение фона в растре размерностью 512*384, приходится его расширять до 512*512 пикселей. Чтобы зря не тратить память видеокарты, по возможности необходимо размещать в свободных участках какие-либо дополнительные изображения. В моём случае влез рисунок 128*128, хотя его первоначальные размеры были в два раза больше (пожертвовал для уменьшения архива). Если же нужно вместить что-то нестандартное, то придётся делать ещё одну текстуру, или расширять существующую, скажем, до 512*1024. Тут нужно пробовать разные варианты и комбинации, пока не будет найден какой-то компромисс. Кстати, если уж говорить о размерах текстур, так вот: Direct3D МОЖЕТ загрузить растр любого размера, но при рендеринге тем способом, который я собираюсь использовать, возникнут искажения. Этот вопрос мною ещё слабо изучен, поэтому лучше пока это "на потом", а пока выучить степени двойки наизусть.

Теперь можно приступать непосредственно к рендерингу изображения. Для простоты я "завесил" экран одной частью текстуры (кстати, это небезызвестный кадр из альфа-версии DOOM 3 :). Сверху же будет выводиться спрайт, другая часть текстуры.

Итак, для начала необходимо очистить кадровый буфер и "скомандовать" GPU начать просчитывать новый кадр:
// очищаем кадровый буфер
g_device.Clear(0, nil, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 0, 0), 1.0, 0);
// начинаем рисовать изображение
g_device.BeginScene();
Далее выводим фон:
SetRect(rcTex, 0, 0, 512, 384); // область текстуры
transv.x := 0; // координаты левой
transv.y := 0; // верхней точки
g_pSprite.Draw(g_pTexture, @rcTex, nil, nil, 0, @transv, $FFFFFFFF);
С методом ID3DXSprite.Draw() надо разобраться.
function Draw(const pSrcTexture: IDirect3DTexture8; pSrcRect: PRect;
  pScaling, pRotationCenter: PD3DXVector2; const Rotation: Single;
  pTranslation: PD3DXVector2; const Color: TD3DColor): HResult; stdcall;
Параметром pSrcTexture передаём наш интерфейс pTexture. PSrcRect - это область текстуры, которая будет "натягиваться" на полигон, имитирующий наш спрайт. PScaling - это параметр типа D3DXVECTOR2, задаёт масштабирование спрайта по осям X и Y. Значение 1.0 задаётся для вывода реальных размеров, при 0.5 произойдет уменьшение в два раза, при 2.0 - увеличение в два раза. PRotationCenter - это центр оси поворота. Rotation - это угол поворота в радианах. Т. к. обычно используюся градусы, необходимо преобразовывать градусную меру в радианную. Я отыскал вот такую формулу перехода:

A = 3.14 * n / 180 (радиан).

pTranslation - перенос системы координат, в просторечье позиция левого верхнего угла спрайта. Color - используется при альфа-прозрачности и при задании цветовых компонент. Параметр - типа D3DCOLOR, определённого как DWORD. Первые три бита описывают компоненты RGB, последний - альфа-прозрачность. Для простого отображения текстуры используется значение $FFFFFFFF. Чтобы понять, как нужно манипулировать этим значением, попробуйте для фона задать такое: $FF00FF00.

При прорисовке фона мы указываем только область текстуры, позицию левого верхнего угла и цвет. Остальным параметрам передаём nil.

Теперь необходимо вывести спрайт и закончить рендеринг. Вот тут-то я и показал преимущества Direct3D над DirectDraw. То, чего нельзя было сделать ранее, можно легко использовать сейчас:
SetRect(rcTex, 0, 384, 128, 512); // область текстуры
transv.x := x - 64 * fScale; // координаты левой
transv.y := y - 64 * fScale; // верхней точки
scalev.x := fScale; // масштабирование по оси X и
scalev.y := fScale; // Y
rotv.x := 64 * fScale; // положение оси
rotv.y := 64 * fScale; // поворота (здесь центр спрайта)
g_pSprite.Draw(g_pTexture, @rcTex, // текстура и её область
  @scalev, // масштаб
  @rotv, // позиция оси поворота
  D3DX_PI * fAngle / 180.0, // угол поворота(перевод градуса в радианы)
  @transv, // позиция в окнонных координатах
  ((255 - alpha) shl 24) + $FFFFFF); // альфа-прозрачность
// закончили рисовать изображение
g_device.EndScene();
// выводим содержимое кадрового буфера на экран
g_device.Present(nil, nil, 0, nil);

Спрайт можно поворачивать клавишами "вправо" и "влево", прозрачность меняется при нажатии на "вверх" и "вниз", масштабирование происходит при нажатии на "+" или "-" на основной клавиатуре.

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

Итак, какие впечатления? При ПРОСТОМ отображении Direct3D рисует картинку, неотличимую от той, которую мы бы получили при использовании DirectDraw. Отсутствует какая-либо размытость, или неточность. Это главный плюс. Применение эффектов вроде прозрачности или масштабирования никак не сказывается на скорости вывода. Оно и понятно - мы же имеем дело с рендерингом изображения, а не с BitBlt-операциями. При повороте спрайта или его масштабировании применяется билинейная фильтрация - это сглаживание пикселей изображения, используется для устранения большой ступенчатости и "переливов" пикселей. К сожалению, здесь я обнаружил проблему. При повороте спрайта или при изменении масштаба начинают сказываться ошибки в отображении. Они проявляются в том, что при сглаживании начинают "захватываться" соседние пиксели текстуры, уже не принадлежащие нашему изображению. Например, видены "уголки" или тонкая серая полоска справа - там, где находится граница с пустым белым изображением. Можно только посетовать, что разработчики алгоритмов фильтрации упростили их до такой степени. Верным решением было бы задействование только тех пикселей, которые указаны в области pSrcRect, и не иметь НИКАКИХ дел с теми, что лежат вне её. Единственным надёжным решением может служить такое: перед размещением разных изображений в одном залейте его каким-нибудь однотонным цветом, который будет служить цветовым ключом. После этого размещайте изображения, но так, чтобы между ними всегда был зазор в 1 пиксель. Это гарантирует отсутствие помех. Понимаю, накладно, но иного пути не вижу. Всё-таки, наверное, простой алгоритм фильтрации выполняется на аппаратном уровне гораздо быстрее любого другого "заумного". Ради скорости и качества, думаю, в графическом редакторе можно и попотеть. На последок: раскоментируйте строку
//g_device.SetRenderState( D3DRS_FILLMODE, D3DFILL_WIREFRAME );
и добавьте вызовы g_pSprite._Begin() после g_device.Begin() и g_pSprite._End() перед g_device.End(). Сможете увидеть, из чего состоят "спрайты" :) Ну вот и всё, что нужно при простом использовании Direct3D. Хочется ещё заметить, что такие спрайты используются не только в 2D-графике, но так же с успехом и при построении трёхмерных сцен. Например, при выводе HUD. Или вот, в Quake 3 - газы при стрельбе из Machine Gun'а ни что иное, как спрайт, помещённый перед стволом и отрисованный с некоторой прозрачностью. В общем, примеров использования достаточно. Конечно, после практики с интерфейсом ID3DXSprite не лишне попробовать и более низкоуровневые методы вывода изображения. http://www.delphi-jedi.org/DelphiGraphics/directx/headers/DirectXGraphics.zip - заголовочные файлы для работы с Direct3D. http://www.delphi-jedi.org/DelphiGraphics/directx/headers/DirectXGraphics.zip - все заголовочные файлы для работы с DirectX 8.

Скачать примеры к статье D3DXSprite.zip (95 K).

Смотрите по теме: DirectX для начинающих DirectX для начинающих. DirectInput API DirectX для начинающих. Sprite DirectX для начинающих. Sound DirectX для начинающих. Часть третья. Считывание и запись Полупрозрачные спрайты в DirectDraw Пишем DirectX-движок DirectX и Delphi

Как заставить работать DelphiX и UnDelphiX под Delphi6.

Оформил: DeeCo

Автор: Michael Wilson.

Введение

Эта статья расскажет вам как откомпилировать известные библиотеки DelphiX и UnDelphiX под Delphi 6.0. Весь процесс займет не больше 15-20 минут, кроме того вы изучите общие приемы, которые могут помочь в аналогичных ситуациях. Почему это статья, а не, скажем, патч или дополнение к Delphi? Есть две причины:

1) По соглашению об использовании библиотеки ("TERMS AND CONDITIONS OF USE"), нельзя рапространять измененные части дистрибутива DelphiX.

2) Действия, которые необходимо совершить для конвертации проекта под Delphi 6.0, делают DelphiX несовместимой для использования в Delphi 3-5.

Предупреждение: пожалуйста, сохраните резервную копию всех изменяемых файлов. Методы, изложенные здесь, протестированы на Delphi 6.0 Personal, если у вас другая редакция (например, Professional), могут возникнуть непредвиденные проблемы.

Итак, перекомпиляция:

Шаг 1: Во-первых, нужно убедиться, что у вас установлена последняя версия DelphiX. Ее можно скачать с этого сайта. После распаковки архива запустите Delphi 6. Не запускайте файл install_for?.exe!

Шаг 2: В меню File выберите пункт Open и откройте DelphiX_for5.dpk. Delphi 6 преобразует пакет к 6-й версии.

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

Сохраните пакет как DelphiX_for6.dpk.

Шаг 3: Попробуйте нажать кнопку Install. Компилятор покажет вам ошибки, которые нужно исправить. Все ошибки относятся к модулю DXRender.pas.

[ Замечание: если вы компилируете библиотеку UnDelphiX].

Данное изменение касается вас только в случае компиляции библиотеки UnDelphiX. Этот фрагмент кода:

var
  NilGUID: TGUID absolute 0;

… нужно заменить на …

var
  NilGUID: TGUID;

[ Замечание: если вы компилируете библиотеку UnDelphiX].

Шаг 4: Первый тип ошибок, которые нужно исправить - это ошибка "Operand Size Mismatch". Всего должно быть около 40-50 ошибок такого типа. Исправить ее очень легко:

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

mov eax, Dest;
add eax, TDXRMachine_Color.R + 1 // bad statement

... заменяется на ...

mov eax, Dest;
add eax, BYTE(TDXRMachine_Color.R + 1) // good statement

Этот фрагмент просто извлекает младшие 8 бит TDXRMachine_Color.R - вам нужно исправить все строки, где есть ссылка на TDXRMachine_Color. Это делается очень легко с помощью пункта меню Search -> Replace...

Шаг 5: Второй тип ошибок, требующих исправления, - это "Inline Assember Syntax". Всего будет порядка 10-20 ошибок этого типа и они всегда возникают, если в исходном тексте есть упоминание об _AddTable или _SubTable. Исправляется ошибка также просто:

movzx edx, byte[offset _AddTable + edx + ebx]

... заменяется на ...

movzx edx, byte[offset(_AddTable + edx + ebx)]

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

Шаг 6: Последняя и самая неприятная ошибка, связанная с модулем DsgnIntf.pas. Из-за нее не компилируется модуль DxReg.pas библиотеки DelphiX.

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

Есть два способа исправить ошибку:

1) Delphi Personal: во-первых, удалите все строки под объявлениями типов в DXReg.Pas. Затем удалите ссылки на RegisterComponentEditor и RegisterPropertyEditor в процедуре Register. Наконец, удалите весь код, который находится ниже процедуры RegisterComponents (убедитесь, что модуль по-прежнему заканчивается "end."). После вышеописанных действий модель DXReg.Pas должен выглядеть вот так:

unit DXReg;
interface
uses
  Windows, SysUtils, Classes, Forms, Dialogs, Graphics, TypInfo,
  DXDraws, DXSounds, DIB, Wave, DXInput, DXPlay, DXSprite,
  DXClass;
procedure Register;
implementation
const
  SNone = '(None)';
  SSettingImage = '&amp;Image...';
  SSettingWave = '&amp;Wave...';
  SDXGFileFilter = 'DXG file(*.dxg)|*.dxg|All files(*.*)|*.*';
  SDXGOpenFileFilter =
    'DXG file(*.dxg)|*.dxg|Bitmap file(*.bmp)|*.bmp|All files(*.*)|*.*';
  SDXWFileFilter = 'DXW file(*.dxw)|*.dxg|All files(*.*)|*.*';
  SDXWOpenFileFilter =
    'DXW file(*.dxw)|*.dxw|Wave file(*.wav)|*.wav|All files(*.*)|*.*';
  SSinglePlayer = '&amp;Single player';
  SMultiPlayer1 = 'Multi player &amp;1';
  SMultiPlayer2 = 'Multi player &amp;2';
  SOpen = '&amp;Open...';
  SSave = '&amp;Save..';
procedure Register;
begin
  RegisterComponents('DelphiX',
    [TDXDraw,
    TDXDIB,
      TDXImageList,
      TDX3D,
      TDXSound,
      TDXWave,
      TDXWaveList,
      TDXInput,
      TDXPlay,
      TDXSpriteEngine,
      TDXTimer,
      TDXPaintBox]);
end;
end.

С таким решением проблемы вы не сможете использовать редактор свойств, включенный в DelphiX.

2) Delphi Professional: решение проблемы с профессиональной версией Delphi была подсказана Tommy "QuazzeMan" Jonsson:

A) В модуле DXReg.pas замените ссылку на DsgnIntf в списке USES на DesignIntf. b) Добавьте DesignEditors в список USES модуля DXReg.Pas c) Добавьте DesignIDE в секцию REQUIRED пакета.

Шаг 7: Все, можно наслаждаться работой DelphiX и UnDelphiX в шестой версии Delphi.

Динамическое добавление пунктов меню.

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

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

Новый элемент верхнего уровня:

procedure tform1.addmainitem(s: string);
var
  newitem: Tmenuitem;
begin
  newitem := tmenuitem.create(Mainmenu1);
  newitem.caption := s;
  {если вы хотите определить событие onclick
  newitem.onclick:=Dynamenuclick; }
  {добавляем это к верхнему уровню меню}
  mainmenu1.items.insert(mainmenu1.items.count, newitem);
  removemenu1.enabled := true;
  addmenuitem1.enabled := true;
end;

Создание подменю:

procedure tform1.addsubitem(s: string; to : integer);
var
  newitem, toitem: Tmenuitem;
begin
  {to = верхний уровень меню для нового пункта}
  toitem := mainmenu1.items[to ];
  newitem := tmenuitem.create(toitem);
  newitem.caption := s;
  {если вы хотите определить событие onclick
  newitem.onclick:=Dynamenuclick; }
  toitem.onclick := nil;
  toitem.insert(toitem.count, newitem);
  removemenuitem1.enabled := true;
end;

Динамическое добавление пунктов меню 2.

Автор: Neil J. Rubenking

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

unit Istopmnu;
interface
uses
  SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics,
  Controls, Forms, Dialogs, StdCtrls, Menus;
type
  TForm1 = class(TForm)
    MainMenu1: TMainMenu;
    SubMenu1: TMenuItem;
    procedure AClick(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;
var
  Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.AClick(Sender: TObject);
var
  TM: TMenuItem;
  Lev: Word;
begin
  MessageBeep(0);
  TM := Sender as TMenuItem;
  if TM.Count > 0 then
    Caption := 'подменю'
  else
    Caption := 'элемет меню';
  Caption := Caption + ' с именем "' + TM.Name + '"';
  Lev := 0;
  while (TM.Parent <> nil) and (TM.Parent is TMenuItem) do
  begin
    TM := TM.Parent;
    Inc(Lev);
  end;
  case Lev of
    1: Caption := 'Верхний уровень ' + Caption;
    2: Caption := '2-й уровень ' + Caption;
    3: Caption := '3-й уровень ' + Caption;
  else
    Caption := Format('%d-й уровень %s', [Lev, Caption]);
  end;
end;
procedure TForm1.FormCreate(Sender: TObject);
var
  TM: TMenuItem;
  N: Integer;
begin
  TM := SubMenu1;
  TM.Add(NewItem('&Элемент', 0, False, True, AClick, 0, 'MenuItem2'));
  for N := 2 to 5 do
  begin
    TM.Add(TMenuItem.Create(nil));
    TM := TM.Items[TM.Count - 1];
    TM.Caption := '&Меню';
    TM.Name := 'SubMenu' + IntToStr(N);
    TM.OnClick := AClick;
    TM.Add(NewItem('&Элемент', 0, False, True, AClick, 0,
      'MenuItem' + IntToStr(N + 1)));
  end;
  MainMenu1.Items.Add(NewSubMenu('Меню&2', 0, 'SM1',
    [NewItem('&Элемент', 0, False, True, AClick, 0, 'MI2'),
    NewSubMenu('&Меню', 0, 'SM2',
      [NewItem('&Элемент', 0, False, True, AClick, 0, 'MI3'),
      NewSubMenu('&Меню', 0, 'SM3',
        [NewItem('&Элемент', 0, False, True, AClick, 0, 'MI4'),
        NewSubMenu('&Меню', 0, 'SM4',
          [NewItem('&Элемент', 0, False, True, AClick, 0, 'MI5'),
          NewSubMenu('&Меню', 0, 'SM5',
            [NewItem('&Элемент', 0, False, True, AClick, 0, 'MI6')
            ])
          ])
        ])
      ])
    ]));
  TM := MainMenu1.Items[1];
  while TRUE do
  begin
    TM.OnClick := AClick;
    if TM.Count < 2 then
      Break;
    TM := TM.Items[1];
  end;
end;
end.

Динамическое добавление пунктов меню 3.

Вы можете использовать готовые функции, определенные в модуле Menus. Определения в Delphi 2:

function NewMenu(Owner: TComponent; const AName: string;
Items: array of TMenuItem): TMainMenu;
function NewPopupMenu(Owner: TComponent; const AName: string;
Alignment: TPopupAlignment; AutoPopup: Boolean; Items: array of
TMenuitem): TPopupMenu;
function NewSubMenu(const ACaption: string; hCtx: Word; const AName:
string; Items: array of TMenuItem): TMenuItem;
function NewItem(const ACaption: string; AShortCut: TShortCut;
AChecked, AEnabled: Boolean; AOnClick: TNotifyEvent; hCtx: Word;
const AName: string): TMenuItem;
function NewLine: TMenuItem;

Это превращает вышепоставленную задачу в сущий пустяк.

Динамически создаваемые псевдонимы.

Использование псевдонима для доступа к базе данных обеспечивает независимость программы от размещения данных в системе, позволяет размещать программу работы с данными и базу данных на разных дисках компьютера, в том числе и на сетевом. Вместе с тем, для простых баз данных типичным решением является размещение базы данных в отдельном подкаталоге того каталога, в котором находится программа работы с базой данных. Таким образом, программа работы с базой данных всегда "знает", где находятся данные. При таком подходе можно отказаться от создания псевдонима при помощи BDE Administrator и возложить задачу создания псевдонима на программу работы с базой данных. Причем, псевдоним будет создаваться автоматически во время запуска программы и уничтожаться во время завершения ее работы. Очевидно, что такой подход облегчает администрирование базы данных.

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

unit school3_;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
  Dialogs, Grids, DBGrids, Db, DBTables, ExtCtrls, DBCtrls, StdCtrls;
type
  TForm1 = class(TForm)
    Table1: TTable; // таблица (вся база данных)
    Query1: TQuery; // запрос (записи БД, удовлетворяющие критерию выбора)
    DataSource1: TDataSource; // источник данных - таблица или запрос
    DBGrid1: TDBGrid; // таблица для отображения БД или результата выполнения запроса
    DBNavigator1: TDBNavigator;
    DBText1: TDBText;
    Button1: TButton; // кнопка запрос
    Button2: TButton; // кнопка Все записи
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure FormActivate(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;
var
  Form1: TForm1;
implementation
{$R *.DFM}
// щелчок на кнопке Запрос
procedure TForm1.Button1Click(Sender: TObject);
var
  fam: string[30];
begin
  fam := InputBox('Выборка информации из БД',
    'Укажите фамилию и щелкните на OK.', '');
  if fam <> '' // пользователь ввел фамилию
    then
  begin
    with form1.Query1 do begin
      Close; // закрыть файл-результат выполнения предыдущего запроса
      SQL.Clear; // удалить текст предыдущего запроса
      // записываем новый запрос в свойство SQL
      SQL.Add('SELECT Fam, Name, Class');
      SQL.Add('FROM ":Школа:school.db"');
      SQL.Add('WHERE');
      SQL.Add('(Fam = "' + fam + '")');
      SQL.Add('ORDER BY Name, Fam');
      Open; // активизируем выполнение запроса
    end;
    if Query1.RecordCount <> 0 then
      DataSource1.DataSet := Query1 // отобразить рез-т выполнения запроса
    else begin
      ShowMessage('В БД нет записей, удовлетворяющих критерию запроса.');
      DataSource1.DataSet := Table1;
    end;
  end;
end;
// щелчок на кнопке Все записи
procedure TForm1.Button2Click(Sender: TObject);
begin
  DataSource1.DataSet := Table1; // источник данных - таблица
end;
// активизация формы
procedure TForm1.FormActivate(Sender: TObject);
begin
  with Session do
  begin
    ConfigMode := cmSession;
    try
      { Если файл данных находиться в том же каталоге,
      что и выполняемый файл программы, то в программе
      путь к файлу данных может быть получен из командной
      строки при помощи функции ExtractFilePath(ParamStr(0)).
      В приведенном примере файл данных находиться в подкаталоге
      DATA каталога программы. }
      // создадим временный псевдоним для базы данны
      AddStandardAlias('Школа',
        ExtractFilePath(ParamStr(0)) + 'DATA\',
        'PARADOX');
      Table1.Active := True; // откроем базу данных
    finally
      ConfigMode := cmAll;
    end;
  end;
end;
end.

В рассматриваемом варианте программы предполагается, что база данных содержится в подкаталоге DATA того каталога, в котором находится выполняемый файл программы. Создает псевдоним процедура TForm1.FormActivate. Непосредственное создание псевдонима выполняет процедура AddstandardAlias, которой в качестве параметра передается имя псевдонима и соответствующее ему имя каталога. Так как во время разработки программы нельзя знать, в каком каталоге будет размещена программа работы с базой данных и, следовательно, подкаталог базы данных -DATA, имя каталога определяется во время работы программы путем обращения к функциям ParamStr(0) и ExtractFilePatch. Значение первой -полное имя выполняемого файла программы, второй — путь к этому файлу. Таким образом, процедуре AddstandardAiias передается полное имя каталога базы данных.

Динамическая и статическая загрузка DLL.

DLL возможно загружать двумя способами:

статически динамически

Давайте создадим простую библиотеку DLL:

{Project file name: c:\example\exdouble\exdouble.dpr}
library ExDouble;
// my simple dll
function calc_double(r: real): real; stdcall;
begin
  result := r * 2;
end;
exports
  calc_double index 1;
end;

Теперь посмотрим, как её можно загружать:

Статическая загрузка DLL

При таком способе загрузки достаточно поместить файл DLL в директорию приложения или в директорию Windows, или в Windows\System, Windows\Command. Однако, если система не найдёт этого файла в этих директория, то высветится сообщение об ошибке (DLL не найдена, или что-то в этом духе).

unit untMain;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;
type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
end;
var
  Form1: TForm1;
implementation
function calc_double(r: real): real; stdcall; external 'ExDouble.dll';
{$R *.DFM}
procedure TForm1.Button1Click(Sender: TObject);
begin
  // в окошке сообщения будет цифра 21
  ShowMessage(FloatToStr(calc_double(10.5)));
end; 
end.

Динамическая загрузка DLL

При динамической загрузке требуется написать немного больше кода.

А вот как это выглядит:

unit untMain;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;
type
  Tcalc_double = function ( r: real ): real;
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
end;
var
  Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.Button1Click(Sender: TObject);
var
  hndDLLHandle: THandle;
  calc_double: Tcalc_double;
begin
  try
    // загружаем dll динамически
    hndDLLHandle := loadLibrary ( 'ExDouble.dll' );
    if hndDLLHandle <> 0 then
    begin
      // получаем адрес функции
      @calc_double := getProcAddress ( hndDLLHandle, 'calc_double' );
      // если адрес функции найден
      if addr ( calc_double ) <> nil then
      begin
        // показываем результат ( 21...)
        showMessage ( floatToStr ( calc_double ( 10.5 ) ) );
      end
      else
        // DLL не найдена ("handleable")
        showMessage ( 'Function not exists...' );
    end
    else
      // DLL не найдена ("handleable")
      showMessage ( 'DLL not found...' );
  finally
    // liberar
    freeLibrary ( hndDLLHandle );
  end;
end;
end.

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

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

type
  t = class
    function a : integer; {статический}
    function b : integer; virtual;
    function c : integer; dynamic;
    property i : integer read a; { ok }
    property j : integer read b; { ok }
    property k : integer read c;
    { ОШИБКА: type mismatch (несовпадение типа) }
  end;

Динамические массивы.

const
  MaxBooleans = (High(Cardinal) - $F) div sizeof(boolean);
type
  TBoolArray = array[1..MaxBooleans] of boolean;
  PBoolArray = ^TBoolArray;
var
  B: PBoolArray;
  N: integer;
begin
  N := 63579;
  {= получение памяти под динамический массив.. =}
  GetMem(B, N * sizeof(boolean));
  {= работа с массивом... =}
  B^[3477] := FALSE;
  {= возвращение памяти в кучу =}
{$IFDEF VER80}
  FreeMem(B, N * sizeof(boolean));
{$ELSE}
  FreeMem(B);
{$ENDIF}
end.

Динамические массивы 2.

В. Возможно создавать динамически-изменяющиеся массивы в Delphi?

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

Прежде, чем вы сможете пользоваться массивом, вам необходимо распределить для него память. Используя AllocMem, вы можете точно управлять выделяемым размером памяти. Для того, чтобы определить необходимое количество байт, которые вы должны распределить, просто умножьте размер массива на размер отдельного элемента массива. Имейте в виду, что самый большой блок, который вы сможете распределить в любой момент в 16-битной среде равен 64Кб. Самый большой блок, который вы можете в любой момент распределить в 32-битной среде равен 4Гб. Для определения максимального числа элементов, которые вы можете иметь в вашем конкретном массиве (в 16-битной среде), разделите 65,520 на размер отдельного элемента. Например: 65520 div SizeOf(LongInt).

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

type
  ElementType = LongInt;
const
  MaxArraySize = (65520 div SizeOf(ElementType));
  (* в 16-битной среде *)
type
  MyArrayType = array[1..MaxArraySize] of ElementType;
var
  P: ^MyArrayType;
const
  ArraySizeIWant: Integer = 1500;

Затем, для распределения памяти под массив, вы могли бы использоваться следующую процедуру:

procedure AllocateArray;
begin
  if ArraySizeIWant <= MaxArraySize then
    P := AllocMem(ArraySizeIWant * SizeOf(LongInt));
end;
Не забывайте о том, что величина ArraySizeIWant должна быть меньше или равна MaxArraySize.

Вот процедура, которая с помощью цикла устанавливает величину каждого члена:

procedure AssignValues;
var
  I: Integer;
begin
  for I := 1 to ArraySizeIWant do
    P^[I] := I;
end;
Имейте в виду, что вам необходимо самому организовать контроль допустимого диапазона. Если вы распределили память для массива с пятью элементами, и пытаетесь назначить какое-либо значение шестому, вы получите ошибку и, возможно, порчу памяти.

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

procedure DeallocateArray;
begin
  P := AllocMem(ArraySizeIWant * SizeOf(LongInt));
end;

Ниже приведен пример динамического массива:

unit Unit1;
interface
uses
  SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics,
  Controls, Forms, Dialogs, StdCtrls;
type
  ElementType = Integer;
const
  MaxArraySize = (65520 div SizeOf(ElementType));
  { в 16-битной среде }
type
  { Создаем тип массива. Убедитесь в том, что вы установили
  максимальный диапазон, который вам, вероятно, может понадобиться. }
  TDynamicArray = array[1..MaxArraySize] of ElementType;
  TForm1 = class(TForm)
    Button1: TButton;
    procedure FormCreate(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;
var
  Form1: TForm1;
  { Создаем переменную типа указатель на ваш тип массива. }
  P: ^TDynamicArray;
const
  { Это типизированные константы. В действительности они
  являются статическими переменными, инициализирующимися
  во время выполнения указанными в исходном коде значениями.
  Это означает, что вы можете использовать типизированные
  константы точно также, как и любые другие переменные.
  Удобство заключается в автоматически инициализируемой величине. }
  DynamicArraySizeNeeded: Integer = 10;
implementation
{$R *.DFM}
procedure TForm1.FormCreate(Sender: TObject);
begin
  { Распределяем память для нашего массива. Будь внимательны
  и распределяйте размер, в точности необходимый для размещения нового массива.
  Если вы попытаетесь записать элемент, выходящий за допустимый диапазон,
  компилятор не ругнется, но объект исключения вам обеспечен. }
  DynamicArraySizeNeeded := 500;
  P := AllocMem(DynamicArraySizeNeeded * SizeOf(Integer));
  { Как присвоить значение пятому элементу массива. }
  P^[5] := 68;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
  { Вывод данных. }
  Button1.Caption := IntToStr(P^[5]);
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
  { Освобождаем распределенную для массива память. }
  FreeMem(P, DynamicArraySizeNeeded * SizeOf(Integer));
end;
end.

Динамические массивы 3.

Автор: Robert Wittig Вот "демо-модуль", демонстрирующий три различных способа (далеко не все) создания динамических массивов. Все три способа для распределения достаточного количества памяти из кучи используют GetMem, tList используют для добавления элементов в список массива и используют tMemoryStream для того, чтобы распределить достаточно памяти из кучи и иметь к ней доступ, используя поток. Старый добрый GetMem вполне подходит для такой задачи при условии, что массив не слишком велик (<64K).

PS. Я не стал ловить в коде исключения (с помощью блоков Try...Finally}, которые могли бы мне помочь выявить ошибки, связанные с распределением памяти. В реальной системе вы должны быть уверены в своем грациозном владении низкоуровневыми операциями с памятью.

{++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++}
{ Форма, демонстрирующая различные методы создания массива с         }
{ динамически изменяемым размером. Разместите на форме четыре кнопки,}
{ компоненты ListBox и SpinEdit и создайте, как показано ниже,       }
{ обработчики событий, возникающие при нажатии на кнопки. Button1,   }
{ Button2 и Button3 демонстрируют вышеуказанных метода. Button4      }
{ очищает ListBox для следующего примера.                            }
{++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++}
unit Dynarry1;
interface
uses
  SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
  Forms, Dialogs, StdCtrls, Spin;
type
  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    Button3: TButton;
    SpinEdit1: TSpinEdit;
    ListBox1: TListBox;
    Button4: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Button3Click(Sender: TObject);
    procedure Button4Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;
var
  Form1: TForm1;
implementation
{$R *.DFM}
type
  pSomeType = ^SomeType;
  SomeType = Integer;
procedure TForm1.Button1Click(Sender: TObject);
type
  pDynArray = ^tDynArray;
  tDynArray = array[1..1000] of SomeType;
var
  DynArray: pDynArray;
  I: Integer;
begin
  { Распределяем память }
  GetMem(DynArray, SizeOf(SomeType) * SpinEdit1.Value);
  { Пишем данные в массив }
  for I := 1 to SpinEdit1.Value do
    DynArray^[I] := I;
  { Читаем данные из массива }
  for I := SpinEdit1.Value downto 1 do
    ListBox1.Items.Add('Элемент ' + IntToStr(DynArray^[I]));
  { Освобождаем память }
  FreeMem(DynArray, SizeOf(SomeType) * SpinEdit1.Value);
end;
procedure TForm1.Button2Click(Sender: TObject);
var
  List: tList;
  Item: pSomeType;
  I: Integer;
begin
  { Создаем список }
  List := tList.Create;
  { Пишем данные для списка }
  for I := 1 to SpinEdit1.Value do
  begin
    { Распределяем уникальный экземпляр данных }
    New(Item);
    Item^ := I;
    List.Add(Item);
  end;
  { Читаем данные из списка - базовый индекс списка 0, поэтому вычитаем из I единицу }
  for I := SpinEdit1.Value downto 1 do
    ListBox1.Items.Add('Элемент ' +
      IntToStr(pSomeType(List.Items[I - 1])^));
  { Освобождаем лист }
  for I := 1 to SpinEdit1.Value do
    Dispose(List.Items[I - 1]);
  List.Free;
end;
procedure TForm1.Button3Click(Sender: TObject);
var
  Stream: tMemoryStream;
  Item: SomeType;
  I: Integer;
begin
  { Распределяем память потока }
  Stream := tMemoryStream.Create;
  Stream.SetSize(SpinEdit1.Value);
  { Пишем данные в поток }
  for I := 1 to SpinEdit1.Value do
    { Stream.Write автоматически отслеживает позицию записи,
    поэтому при записи данных за ней следить не нужно }
    Stream.Write(I, SizeOf(SomeType));
  { Читаем данные из потока }
  for I := SpinEdit1.Value downto 1 do
  begin
    Stream.Seek((I - 1) * SizeOf(SomeType), 0);
    Stream.Read(Item, SizeOf(SomeType));
    ListBox1.Items.Add('Элемент ' + IntToStr(Item));
  end;
  { Освобождаем поток }
  Stream.Free;
end;
procedure TForm1.Button4Click(Sender: TObject);
begin
  ListBox1.Items.Clear;
end;
end.

Динамические массивы 4.

Автор: Виталий.

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

type
  DAr = array of real;
var
  A: DAr;

Или сразу.

var A:array of real; 

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

SetLength(A,7)

Так мы создали массив состоящий из 7 элементов начиная с 0. Важно! Первый элемент в динамическом массиве всегда нулевой. Для определения верхний границы используем функцию Hihg.

I:=High(A);

I - верхняя граница. Для определения длины Length(A), для определения нижней границы Low(A). При нулевой длине массива High, возращает -1. Пример:

var
  a,b: array of integer;
begin
  SetLength(a,2);
  SetLength(b,2);
  a[0]:=2;
  b[0]:=3;
  a:=b;
  b[0]:=4;
end;
После этих манипуляций а[0] равно 4. Дело в том при присвоении a:=b не происходит копирование т.к. а, b, это всего лишь указатели на область памяти. Для копирования необходимо использовать функцию Copy. Я надеюсь что это кому-нибудь поможет в работе. Всего наилучшего. Виталий

P.S. Не советую изменять длину массивов в DLL, у меня при этом возникала ошибка Acess violation побороть ее мне так и не удалось.

Динамические массивы 5.

Автор: Igor Nikolaev aKa The Sprite

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

var
  intArray : array of integer;

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

begin
  intArray:=(New(IntArray,100); //Размер массива? 100
end;

Решение для динамически создаваемых компонентов.

Предупреждение: Если вы просто хотите во время выполнения приложения создавать компоненты необходимого вам типа, ознакомьтесь с файлом delphi\doc\VB2Delph.wri и следуйте его рекомендациям, лучшего способа изучения этой темы пока не существует. Данный совет повествует об использовании в Delphi RTTI. Во-первых, в вашем приложении необходимо зарегистрировать все классы, экземпляры которых вы собираетесь в каком-то месте кода создавать. Сделать это можно с помощью функций RegisterClass(), RegisterClasses() и RegisterClassAlias().

Пример:

procedure TForm1.FormCreate(Sender: TObject);
begin
  RegisterClasses([TButton, TEdit, TMemo, TLabel]);
end;
Это может навести вас на мысль об ограничениях, но Delphi строгий язык. Если вы хотите истинно динамическое создание объектов в слаботипизированной среде позднего связывания, используйте динамический язык типа Smalltalk. У меня есть подозрение, что Delphi использует этот механизм регистрации для регистрации всех компонентов в DCL при его запуске, позволяя этим самым создавать любой компонент во время разработки. Создание компонентов. Используйте функцию FindClass() для получения ссылки на класс компонента, который вы хотите создать, и вызывайте его метод Create. Легко, не правда ли? В примере у меня имеется приведение типа SomeComponent к TControl, после чего я уже могу установить свойство parent (я могу делать это, поскольку я знаю, что все зарегистрированные мною классы являются потомками TControl). Для того, чтобы визуальный компонент появился на форме, вам необходимо установить свойство parent.

Пример:

procedure TForm1.CreateClick(Sender: TObject);
begin
  SomeComponent:= TComponentClass(FindClass(ClassName.Text)).Create(Self);
  (SomeComponent as TControl).Parent := Self;
end;
Теперь, когда вы имеете созданный компонент, как установить его свойства без использования самого большого блока case во вселенной? Очень просто: для получения информации о свойстве из структуры run-time type information (RTTI) используется функция GetPropInfo(), после чего для установления значений используется набор функций SetXXXXProp(). (Примечание: эти функции не задокументированы в файлах помощи Delphi. OO-программисты, как я понимаю, пользуются примерами из чужого кода и не изобретают свой велосипед.) У каждой функции SetXXXXProp() имеется функция-сателлит GetXXXXProp(), позволяющая узнать значения свойств объекта.

Пример:

procedure TForm1.SetPropertyClick(Sender: TObject);
var
  PropType: PTypeInfo;
  PropInfo: PPropInfo;
begin
  PropInfo := GetPropInfo(SomeComponent.ClassInfo, PropertyName.Text);
  PropType := PropInfo^.PropType;
  case PropType^.Kind of
    tkInteger:
      SetOrdProp(SomeComponent, PropInfo,
        StrToInt(PropertyValue.Text));
    tkChar:
      SetOrdProp(SomeComponent, PropInfo, Ord(PropertyValue.Text[1]));
    tkEnumeration:
      SetOrdProp(SomeComponent, PropInfo, GetEnumValue(PropType,
        PropertyValue.Text));
    tkFloat:
      SetFloatProp(SomeComponent, PropInfo,
        StrToFloat(PropertyValue.Text));
    tkString:
      SetStrProp(SomeComponent, PropInfo, PropertyValue.Text);
  end;
end;
Вы также можете установить значения свойств Set, Class и Method, но это будет немного сложнее. Немного позже я объясню как это можно сделать. Это все. Вы проведете время с большой пользой, изучая исходный код VCL, и удивляясь, когда вы все там увидите собственными глазами.

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

Динамическое создание формы.

Автор: Steve Schafer Я хочу следующее: мой компонент должен "динамически" создавать форму. я не хочу включать имя модуля создаваемой формы в список используемых модулей моей текущей формы!!!

Ок, но модуль, содержащий форму, должен включаться в ваш EXE-файл, после чего вы должны вызвать RegisterClass. Наилучшее место для размещения вызова - секция инициализации модуля, определяющего форму:

unit MyUnit;
interface
type
TMyForm = class(TForm)
...
implementation
...
initialization
RegisterClass(TMyForm);
end.

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

var
SomeForm: TForm;
FormClass: TFormClass;
...
FormClass := TFormClass(FindClass('TMyForm'));
SomeForm := FormClass.Create(Application);
...

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

Дай бог памати...буквально метра 128.
uses WinCRT;
procedure TForm1.Button1Click(Sender: TObject);
var
  MyArray: array[0..30] of char;
  b: ^char;
  i: integer;
begin
  StrCopy(MyArray, 'Delphi World - это круто!!!');
  b := @MyArray;
  for i := StrLen(MyArray) downto 0 do
  begin
    write(b^);
    inc(b, sizeof(char));
  end;
end;

Динамическое распределение памяти 2.

- Что делает программист после запоя наутро? - Тестирует память.
Как мне уменьшить количество занимаемой мной памяти в сегменте данных? (или как мне распределять память динамически?)

Скажем, ваша структура данных выглядит похожей на эту:

type
  TMyStructure = record
    Name: String[40];
    Data: array[0..4095] of Integer;
end;

Она слишком большая для глобального распределения, так что вместо объявления глобальной переменной,

var
  MyData: TMyStructure;

Объявляете указательный тип,

type
  PMyStructure = ^TMyStructure;

И переменную этого типа,

var
  MyDataPtr: PMyStructure;
Такой указатель занимает всего лишь четыре байта сегмента данных.

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

New(MyDataPtr);

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

MyDataPtr^.Name := 'Советы по Delphi';
MyDataPtr^.Data[0] := 12345;

И, наконец, после использования памяти, освободите ее:

Dispose(MyDataPtr);

Динамическое OLE.

Чем компьютер лучше женщины: 1. Компьютер не скажет: "ты мне не нравишься, я люблю другого". 2. Кожух с компьютера снимается без сопротивления. 3. Изображение на компьютере легко поменять. 4. Звук на колонках легко выключить. 5. У компьютера не бывает месячных. 6. Компьютер не потащит тебя знакомится со своими родителями. 7. Память на компьютере можно увеличить. 8. У девушки не бывает режима турбо. 9. Вирусы компьютера пользователю не передаются. 10. Компьютер можно собрать из тех частей, которые тебе нравятся. 11. Компьютер можно отформатировать. 12. У компьютера не болит голова. 13. Компьютер можно иметь в присутствии других. 14. Компьютер можно иметь везде. 15. К компьютеру можно подключить дополнительные периферийные устройства. 16. Компьютер не залетит. 17. Компьютер потерпит любые извращения. 18. Компьютер не ревнует, когда вы имеете другой компьютер. 19. На компьютер можно поставить пароль. 20. Девушку нельзя перезагрузить и начать всё сначала. 21. Сетевой сервер может одновременно обслуживать уйму народа. 22. На компьютере можно играть с друзьями. 23. Компьютер не заставит тебя на нем женится. 24. Компьютер не обидится, если ты назовешь его другим именем. 25. На компьютере можно отлаживать чужие программы. 26. У компьютера не бывает тещи. 27. Компьютер всегда запомнит, что ему скажешь. 28. Компьютер никогда не опаздывает. 29. Компьютер не боится мышей. 30. Компьютер не требует постоянно спонсировать его. 31. С глюками компьютера легко справится. 32. Компьютер грузится быстрее. 33. Компьютер не волнует, когда ты кончишь. 34. Компьютер мыслит логически. 35. Компьютер не потеет.

Некоторое время тому назад вы задавали вопрос по поводу динамического OLE без создания диалогового окна для просмотра файла. Я таки добился этого и вот код...

uses...BOleDefs...
procedure TPreview.previewOLEFile(fileName: string);
var
  Info: ^BOleInitInfo;
  cString: array[0..80] of char;
begin
  Screen.Cursor := crHourGlass;
  try
    GetMem(Info, sizeof(BoleInitInfo));
    Info^.How := BOLE_EMBED;
    Info^.Where := BOLE_FILE;
    Info^.pContainer := nil;
    Info^.hIcon := 0;
    Info^.Storage := nil;
    StrPCopy(cString, fileName);
    Info^.Path := cString;
    oleDocument.PInitInfo := Info;
    ReleaseOLEInitInfo(Info); { данная функция освобождает (getmem) память ! }
    oleDocument.Visible := True;
  finally
    Screen.Cursor := crDefault;
  end;
end;

Динамические PageControl и TabSheet.

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

var
  T: TTabSheet;
  P: TPageControl;
begin
  // Создаем PageControl
  // При создании получаем ссылку на PageControl, чтобы в дальнейшем на него ссылаться.
  P := TPageControl.Create(application);
  with P do
  begin
    Parent := Form1; // устанавливаем его как элемент управления формы.
    Top := 30;
    Left := 30;
    Width := 200;
    Height := 150;
  end; // with TPageControl
  // Создаем 3 страницы
  T := TTabSheet.Create(P);
  with T do
  begin
    Visible := True;
      // Это необходимо, или форма не будет корректно перерисовываться
    Caption := 'Страница 1';
    PageControl := P; // Назначаем Tab в Page Control
  end; // with
  T := TTabSheet.Create(P);
  with T do
  begin
    Visible := True;
      // Это необходимо, или форма не будет корректно перерисовываться
    Caption := 'Страница 2';
    PageControl := P; // Назначаем Tab в Page Control
  end; // with
  T := TTabSheet.Create(P);
  with T do
  begin
    Visible := True;
      // Это необходимо, или форма не будет корректно перерисовываться
    Caption := 'Страница 3';
    PageControl := P; // Назначаем Tab в Page Control
  end; // with
  // Создаем 3 кнопки, 1 на страницу
  with tbutton.create(application) do
  begin
    Parent := P.Pages[0]; // "Указываем" кнопке родительскую страницу
    Caption := 'Привет, страница 1';
    Left := 0;
    Top := 0;
  end; // with
  with tbutton.create(application) do
  begin
    Parent := P.Pages[1]; // "Указываем" кнопке родительскую страницу
    Caption := 'Привет, страница 2';
    Left := 50;
    Top := 50;
  end; // with
  with tbutton.create(application) do
  begin
    Parent := P.Pages[2]; // "Указываем" кнопке родительскую страницу
    Caption := 'Привет, страница 3';
    Left := 100;
    Top := 90;
  end; // with
  // Это должно быть сделано, или Tab первоначально не синхронизируется
  // с правильной страницей. Только в случае, если у вас более чем одна страница.
  P.ActivePage := P.Pages[1];
  P.ActivePage := P.Pages[0]; // Реально показываемая страница
end;

Динамические PageControl и TabSheet 2.

В данном документе показана технология динамического добавления страниц компонента PageControl (объектов TTabSheet) к элементу управления Windows 95/NT PageControl (объект TPageControl). Оба этих объекта объявлены в модуле ComCtrls. Поэтому убедитесь в том, что ComCtrls указан в списке используемых модулей. Как динамически создать PageControl

Прежде, чем мы приступим к динамическому созданию страниц, давайте динамически создадим PageControl (если он еще не на форме). Это делается посредством вызова конструктора TPageControl Create с параметром owner, равным Self. Конструктор Create возвращает объектную ссылку на вновь созданный объект PageControl и назначает его переменной 'PageControl'. Вторым шагом будет установка свойства PageControl Parent в Self. Свойство Parent определяет где должен быть отображен новый PageControl; в нашем случае это будет сама форма. Вот кусок кода, демонстрирующий вышесказанное:

var
PageControl : TPageControl;
PageControl := TPageControl.Create(Self);
PageControl.Parent := Self;
Примечание: При разрушении формы разрушаются также PageControl и ее закладки, поскольку они принадлежат форме. Как динамически создавать закладки

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

var
TabSheet : TTabSheet;
TabSheet := TTabSheet.Create(Self);

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

TabSheet.Caption := 'Закладка 1'; 

И, наконец, самая важное действие заключается в том, что новой странице необходимо сообщить, какому объекту PageControl она принадлежит. Это делается с помощью присваивания свойством TTabSheet PageControl переменной-ссылки TPageControl, типа той, которую мы создали выше (PageControl). Вот кусок кода, демонстрирующий вышесказанное:

TabSheet.PageControl := PageControl; 
Как динамически добавлять к страницам элементы управления

Ключевым моментом при создании и размещении элемента управления на странице TabSheet является назначение свойства Parent на переменную-ссылку класса TTabSheet. Вот пример:

var
Button : TButton;
Button := TButton.Create(Self);
Button.Caption := 'Кнопка 1';
Button.Parent := TabSheet;
Более подробно об объектах TPageControl и TTabSheet вы можете узнать в онлайн-документации, или посмотреть код файла ComCtrls.pas, расположенного в вашем каталоге ..\Delphi 2.0\SOURCE\VCL.

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

// Код использует форму с единственной на ней кнопкой.
unit DynamicTabSheetsUnit;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls,
  Forms, Dialogs, StdCtrls, Buttons;
type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
    procedure TestMethod(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;
var
  Form1: TForm1;
implementation
uses ComCtrls;
{$R *.DFM}
procedure TForm1.Button1Click(Sender: TObject);
var
  PageControl: TPageControl;
  TabSheet: TTabSheet;
begin
  // Создаем PageControl
  PageControl := TPageControl.Create(Self);
  PageControl.Parent := Self;
  // Создаем первую страницу и связываем ее с PageControl
  TabSheet := TTabSheet.Create(Self);
  TabSheet.Caption := 'Закладка 1';
  TabSheet.PageControl := PageControl;
  // Создаем первую страницу
  with TButton.Create(Self) do
  begin
    Caption := 'Кнопка 1';
    OnClick := TestMethod; // Назначаем обработчик события
    Parent := TabSheet;
  end;
  // Создаем вторую страницу и связываем ее с PageControl
  TabSheet := TTabSheet.Create(Self);
  TabSheet.Caption := ' Закладка 2';
  TabSheet.PageControl := PageControl;
end;
procedure TForm1.TestMethod(Sender: TObject);
begin
  ShowMessage('Привет');
end;
end.

Как удобнее работать с динамически формируемыми запросами.

Автор: Nomadic В процессе работы с БД иногда необходимо выполнить какие-то мелкие запросы. Держать для этого где-то временную Query меня лично ломает, посему ловите творение (под Delphi) - модуль для создания временных TQuery и работы с ними.

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

var
  S: string;
...
S := FastLookUp(format('select A.F1 from A,B where A.F4=B.F4 and
  B.F9=%d', [1]));
with GiveMeResultSet('select*from A where F1="777"') do
  try
    ....
  finally
    Free; {не забудьте!}
  end;
.....
  if not ExecuteSQL('delete from A') then
    ShowMessage('Something Wrong');
.....

Сам модуль идёт ниже.

{
Temporary Queries Creatin' and handlin'
(c) 1997-98 by Volok Alexander (D1/D2)
creation date: 30.10.1997
last update : 17.06.1998
}
unit TmpQuery;
interface
uses
  DBTables;
const
  InternalDBname = 'MAIN'; {Изменять по вкусу - TDataBase.DataBaseName}
type
  TSQLScript = {$IFDEF WIN32}string{$ELSE}PChar{$ENDIF};
  {Создаст куери с текстом запроса, но не откроет его}
function CreateTempQuery(SQLscript: TSQLscript): TQuery;
{Создаст куери и откроет запрос - не забудьте прибить}
function GiveMeResultSET(SQLscript: TSQLscript): TQuery;
{Проверит непустоту выборки, заданной ...}
function CheckExistence(SQLscript: TSQLscript): boolean;
{Вытащит аж одно значение(лукап) из выборки, заданной ...}
function FastLookUP(SQLscript: TSQLscript): string;
{Выполнит запрос и сообщит результат}
function ExecuteSQL(SQLscript: TSQLscript): boolean;
implementation
uses
  Forms;
function CreateTempQuery(SQLscript: TSQLscript): TQuery;
begin
  Result := TQuery.Create(Application);
  with Result do
  begin
    DatabaseName := InternalDBname;
{$IFDEF WIN32}
    SQL.Text := SQLscript;
{$ELSE}
    SQL.SetText(SQLscript);
{$ENDIF}
  end;
end;
function ExecuteSQL(SQLscript: TSQLscript): boolean;
begin
  with CreateTempQuery(SQLscript) do
  begin
    try
      ExecSQL;
      Result := True;
    except
      Result := False;
    end;
    Free;
  end;
end;
function CheckExistence(SQLscript: TSQLscript): boolean;
begin
  with GiveMeResultSET(SQLscript) do
  begin
    Result := not EOF;
    Free;
  end;
end;
function GiveMeResultSET(SQLscript: TSQLscript): TQuery;
begin
  Result := CreateTempQuery(SQLscript);
  with Result do
  try
    Open;
  except
    Free;
    Result := nil;
  end;
end;
function FastLookUP(SQLscript: TSQLscript): string;
begin
  with GiveMeResultSET(SQLscript) do
  begin
    try
      Result := Fields[0].AsString;
    except
      Result := '';
    end;
    Free;
  end;
end;
end.

Динамическое использование DLL.

Автор: Dr. Bob
Y3K. На пляже мама с ребенком. - Мам, а откуда берется песок? - Из микросхем. Их сердцевина состоит из кремния. Его собирают, перемалывают и сжигают, и получают песок.
...возможно ли заставить Delphi осуществлять только линковку ссылок на необходимые приложению DLL-функции с помощью директив компилятора или чего-то подобного? Я могу с ними динамически связаться с помощью GetProcAddress или функций линковки, но я был бы счастлив если была бы альтернатива попроще. (стилистика авторская - В.О.) Нет, даже в вашем случае вы должны использовать динамическое связывание... Часто кажется, что проще написать в модуле явное импортирование функций, чем использовать их динамическое связывание. Тем не менее, основное преимущество динамически загружаемых DLL в том, что ваше приложение может быть загружено и быть работоспособным и в случае отсутствия DLL, при условии, что приложение может к этому "приспособиться". Например, если если вы реализовали в своей DLL трехмерное представление вашего объекта, но DLL не была найдена (при попытке загрузить ее динамически), то приложение также будет работать, но объекты будут представлены в обычном, двумерном виде. Если DLL, о которой идет речь, будет связана статически, Windows откажется выполнять приложение (с претензией, что ваша 3D.DLL не была обнаружена), чему пользователь будет "неслыханно рад". Другое преимущество явного импорта в модуле в том, что приложение будет загружаться быстрее, поскольку динамически подгружаемые DLL будут загружены впоследствии, когда они действительно понадобятся.

Проследим, что получается, если вы используете в модуле явный импорт DLL или в вашем коде вы объявляете функцию DLL с внешним 'DLLNAME'. В этом случае компоновщик установит ссылки на DLL-функции в таблице импортируемых имен скомпонованного EXE-файла и загрузчик Windows автоматически загрузит DLL (во время выполнения главного блока) вместе с файлом EXE:

procedure Foo(X: Integer); external 'BAR' index 1;
Если в течение связывания (также называемое статической линковкой) Windows не сможет найти DLL, вы получите предупреждение - чаще всего File Error MessageBox (как раз то, в котором не указан ненайденный файл - ну очень "полезная" штуковина!). Поэтому вы должны быть абсолютно уверенными в том, что DLL будет доступна все то время, когда вы захотите ей воспользоваться, т.е. всегда.

Динамически связанная DLL, использующая модуль явного импорта, пользуется методами VB, каковые вы также можете использовать, но делая это "вручную". С помощью LoadLibrary по мере необходимости вы загружаете DLL, получаете указатели на экспортируемые функции, которые вы предполагаете использовать с GetProcAddress и освобождаете DLL с помощью FreeLibrary, когда она вам больше не нужна:

var
  Hbar: Thandle;
  Foo: procedure(X: Integer);
begin
  Hbar := LoadLibrary('BAR.DLL');
  if Hbar >= 32 then { успешно }
  begin
    Foo := GetProcAddress(HBar, 'FOO');
    ...
    Foo(1);
    ...
    FreeLibrary(HBar);
  end
  else
    MessageDlg('Ошибка: не могу найти BAR.DLL', mtError, [mbOk], 0)
end.

Удаление и добавление элементов в динамический массив.

Автор: http://sunsb.dax.ru Крутая штука динамический массив. Очень быстрая и здоровая реализация. Единственное, чего на мой взгляд не хватает, это механизма удаления элемента из середины массива и соответственно вставки в середину. Насколько я понял ( и проверил ), в памяти массив хранится по-разному в зависимости от типа его элементов. Скажм если в массиве строки(!! не shortString ) - хранятся указатели на них, а если прямоугольники (TRect) - то непосредственно сами прямоугольники.

Ниже привожу подпрограммы удаления и добавления элемента.

procedure delElem( var A:TRectArray; Index:integer );
var Last : integer;
begin
   Last:= high( A );
   if Index <  Last then move( A[Index+1], A[ Index ],
       (Last-Index) * sizeof( A[Index] )  );
   setLength( A, Last );
end;
procedure addElem( var A: TRectArray; Index: integer;
                                       ANew: TRect );
var Len : integer;
begin
   Len:= Length( A );
   if Index > = Len then Index := Len+1;
   setLength( A, Len+1);
   move( A[Index], A[ Index+1 ],
         (Len-Index) * sizeof( A[Index] ));
   A[Index] := ANew;
end;

Подпрограмма delElem полностью универсальна, а в addElem Вам нужно поменять тип добовляемого елемента (ANew) на требуемый.

Использование DynArrayFromVariant.

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

procedure TForm1.Button1Click(Sender: TObject);
 type
   T2DIntArray = array of array of Integer;
 var
   DynArray: T2DIntArray;
   V: Variant;
   i, k: Integer;
 begin
   V := VarArrayCreate([0, 1, 0, 1], varInteger);
   V[0, 0] := 00;
   V[0, 1] := 01;
   V[1, 0] := 10;
   V[1, 1] := 11;
   DynArrayFromVariant(Pointer(Dynarray), V, TypeInfo(T2DIntArray));
   for i := 0 to High(Dynarray) do
     for k := 0 to High(Dynarray[i]) do
       memo1.Lines.add(IntToStr(DynArray[i, k]));
 end;
 { 
  The problem with DynArrayFromVariant is that you can only use it on 
  variant arrays with 0-based indexes, trying to use it on an array with 1 
  as lower bound blows up. Since the documentation is silent on that i 
  would consider it a bug. 
}

Динамическая загрузка DLL.

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

{ 
  There are two possibilities to load a dll: 
  1. Static loading of a DLL means that the DLL is loaded when the application is executed. 
     This is the easier option to dynamic loading, as you'll see, however it means that if the DLL 
     is missing, your program will not run. 
}
   {Examples:}
   {a. Import a function called MYImportFunction from MYLIBRARY.dll}
   procedure MYImportFunction; external 'MYLIBRARY.dll';
   {b. Import a routine under a different name from the one it has in the library. }
   procedure MYImportFunction; external 'MYLIBRARY.dll' name 'MyNewImportFunctionName';
   {c. By ordinal value: Has to be the same value as when using the exports 
      keyword when making the DLL}
   procedure MYImportFunction; external 'MYLIBRARY.dll' index 10;
 { 
  2. Dynamic loading 
  By dynamically loading a DLL you decide at runtime which DLL to use. 
  This means you can give your program different functionality depending on which 
  DLL's are present (freeware or shareware versions of a program). 
  Or also if you want to load a library whose name or path must be computed at 
  run time or generated from user input. 
  Dynamic loading of a DLL loads the DLL in your application when 
  it is needed and unload it once you its work is done. 
  It requires more code to use, 
  however it more resource friendly than static loading. 
}
 {**********************************************************}
 { 
  Es gibt zwei Мцg1iсhкеitеn, eine DLL zu laden. 
  1. Statisches Laden 
  Beim statischen Laden wird die zu importierende Prozedur/Funktion mit "external" deklariert. 
  Die Datei 'MYLIBRARY.DLL' wird dann beim Programmstart geladen. 
}
   {Beispiele:}
   {a. Importiert eine Funktion MYImportFunction aus der Dll MYLIBRARY.dll }
   procedure MYImportFunction; external 'MYLIBRARY.dll';
   {b. Man kann auch eine Routine unter einem anderen Namen importieren als der in der dll. }
   procedure MYImportFunction; external 'MYLIBRARY.dll' name 'MyNewImportFunctionName';
   {c. Einen Index angeben: }
   procedure MYImportFunction; external 'MYLIBRARY.dll' index 10;
   // 2. Dynamisches Laden 
{ 
  Beim Dynamischen Laden erfolgt der Zugriff auf die Routinen mit 
  direkten Windows-API-Aufrufen (LoadLibrary, FreeLibrary, GetProcAddress). 
  Die importierten Routinen werden ььег prozedurale Variablen referenziert. 
  Die importierten DLLs werden erst bei der Аиsfьhгиng des Quelltextes geladen. 
  Somit wird Speicherplatz eingespart und das Programm wird auch аиsgеfьhгt, 
  wenn einige DLLs fehlen. 
}
 {**********************************************************}
 // Example for dynamically loading a DLL 
// Beispiel um eine Dll dynamisch zu laden: 
type
   TDLLFunction = function(someParam: TSomeType): TSomeOtherType;
 { 
  To execute such a function by name from a named DLL one could use a 
  "caller" function like 
}
 function CallDLLFunction(const dllname, functionname: string;
   theParameter: TSomeType): TSomeOtherType;
 var
   hDLL: THandle;
   theFunction: TDLLFunction;
   buf: array [0..144] of Char;
 begin
   // Get a handle to the DLL module. 
  // das Handle zum DLL Modul ermitteln. 
  hDLL := LoadLibrary(StrPCopy(buf, dllname));
   // If the handle is valid, try to get the function address. 
  // Wenn das Handle gь1tig ist, versuche die Adresse der Funktion zu ermitteln 
  if hDLL <> 0 then
   begin
     // Return the address of the specified exported (DLL) function. 
    // Adresse der Dll-Funktion ermitteln 
    try
       @theFunction := GetProcAddress(hDll, StrPCopy(buf, functionname));
       // If the function address is valid, call the function. 
      // Wenn die Funktion gefunden wurde... 
      if @theFunction <> nil then
         Result := theFunction(theParameter)
       else
         raise EDLLException.CreateFmt('Unable to link to function %s in DLL %s!',
           [functionname, dllname]);
     finally
       // Free the DLL module. 
      // Dll wieder freigeben. 
      FreeLibrary(hDLL);
     end;
   end
   else
     raise EDLLException.CreateFmt('Unable to load DLL %s!'#13#10 +
       'Reason: %s.', [dllname, DLLErrors[hDLL]]);
 end;

Динамический список.

unit dlist1_;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls;
type
  TForm1 = class(TForm)
    Label1: TLabel;
    Label2: TLabel;
    Label3: TLabel;
    Edit1: TEdit; // фамилия
    Edit2: TEdit; // имя
    Button1: TButton; // кнопка Добавить
    Button2: TButton; // кнопка Показать
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;
var
  Form1: TForm1;
implementation
{$R *.DFM}
type
  TPStudent = ^TStudent; //указатель на тип TStudent
  TStudent = record
    f_name: string[20]; // фамилия
    l_name: string[20]; // имя
    next: TPStudent; // следующий элемент списка
  end;
var
  head: TPStudent; // начало (голова) списка
// добавить элемент в начало списка
procedure TForm1.Button1Click(Sender: TObject);
var
  curr: TPStudent; // новый элемент списка
begin
  new(curr); // выделить память для элемента списка
  curr^.f_name := Edit1.Text;
  curr^.l_name := Edit2.Text;
   // добавление в начало списка
  curr^.next := head;
  head := curr;
   // очистить поля ввода
  Edit1.text := '';
  Edit2.text := '';
end;
// вывести список
procedure TForm1.Button2Click(Sender: TObject);
var
  curr: TPStudent; // текущий элемент списка
  n: integer; // длина (кол-во элементов) списка
  st: string; // строковое представление списка
begin
  n := 0;
  st := '';
  curr := head; // указатель на первый элемент списка
  while curr <> nil do
  begin
    n := n + 1;
    st := st + curr^.f_name + ' ' + curr^.l_name + #13;
    curr := curr^.next; // указатель на следующий элемент
  end;
  if n <> 0
    then ShowMessage('Список:' + #13 + st)
  else ShowMessage('В списке нет элементов.');
end;
end.
Скачать весь проект

Динамический список 2.

unit dlist2_;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls;
type
  TForm1 = class(TForm)
    Label1: TLabel;
    Label2: TLabel;
    Button1: TButton;
    Button2: TButton;
    Label3: TLabel;
    Edit1: TEdit;
    Edit2: TEdit;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure FormActivate(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;
var
  Form1: TForm1;
implementation
{$R *.DFM}
type
  TPStudent = ^TStudent; //указатель на тип TStudent
  TStudent = record
    f_name: string[20]; // фамилия
    l_name: string[20]; // имя
    next: TPStudent; // следующий элемент списка
  end;
var
  head: TPStudent; // начало (голова) списка
procedure TForm1.Button1Click(Sender: TObject);
var
  node: TPStudent; // новый узел списка
  curr: TPStudent; // текущий узел списка
  pre: TPStudent; // предыдущий, относительно curr, узел
begin
  new(node); // создание нового элемента списка
  node^.f_name := Edit1.Text; // фамилия
  node^.l_name := Edit2.Text; // имя
   // добавление узла в список
   // сначала найдем подходящее место в списке для узла
  curr := head;
  pre := nil;
   { Внимание!
     если приведенное ниже условие заменить
     на (node.f_name>curr^.f_name)and(curr<>NIL)
     то при добавлении первого узла возникает ошибка времени
     выполнения, так как curr = NIL и, следовательно,
     переменной curr.^name нет!
     В используемом варианте условия ошибка не возникает, так как
     сначала проверяется условие (curr <> NIL), значение которого
     FALSE и второе условие в этом случае не проверяется.
   }
  while (curr <> nil) and (node.f_name > curr^.f_name) do
  begin
     // введенное значение больше текущего
    pre := curr;
    curr := curr^.next; // к следующему узлу
  end;
  if pre = nil
    then
  begin
          // новый узел в начало списка
    node^.next := head;
    head := node;
  end
  else
  begin
          // новый узел после pre, перед curr
    node^.next := pre^.next;
    pre^.next := node;
  end;
  Edit1.text := '';
  Edit2.text := '';
  Edit1.SetFocus;
end;
procedure TForm1.Button2Click(Sender: TObject);
var
  curr: TPStudent; // текущий элемент списка
  n: integer; // длина (кол-во элементов) списка
  st: string; // строковое представление списка
begin
  n := 0;
  st := '';
  curr := head;
  while curr <> nil do
  begin
    n := n + 1;
    st := st + curr^.f_name + ' ' + curr^.l_name + #13;
    curr := curr^.next;
  end;
  if n <> 0
    then ShowMessage('Список:' + #13 + st)
  else ShowMessage('В списке нет элементов.');
end;
procedure TForm1.FormActivate(Sender: TObject);
begin
  head := nil;
end;
end.
Скачать весь проект

Динамический список 3.

unit dlist3_;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls;
type
  TForm1 = class(TForm)
    Label1: TLabel;
    Label2: TLabel;
    Button1: TButton;
    Button2: TButton;
    Label3: TLabel;
    Edit1: TEdit;
    Edit2: TEdit;
    Button3: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure FormActivate(Sender: TObject);
    procedure Button3Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;
var
  Form1: TForm1;
implementation
{$R *.DFM}
type
  TPStudent = ^TStudent; //указатель на тип TStudent
  TStudent = record
    f_name: string[20]; // фамилия
    l_name: string[20]; // имя
    next: TPStudent; // следующий элемент списка
  end;
var
  head: TPStudent; // начало (голова) списка
procedure TForm1.Button1Click(Sender: TObject);
var
  node: TPStudent; // новый узел списка
  curr: TPStudent; // текущий узел списка
  pre: TPStudent; // предыдущий, относительно curr, узел
begin
  new(node); // создание нового элемента списка
  node^.f_name := Edit1.Text;
  node^.l_name := Edit2.Text;
   // добавление узла в список
   // сначала найдем подходящее место в списке для узла
  curr := head;
  pre := nil;
   { Внимание!
     если приведенное ниже условие заменить
     на (node.f_name>curr^.f_name)and(curr<>NIL)
     то при добавлении первого узла возникает ошибка времени
     выполнения, так как curr = NIL и, следовательно,
     переменной curr.^name нет!
     В используемом варианте условия ошибка не возникает, так как
     сначала проверяется условие (curr <> NIL), значение которого
     FALSE и второе условие в этом случае не проверяется.
   }
  while (curr <> nil) and (node.f_name > curr^.f_name) do
  begin
     // введенное значение больше текущего
    pre := curr;
    curr := curr^.next; // к следующему узлу
  end;
  if pre = nil
    then
  begin
          // новый узел в начало списка
    node^.next := head;
    head := node;
  end
  else
  begin
          // новый узел после pre, перед curr
    node^.next := pre^.next;
    pre^.next := node;
  end;
  Edit1.text := '';
  Edit2.text := '';
  Edit1.SetFocus;
end;
procedure TForm1.Button2Click(Sender: TObject);
var
  curr: TPStudent; // текущий элемент списка
  n: integer; // длина (кол-во элементов) списка
  st: string; // строковое представление списка
begin
  n := 0;
  st := '';
  curr := head;
  while curr <> nil do
  begin
    n := n + 1;
    st := st + curr^.f_name + ' ' + curr^.l_name + #13;
    curr := curr^.next;
  end;
  if n <> 0
    then ShowMessage('Список:' + #13 + st)
  else ShowMessage('В списке нет элементов.');
end;
procedure TForm1.FormActivate(Sender: TObject);
begin
  head := nil;
end;
// щелчок на кнопке Удалить
procedure TForm1.Button3Click(Sender: TObject);
var
  curr: TPStudent; // текущий, проверяемый узел
  pre: TPStudent; // предыдущий узел
  found: boolean; // TRUE - узел, который надо удалить, есть в списке
begin
  if head = nil then
  begin
    MessageDlg('Список пустой!', mtError, [mbOk], 0);
    Exit;
  end;
  curr := head; // текущий узел - первый узел
  pre := nil; // предыдущего узла нет
  found := FALSE;
  // найти узел, который надо удалить
  while (curr <> nil) and (not found) do
  begin
    if (curr^.f_name = Edit1.Text) and (curr^.l_name = Edit2.Text)
      then found := TRUE // нужный узел найден
    else // к следующему узлу
    begin
      pre := curr;
      curr := curr^.next;
    end;
  end;
  if found then
  begin
            // нужный узел найден
    if MessageDlg('Узел будет удален из списка!',
      mtWarning, [mbOk, mbCancel], 0) <> mrYes
      then Exit;
            // удаляем узел
    if pre = nil
      then head := curr^.next // удаляем первый узел списка
    else pre^.next := curr.next;
    Dispose(curr);
    MessageDlg('Узел' + #13 +
      'Имя:' + Edit1.Text + #13 +
      'Фамилия:' + Edit2.Text + #13 +
      'удален из списка.',
      mtInformation, [mbOk], 0);
  end
  else // узла, который надо удалить, в списке нет
    MessageDlg('Узел' + #13 +
      'Имя:' + Edit1.Text + #13 +
      'Фамилия:' + Edit2.Text + #13 +
      'в списке не найден.',
      mtError, [mbOk], 0);
  Edit1.Text := '';
  Edit1.Text := '';
  Edit1.SetFocus;
end;
end.
Скачать весь проект

Простое использование TParser.

Автор: Mike Scott ...можно ли с помощью TParser "вытащить" из строки целочисленные значения, разделенные между собой пробелом, например, из строки '345 998 223 546 777' получить пять чисел?

Как этот пример? Он парсирует текст из поле редактирования с именем Edit1 и помещает полученные числа с список с именем Listbox1:

procedure TForm1.Button1Click(Sender: TObject);
var
  Stream: TStream;
  s: string;
begin
  Stream := TMemoryStream.Create;
  try
    { получаем текст области редактирования и помещаем его в поток }
    s := Edit1.Text;
    Stream.WriteBuffer(s[1], length(s));
    { сбрасываем стартовую позицию потока и создаем парсер }
    Stream.Position := 0;
    with TParser.Create(Stream) do
    try
      { "добываем" числа и добавляем их в список }
      while Token <> toEOF do
      begin
        CheckToken(toInteger); { возбуждаем исключение, если не toInteger }
        Listbox1.Items.Add(IntToStr(TokenInt));
        NextToken;
      end;
    finally
      Free;
    end;
  finally
    Stream.Free;
  end;
end;

Как вы можете видеть, бОльшая часть кода тривиальна: работа с потоком, его создание и освобождение, и парсер, естественно, с обработкой ошибок<g>. CheckToken проверяет, является ли текущий признак ожидаемым, и, если нет, генерирует исключительную ситуацию. В нашем случае мы ищем целые (toInteger), и читаем их из TParser.TokenInt. Просто!

Расчет расстояния между двумя точками на земной поверхности.

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

{ **** UBPFD *********** by delphibase.endimus.com ****
>> Расчет расстояния между двумя точками на земной поверхности.
Расчет расстояния между 2мя точками на земной поверхности методом Винсенти.
Dimka Maslov:
Lat1, Lon1 - широта и долгота точки 1 в градусах
Lat2, Lon2 - широта и долгота точки 2 в градусах
Функция возвращает результат в метрах.
Автор, правда, забыл упомянуть о правиле знаков для южных широт и западных
долгот...
Зависимости: Math
Автор:       Вячеслав
Copyright:   Опубликован в Survey Review №175 за Апрель 1976г.
Дата:        19 марта 2003 г.
***************************************************** }
function Vincenty(Lat1, Lon1, Lat2, Lon2: Extended): Extended;
const // Параметры эллипсоида:
  a = 6378245.0;
  f = 1 / 298.3;
  b = (1 - f) * a;
  EPS = 0.5E-30;
var
  APARAM, BPARAM, CPARAM, OMEGA, TanU1, TanU2,
    Lambda, LambdaPrev, SinL, CosL, USQR, U1, U2,
    SinU1, CosU1, SinU2, CosU2, SinSQSigma, CosSigma,
    TanSigma, Sigma, SinAlpha, Cos2SigmaM, DSigma: Extended;
begin
  lon1 := lon1 * (PI / 180);
  lat1 := lat1 * (PI / 180);
  lon2 := lon2 * (PI / 180);
  lat2 := lat2 * (PI / 180); //Пересчет значений координат в радианы
  TanU1 := (1 - f) * Tan(lat1);
  TanU2 := (1 - f) * Tan(lat2);
  U1 := ArcTan(TanU1);
  U2 := ArcTan(TanU2);
  SinCos(U1, SinU1, CosU1);
  SinCos(U2, SinU2, CosU2);
  OMEGA := lon2 - lon1;
  lambda := OMEGA;
  repeat //Начало цикла итерации
    LambdaPrev := lambda;
    SinCos(lambda, SinL, CosL);
    SinSQSigma := (CosU2 * SinL * CosU2 * SinL) +
      (CosU1 * SinU2 - SinU1 * CosU2 * CosL) *
      (CosU1 * SinU2 - SinU1 * CosU2 * CosL);
    CosSigma := SinU1 * SinU2 + CosU1 * CosU2 * CosL;
    TanSigma := Sqrt(SinSQSigma) / CosSigma;
    if TanSigma > 0 then
      Sigma := ArcTan(TanSigma)
    else
      Sigma := ArcTan(TanSigma) + Pi;
    if SinSQSigma = 0 then
      SinAlpha := 0
    else
      SinAlpha := CosU1 * CosU2 * SinL / Sqrt(SinSQSigma);
    if (Cos(ArcSin(SinAlpha)) * Cos(ArcSin(SinAlpha))) = 0 then
      Cos2SigmaM := 0
    else
      Cos2SigmaM := CosSigma -
        (2 * SinU1 * SinU2 / (Cos(ArcSin(SinAlpha)) * Cos(ArcSin(SinAlpha))));
    CPARAM := (f / 16) * Cos(ArcSin(SinAlpha)) * Cos(ArcSin(SinAlpha)) *
      (4 + f * (4 - 3 * Cos(ArcSin(SinAlpha)) * Cos(ArcSin(SinAlpha))));
    lambda := OMEGA + (1 - CPARAM) * f * SinAlpha * (ArcCos(CosSigma) +
      CPARAM * Sin(ArcCos(CosSigma)) * (Cos2SigmaM + CPARAM * CosSigma *
      (-1 + 2 * Cos2SigmaM * Cos2SigmaM)));
  until Abs(lambda - LambdaPrev) < EPS; // Конец цикла итерации
  USQR := Cos(ArcSin(SinAlpha)) * Cos(ArcSin(SinAlpha)) *
    (a * a - b * b) / (b * b);
  APARAM := 1 + (USQR / 16384) *
    (4096 + USQR * (-768 + USQR * (320 - 175 * USQR)));
  BPARAM := (USQR / 1024) * (256 + USQR * (-128 + USQR * (74 - 47 * USQR)));
  DSigma := BPARAM * SQRT(SinSQSigma) * (Cos2SigmaM + BPARAM / 4 *
    (CosSigma * (-1 + 2 * Cos2SigmaM * Cos2SigmaM) - BPARAM / 6 * Cos2SigmaM *
    (-3 + 4 * SinSQSigma) * (-3 + 4 * Cos2SigmaM * Cos2SigmaM)));
  Result := b * APARAM * (Sigma - DSigma);
end;

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

procedure TForm1.Button1Click(Sender: TObject);
var
  R: Extended;
begin
  R := Vicenty(59.8833, 30.2333, 55.7667, 37.5833);
  ShowMessageFmt('%g', [R]);
end;

Как сэкономить память в программах.

Что общего между морской свинкой и женщиной-программистом? То, что морская свинка - она и не свинка, и не морская.

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

Но сначала обратимся к основам типов данных, используемых в Delphi, и детально рассмотрим - сколько байт памяти занимает каждый тип данных:

1 байтbооlеаn, char and byte 2 байтsmаllInt, word, wordbool 4 байтstring, pointers, longint, integer

Теперь давайте посмотрим на объявление класса в нашем исходном коде:

TMyClass = class
  private
    field1: char;
    field2: longint;
    field3: boolean;
    field4: string;
    field5: byte;
    field6: integer;
  public
    procedure proc1;
end;

Теперь просуммируем байты, которы занимает каждый тип данных. По идее должно получиться 15 байт, но на самом деле это не так. Реальный размер, занимаемый данным экземпляром класса будет составлять 24 байта, т. е. 4 байта на каждое поле. Почему ? Потому что поумолчанию в Delphi, по правилам оптимизации, каждое поле располагается от предыдущего со сдвигом на 4 байта: field1 занимает 1 байт, поидее поле field2 должно следовать сразу же за field1, но по правилам оптимизации, остаются 3 байта не содержащие никакой информации, а следовательно напрасно потерянные. А если бы field2 был бы длиной в 1 байт или 2 байта, то он был бы помещён сразу же за полем field1, потому что это не нарушает правил оптимизации.

Какой же напрашивается вывод ? А если изменить порядок объявления переменных в классе ? Я просто сгруппировал переменные по их размеру (байтовому): вместе все однобайтовые, соответственно вместе все двухбайтовые и т.д.

Вот так стал выглядеть наш класс:

TMyClass = class
  private
    field1: char;
    field3: boolean;
    field5: byte;
    field2: longint;
    field4: string;
    field6: integer;
  public
    procedure proc1;
end;

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

Обработка исключений EDBEngineError.

Error 109: Error 108.
Информация, описывающая условия возникновения ошибки BDE, может быть получена приложением с помощью исключения EDBEngineError. Перехват и обработка в приложениях исключений EDBEngineError осуществляется с помощью конструкции try..except. При наступлении исключения EDBEngineError должен быть создан объект EDBEngineError, различные поля в котором могут быть использованы программистом для определения источника ошибки и принятия решения по исправлению ситуации. Для данного типа исключения может генерироваться более чем одно сообщение об ошибке. Для получения нужной информации необходимо "проиграть" все сообщения. Вот поля, наиболее соответствующие нашему контексту: ErrorCount: тип Integer; указывает количество ошибок в свойстве Errors; отсчет начинается с нуля. Errors: тип TDBError; набор записей типа структуры, содержащей информацию о каждой специфической сгенерированной ошибке; каждая запись доступна через номер индекса, имеющего тип Integer. Errors.ErrorCode: тип DBIResult; указывает код ошибки BDE для ошибки в текущей записи Errors. Errors.Category: тип Byte; категория ошибки, ссылается на поле ErrorCode. Errors.SubCode: тип Byte; субкод значения ErrorCode. Errors.NativeError: тип LongInt; код удаленной ошибки, возвращаемой сервером; если ноль, то ошибка не является ошибкой сервера; в этом поле возвращается код запроса SQL. Errors.Message: тип TMessageStr; если ошибка является ошибкой сервера, то сообщение сервера содержится в текущей записи Errors; если это не ошибка сервера, то это сообщение об ошибке BDE.В случае наличия конструкции try..except, объект EDBEngineError создается непосредственно в секции except. После создания объекта исключения все его области становятся доступными, ссылка на объект может передаваться в другую процедуру для детального исследования ошибок. Метод передачи объекта EDBEngineError в специализированную процедуру для приложения предпочтительнее, поскольку делает код приложения более модульным и уменьшает общий объем кода, необходимого для синтаксического анализа объекта и получения информации о возникшей ошибке. В качестве альтернативы можно предложить идею создания специализированного компонента, предусматривающего описанную функциональность; такое решение позволяет распространять созданный в основе компонента код среди нескольких приложений. Приведенный ниже пример демонстрирует создание объекта DBEngineError, передачу его процедуре и синтаксический разбор объекта для получения информации об ошибке.

В конструкции try..except для создания экземпляра DBEngineError необходим синтаксис, показаный ниже:

procedure TForm1.Button1Click(Sender: TObject);
var
  i: Integer;
begin
  if Edit1.Text > ' ' then 
  begin
    Table1.FieldByName('Number').AsInteger := StrToInt(Edit1.Text);
    try
      Table1.Post;
    except 
      on E: EDBEngineError do
        ShowError(E);
    end;
  end;
end;
В данной процедуре сделана попытка изменить значение поля таблицы и вызвать метод Post соответствующего компонента TTable. Ошибка при попытке отправить измененные данные перехватывается в секции try..except. В случае возникновения EDBEngineError, выполнение передается в секцию except, где происходит создание объекта EDBEngineError (E) и передача его процедуре ShowError. Обратите внимание на то, что в этой секции во внимание берутся только исключения EDBEngineError. В реальной программе код должен содержать проверку на другие типы исключений.

Процедура ShowError в качестве параметра получает ссылку на объект EDBEngineError и "опрашивает" его на предмет наличия информации об ошибках. В приведенном ниже примере информация об ошибках отображается в компоненте TMemo. В реальной жизни отображение ошибок может и не понадобится, но информация о них является логикой работы приложения, расширяющая его функциональность так, чтобы оно могло правильно реагировать на них. Первым шагом необходимо вычислить количество реально произошедших ошибок. Эта величина хранится в свойстве ErrorCounnt. Данное свойство имеет тип Integer и может использоваться для создания цикла, в теле которого можно по очереди получить информацию о каждой ошибке, содержащейся в объекте. В теле цикла поочередно ErrorCount-раз перебираются все содержащиеся в объекте ошибки (как вы помните, каждая ошибка представляет собой запись-элемент свойства Errors), "достается" о них вся информация и помещается в компонент TMemo.

procedure TForm1.ShowError(AExc: EDBEngineError);
var
  i: Integer;
begin
  Memo1.Lines.Clear;
  Memo1.Lines.Add('Количество ошибок: ' + IntToStr(AExc.ErrorCount));
  Memo1.Lines.Add('');
  {Перебираем все записи Errors}
  for i := 0 to AExc.ErrorCount - 1 do
  begin
    Memo1.Lines.Add('Сообщение: ' + AExc.Errors[i].Message);
    Memo1.Lines.Add(' Категория: ' +
      IntToStr(AExc.Errors[i].Category));
    Memo1.Lines.Add(' Код ошибки: ' +
      IntToStr(AExc.Errors[i].ErrorCode));
    Memo1.Lines.Add(' Субкод: ' +
      IntTooStr(AExc.Errors[i].SubCode));
    Memo1.Lines.Add(' Реальная ошибка: ' +
      IntToStr(AExc.Errors[i].NativeError));
    Memo1.Lines.Add('');
  end;
end;

Создать редактируемый TListBox.

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

{NOTE : This will also work with an OwnerDraw ListBox 
But be sure you "pad" the Left Property of the Edit so 
Any icons etc, do not get covered up by the TEdit. 
And also be aware that there will be a bit of "flashing" 
if you do use an OwnerDraw ListBox}
 {This is in a Popup menu.}
 procedure TForm1.Change1Click(Sender: TObject);
 var
   I9: Integer;
   ColInt: Integer;
   LRect: TRect;
 begin
   LRect := ListBox1.ItemRect(ListBox1.ItemIndex);
   {Set the size of the TEdit}
   Edit1.Top := LRect.Top + 1;
   Edit1.Left := LRect.Left + 1;
   Edit1.Width := ListBox1.Canvas.TextWidth(ListBox1.Items.Strings[ListBox1.ItemIndex]) + 6;
   Edit1.Height := (LRect.Bottom - LRect.Top) + 1;
   Edit1.Text := ListBox1.Items.Strings[ListBox1.ItemIndex];
   ListBox1.Selected[ListBox1.ItemIndex] := False;
   Edit1.Visible := True;
   Edit1.SelectAll;
   Edit1.SetFocus;
 end;
 procedure TForm1.ListBox1Click(Sender: TObject);
 begin
   Edit1.Visible := False;
 end;
 procedure TForm1.Edit1KeyPress(Sender: TObject; var Key: Char);
 var
   I8: Integer;
 begin
   if Key = #13 then
   begin
     I8 := ListBox1.ItemIndex;
     ListBox1.Items.Delete(ListBox1.ItemIndex);
     ListBox1.Items.Insert(I8, Edit1.Text);
     Edit1.Visible := False;
     Key := #0;
   end;
 end;

Массив Edit-компонентов.

Не стОит давать деньги программисту (особенно WINDOWS-программисту), т.к. если ему удастся на них что-нибудь купить , то он вернет Вам 0 (ноль) как результат успешно выполненного действия.
procedure DoSomethingWithEditControls;
var
  K: Integer;
  EditArray: array[0..99] of Tedit;
begin
  try
    for K := 0 to 99 do
    begin
      EditArray[K] := TEdit.Create(Self);
      EditArray[K].Parent := Self;
      {Устанавливаем необходимые свойства TEdit}
      SetSomeOtherPropertiesOfTEdit;
      Left := 100;
      Top := K * 10;
      {Что-то делаем при перемещении мыши}
      OnMouseMove := WhatToDoWhenMouseIsMoved;
    end;
    {Делаем все что хотим с полученным массивом Edit-компонентов}
    DoWhateverYouWantToDoWithTheseEdits;
  finally
    for K := 0 to 99 do
      EditArray[K].Free;
  end;
end;

Матрица на основе TEdit.

Автор: Mike Orriss
Для продвижения своих многоплатформных решений, одна из компьютерных компаний решила создать свой формат HELP-файлов на базе ".RTF" (rich text format). Новый формат было решено назвать ".RTFM"
...массив ячеек TEdit будет просто замечательным, но я не могу понять как мне их создавать и делать видимыми.

Допустим, если они имеют имена с Edit1 по Edit 9, то можно попробовать сделать так:

var
  eds: array[1..3,1..3] of TEdit;
  ix: integer;
  ed: TEdit;
begin
  for ix := 0 to 8 do 
  begin
    ed := FindComponent('Edit'+IntToStr(ix+1)) as TEdit;
    if ed <> nil then 
      eds[ix div 3 + 1,ix mod 3 + 1] := ed;
  end;
end;

Затем, допустим, вам захотелось скопировать текст из строки 1 в строку 2:

for ix := 1 to 3 do
  eds[2,ix].Text := eds[1,ix].Text;

Числовая маска компонента Edit.

Автор: Олег Леонтьев
Я здесь лежал, и здесь мои друзья С таким похмельем долго жить нельзя Средь глючных вИндов в пьяной тишине, Какой-то юзер подпевает мне...

Я предлагаю обработать событие OnKeyPress следующим образом:

procedure TForm1.Edit1KeyPress(Sender: TObject; var Key: Char);
begin
  if Key = ',' then
    Key := DecimalSeparator; // запятую заменяем на разделитель для числа
  if Key = '.' then
    Key := DecimalSeparator; // точку заменяем на сепаратор (на всякий случай)
  if not (Key in ['0'..'9','-',DecimalSeparator,#8,#13]) // символы которые пропускаются
    or ((Key = DecimalSeparator) // все цифры знак минус разделитель
    and (POS (DecimalSeparator, Edit1.Text) > 0)) // запрет на второй сепаратор
    or ((Key = '-') and (POS ('-', Edit1.Text) > 0)) // запрет на второй минус
    or ((Key = '-') and (Edit1.SelStart <> 0)) // запрет на минус не вначале цифры
    then // (можно и предыдущей строчки но так лучше для понимания)
  begin
      Key := #0; // запрещенный символ заменяем нулем
      MessageBeep (MB_OK); // биб в спикир
  end;
end;

Преобразовать первую букву каждого слова к верхнему регистру в TEdit.

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

procedure TForm1.Edit1Change(Sender: TObject);
 var
   OldChange: TNotifyEvent;
   OldStart: Integer;
 begin
   with (Sender as TEdit) do
   begin
     OldChange := OnChange;
     OnChange  := nil;
     OldStart  := SelStart;
     if ((SelStart > 0) and (Text[SelStart - 1] = ' ')) or (SelStart = 1) then
     begin
       SelStart  := SelStart - 1;
       SelLength := 1;
       SelText   := AnsiUpperCase(SelText);
     end;
     OnChange := OldChange;
     SelStart := OldStart;
   end;
 end;

Ограничение ввода в текстовое поле.

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

{ 
  In this exemple, the only keys allowed are the 
  same allowed in e-mail adresses. 
}
 procedure TForm1.Edit1KeyPress(Sender: TObject;
   var Key: Char);
 const
   AllowedChars: string = 'abcdefghijklmnopq' +
     'rstuvwxyz01234567_.@';
 var
   i: Integer;
   Ok: Boolean;
 begin
   i  := 0;
   Ok := False;
   { If you erase next line, user won't be able to type backspace }
   if Key = #8 then Ok := True;
   repeat
     i := i + 1;
     if Key = AllowedChars[i] then Ok := True;
   until (Ok) or (i = Length(AllowedChars));
   if not Ok then Key := #0;
 end;

Проблема потери фокуса для TEdit.

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

To Reproduce the Problem: Um das Problem nachzuvollziehen: 1. Drop two TEdits onto a form. Plaziere zwei TEdits auf einer Form. 2. In the OnExit Event of Edit1 add the following code: Im OnExit Ereignis von Edit1 schreibe diesen Code: Application.MessageBox( 'Title','...', mb_ok ); 3. Run the application. Starte die Anwendung. 4. First select Edit1 then Edit2 Klicke auf Edit und dann auf Edit2 5. The Message box is shown. Click the OK button, and the Caret has dissapeared! Eine MessageBox wird angezeigt. Klicke auf den OK Button und der Cursor ist verschwunden! 6. How to handle this: Wie man das umgehen kann:
 procedure TForm1.Edit1Exit(Sender: TObject);
 begin
  Application.MessageBox('qq','qq',mb_ok);
  if Assigned(ActiveControl) then
    PostMessage(ActiveControl.Handle,WM_SETFOCUS,0,0);
 end;

Исключить звуковой сигнал в поле ввода.

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

// Either disable the Beep in the OnKeyPress handler: 
// Unterdrucke den Beep-Ton entweder im OnKeyPress Ereignis: 
procedure TForm1.Edit1KeyPress(Sender: TObject; var Key: Char);
 begin
   if key = #13 then // #13 = Return 
  begin
     key := #0;
     // Code... 
  end;
 end;
 // Or in the OnKeyDown-Handler: 
// Oder im OnKeyDown Ereignis: 
procedure TForm1.Edit1KeyDown(Sender: TObject; var Key: Word;
   Shift: TShiftState);
 var
   Mgs: TMsg;
 begin
   if Key = VK_RETURN then
     PeekMessage(Mgs, 0, WM_CHAR, WM_CHAR, PM_REMOVE);
 end;

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

Перехватите событие KeyPress и установите key = #0 для недопустимых клавиш.

procedure TForm1.Edit1KeyPress(Sender: TObject; var Key: Char);
begin
  if ((UpCase(Key) < 'A') or (UpCase(Key) > 'Z')) then
    Key := #0;
end;

Элемент управления Edit, реагирующий на событие OnTimer.

Как-то раз встала такая проблема: если пользователь какое-то время ничего не вводит в элемент управления Edit, то предупредить его об этом.

unit EditOnTime;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls,
  Forms, Dialogs, ExtCtrls, StdCtrls;
type
  TEditOnTime = class(TEdit)
  private
    FInterval: integer;
    FTimer: TTimer;
    FOnTimer: TNotifyEvent;
    procedure SetInterval(Interval: integer);
    procedure Timer(Sender: TObject);
  protected
    procedure KeyPress(var Key: char); override;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
  published
    property Interval: integer read FInterval write SetInterval default 750;
    property OnTimer: TNotifyEvent read FOnTimer write FOnTimer;
end;
procedure register;
implementation
//******************* RegisterComponent
// Здесь мы регистрируем компонент в IDE
procedure register;
begin
  RegisterComponents('MPS', [TEditOnTime]);
end;
//******************* TEditOnTime.SetInterval
// устанавливаем интервал таймера
procedure TEditOnTime.SetInterval(Interval: integer);
begin
  FInterval := Interval;
  if Assigned(FTimer) then
    FTimer.Interval := FInterval;
end;
//******************* TEditOnTime.Create
constructor TEditOnTime.Create(AOwner: TComponent);
begin
  FInterval := 750;
  inherited Create(AOwner);
  if not (csDesigning in ComponentState) then
    try
      FTimer := TTimer.Create(self);
      FTimer.Enabled := false;
      FTimer.Interval := FInterval;
      FTimer.OnTimer := Timer;
    except
      FreeAndNil(FTimer);
    end;
end;
//******************* TEditOnTime.Destroy
destructor TEditOnTime.Destroy;
begin
  if Assigned(FTimer) then
    FreeAndNil(FTimer);
  inherited Destroy;
end;
//******************* TEditOnTime.OnTimer
procedure TEditOnTime.Timer(Sender: TObject);
begin
  FTimer.Enabled := false;
  if Assigned(FOnTimer) then
    FOnTimer(self);
end;
//******************* TEditOnTime.KeyPress
procedure TEditOnTime.KeyPress(var Key: char);
begin
  FTimer.Enabled := false;
  inherited KeyPress(Key);
  FTimer.Enabled := (Text <> '') and Assigned(FTimer)
    and Assigned(FOnTimer);
end;
end.

Замена текста в поле TEdit.

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

procedure ReplaceText(Edit: TCustomEdit; strOLD, strNEW: string);
 var
   x, Position: integer;
   tmpstr, tmpstr2: string;
 begin
   tmpstr := Edit.Text;
   for x := 0 to Length(Edit.Text) do
   begin
     if Copy(Edit.Text, x, Length(strold)) = strold then
     begin
       tmpstr   := Copy(Edit.Text, 0, x - 1) + strnew;
       Position := x;
     end;
   end;
   tmpstr2 := Edit.Text;
   if Position <> 0 then
     Edit.Text := tmpstr + Copy(tmpstr2, Position + Length(strOLD), Length(tmpstr2))
   else
     Edit.Text := tmpstr;
 end;
 // Example/Beispiel: 
procedure TForm1.Button1Click(Sender: TObject);
 begin
   ReplaceText(Edit1, 'OldWord', 'NewWord');
 end;
 // With the StringReplace function: 
procedure ReplaceText(Edit: TCustomEdit; strOLD, strNEW: string);
 begin
   Edit.Text := StringReplace(Edit1.Text, strOLD, strNEW, [rfReplaceAll]);
 end;

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

Утро. Квартира сисопа. Ночная подружка подходит к компу, смотрит в логи и говорит: - Да, и cps у тебя тоже маленький...
TEdit1  = class(TEdit)
  public
    procedure CreateParams(var Params: TCreateParams); Override;
end;
procedure TEdit1.CreateParams(var Params: TCreateParams);
begin
  inherited CreateParams(Params);
  Params.Style := Params.Style or ES_MULTILINE or ES_RIGHT;
end;

Edit с возможностью автоматического выбора.

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

Автор: Константин Хрипков
EDIT твою мать! FORMAT тебя за ногу!
Приведу письмо читателя:

...я тоже пишу в Delphi и могу поделиться своим опытом. В дополнение к этому письму, чтобы не быть голословным, прилагаю маленький компонент THintEdit, порожденный от TCustomEdit, который представляет собой с виду обычный TEdit элемент с возможностью автоматического выбора стринговых значений из скрытого списка (так, как это реализовано в Netscape Nаvigаtоr'е). Описание особенно не нужно, так как выполнено все достаточно элементарно: значения для выбора заносятся в свойство HintList, тип свойства TStrings. При нажатии клавиш вверх/вниз выбираются значения, соответствующие набранным начальным символам.

unit HintEdit;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls;
type
  THintEdit = class(TCustomEdit)
  private
    { Private declarations }
    FHintList: TStrings;
    Searching,
      CanSearch: boolean;
    CurSPos: integer;
  protected
    { Protected declarations }
    procedure Change; override;
    procedure KeyDown(var Key: Word; Shift: TShiftState); override;
  public
    { Public declarations }
    constructor Create(AOwner: TComponent); override;
    property HintList: TStrings read FHintList write FHintList;
    destructor Destroy; override;
  published
    { Published declarations }
    property Anchors;
    property AutoSelect;
    property AutoSize;
    property BiDiMode;
    property BorderStyle;
    property CharCase;
    property Color;
    property Constraints;
    property Ctl3D;
    property DragCursor;
    property DragKind;
    property DragMode;
    property Enabled;
    property Font;
    property HideSelection;
    property ImeMode;
    property ImeName;
    property MaxLength;
    property OEMConvert;
    property ParentBiDiMode;
    property ParentColor;
    property ParentCtl3D;
    property ParentFont;
    property ParentShowHint;
    property PasswordChar;
    property PopupMenu;
    property ReadOnly;
    property ShowHint;
    property TabOrder;
    property TabStop;
    property Text;
    property Visible;
    property OnChange;
    property OnClick;
    property OnDblClick;
    property OnDragDrop;
    property OnDragOver;
    property OnEndDock;
    property OnEndDrag;
    property OnEnter;
    property OnExit;
    property OnKeyDown;
    property OnKeyPress;
    property OnKeyUp;
    property OnMouseDown;
    property OnMouseMove;
    property OnMouseUp;
    property OnStartDock;
    property OnStartDrag;
  end;
procedure Register;
implementation
{$R *.DCR}
procedure Register;
begin
  RegisterComponents('Netscape', [THintEdit]);
end;
constructor THintEdit.Create;
begin
  inherited;
  FHintList := TStringList.Create;
  Searching := false;
  CanSearch := true;
  CurSPos := -1;
end;
procedure THintEdit.Change;
var
  i, l: integer;
begin
  if Searching then
    Exit;
  if not CanSearch then
    Exit;
  if Text = '' then
    exit;
  l := Length(Text);
  for i := 0 to FHintList.Count - 1 do
    if Copy(FHintList[i], 1, l) = Text then
    begin
      Searching := true;
      CurSPos := i;
      Text := FHintList[i];
      Searching := false;
      SelStart := Length(Text);
      SelLength := -(Length(Text) - l);
      break;
    end;
  inherited;
end;
procedure THintEdit.KeyDown;
var
  l: integer;
begin
  if Chr(Key) in ['A'..'z', 'А'..'Я', 'а'..'я'] then
    CanSearch := true
  else
    CanSearch := false;
  case Key of
    VK_DOWN:
      begin
        if (CurSPos < HintList.Count - 1) and (SelLength > 0) then
          if Copy(FHintList[CurSPos + 1], 1, SelStart) = Copy(Text, 1, SelStart)
            then
          begin
            l := SelStart;
            Inc(CurSPos);
            Text := FHintList[CurSPos];
            SelStart := Length(Text);
            SelLength := -(Length(Text) - l);
          end;
        Key := VK_RETURN;
      end;
    VK_UP:
      begin
        if (CurSPos > 0) and (SelLength > 0) then
          if Copy(FHintList[CurSPos - 1], 1, SelStart) = Copy(Text, 1, SelStart)
            then
          begin
            l := SelStart;
            Dec(CurSPos);
            Text := FHintList[CurSPos];
            SelStart := Length(Text);
            SelLength := -(Length(Text) - l);
          end;
        Key := VK_RETURN;
      end;
    VK_RETURN:
      begin
        SelStart := 0;
        SelLength := Length(Text);
      end;
  end;
  inherited;
end;
destructor THintEdit.Destroy;
begin
  FHintList.Free;
  inherited;
end;

Пасхальные яйца в Delphi и CBuilder.

Автор: Матвеев Игорь

Недавно я прочитал где-то о проекте JEDI и о том, что если в Delphi версии 6.0 (В остальных не знаю) в окне "О Delphi..." (Помошь->О Delphi...) при нажатой клавише Alt нажать последовательно J E D I - симпатично проплывет объявление об этом проекте. Сегодня я установил себе C++Builder и совершенно случайно заметил, что там все в том же диалоговом окне "About" при нажатой клавише Alt нажать T - показывается "C++Builder Team". Вот я и заинтересовался подобными штучками в продуктах Borland.

Итак, после определенного ряда испытаний удалось выяснить:

Delphi 6.0: (При нажатой клавише Alt)
1: J E D I Информация о пректе JEDI.
2: T E A M Команда разработчиков Delphi.
3: Q U A L I T Y Информация о Delphi QA Staff.
4: D E V E L O P E R S Информация о Delphi R&D Staff.
C++Builder 4.253 (При нажатой клавише Alt)
1: T Команда разработчиков C++Builder.
2: D Информация о C++Builder R&D Staff.
3: V E R S I O N Информация о версии.
Пока это все.

Если у кого-то есть подобная информация - пишите E-mail: teap_leap@mail.ru.

Эллипсовидное окно.

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

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

Сначала в частных объявлениях (после слова private) объявляем процедуру:

private
procedure WMNCHitTest(var M: TWMNCHitTest); message wm_NCHitTest;

Затем в разделе implementation описываем её так:

implementation
{$R *.DFM}
procedure TForm1.WMNCHitTest(var M: TWMNCHitTest);
begin
  inherited;
  if M.Result = htClient then
    M.Result := htCaption;
end;

Далее самое главное. По созданию окна (событие OnCreate) пишем следующий код:

procedure TForm1.FormCreate(Sender: TObject);
var
  hsWindowRegion: Integer;
begin
  hsWindowRegion := CreateEllipticRgn(50, 50, 350, 200);
  SetWindowRgn(Handle, hsWindowRegion, true);
end;

Ещё одна защита ваших shareware программ и как её снять.

Автор: Bad_guy WEB сайт: http://cracklab.narod.ru Многие люди стремятся заниматься программированием, но существует и такая часть компьютерных профессионалов, как крэкеры, причём крэкеры не стесняются обычно заниматья и программированием тоже. Существует слух, что крэкер серьёзной программы не напишет, с этим мнением мне лично сложно согласиться, однако если представить человека погружённого в крэк целиком, естественно можно предположить, что серьёзнее кеймекера он программы не напишет. Всё же большинство крэкеров не абсолютные энтузиасты и равно как и крэком любят заниматься и программироаванием, а может даже больше программированием, к коим я, собственно говоря, и отношусь. ... Время шло, мой опыт рос, и в один прекрасный момент мою голову поразила мысль о том, что надо бы интернет - то как-нибудь окупать, вот и решил я тогда взять ноги в руки, прокопать как следует свои дельфовые проекты и создать коммерческий продукт. Есть идея - есть дело и через пару недель мой первый коммерческий проект был закончен, не буду вам говорить что это за программа, скажу лишь что продавать её я решил только на западе, потому что у нас в стране хроническая привычка покупать софт только в переходах метро, может быть конечно это и неплохо, ну да ладно... Во время написания этой программы я не мог не удилить внимания её защите, ведь можно сказать это моя слабость, а можно и - специализация, но защиту эту поленившись я конечно придумал не 100% надёжности, да и понадеялся, что за её цену на Западе даже и руки не станет пачкать никакой крэкер (зря я конечно так-то сам ведь знаю, что здесь энтузиазм главное), кстати, почитав конференцию шароварщиков я понял, что выход крэка не подрывает целиком их бизнес, только малая доля потенциальных покупателей использует крэк, остальные - всё равно купят (для России я думаю дело всё-таки обстоит иначе), ну, пожалуй хватит лирики...

Суть моего метода защиты в программе была такова: Сначала я написал всю программу. Назвав её Freeware, можно было бы её выкладывать, но как бы не так (теперь я вообще наверно не одной фриверки не напишу - деньжатами запахло), затем я стал предпринимать попытки навешать всяких нагов и ограничений и это у меня тоже получилось. Защита была организована так: решено было использовать ключ, регистрационное имя в вычисление ключа не входило. В тех местах, где было ограничение (например, вместо окна сохранения, нажав кнопку "сохранить" вы получаете сообщение "Отключено в незарегверсии") я вставил вызов процедуры-пустышки, программа шла в ту пустышку, пустышка усиленно что-то шифровала, складывала, делила и _ВСЕГДА_ возвращала значение переменной, допустим число "17", этот значение сравнивалось с оригиналом и если не совпадало, происходил выход из программы, а если совпадало, то в переменную где было "17" записывался "0" и программа спокойно продолжала работать. В чём сила, брат, спросите вы ? А вот в чём вся сила: адрес этой вызываемой процедуры был не указан внутри кода, то есть было сделано не так:

Call 40152A

А вот так:

Mov eax, regprocaddr
Call eax

Причём если regprocaddr не был известен (был равен 0 в незарегистрированной версии), то конечно программа показывала сообщение "Отключено в незарегверсии" и не шла в тот код, regprocaddr же каждый пользователь получал следующим образом: regcode, который присылался пользователю состоял из двух частей long intеgегов, в строковом формате, эти две части хогились и это и есть regprocaddr. Теперь было так: если regprocaddr > 0 тогда программа попадала на этот код:

Mov eax, regprocaddr
Call eax
и если regprocaddr был неправильный, то программа вызывала черт знает что вместо процедуры и естественно вылезал exception, а если даже и попадала в какую-то процедуру, то всё равно вероятность того, что та процедура вернёт в переменную значение "17" нулевая и программа всё равно завершит работу. Вы, прочитав всё вышесказанное, скажете: а я обойду твою процедуру и сравнение с "17" тоже, согласен, но вы никогда не сможете подобрать regcode и обязательно должны будете попатчить программу, а этот портит всю красоту взлома, а, да я ещё не так прост и вам будет надо патчить прогу в 7-10 местах так-то, а теперь представьте себе ваш патчер, который будет пропатчивать 60-100 байт - во веселье-то. Так что, как видите, мною была осуществлена 100% защита от подбора ключа и слабенькая защитка от патча (расситанная в основном на отсутствие упорста у крэкера). Для полного понта я ещё и запаковал программу компрессором, ну ещё одна подножка неупорному крэкеру (в моей голове уже давно зараждается мысль "поправить" исходники UPX и таким образом создать свой компрессор). Самая главная пакость этого метода защиты (для меня) это то, что зная один правильный регистрационный код можно сделать кеймекер и я даже не узнаю какой из "законопослушных" "зарегистрированных" пользователей так постарался. А вообще я не жадный, получу 100$, а потом уж пускай хоть всю изломают эту защиту - для меня программа (интернет) уже окупится. P.S. Вот найдите теперь эту мою crackme.exe и потренируйтесь на практике, а то я уверен, что многие кто это всё прочитал не очень врубились в смысл.

Удачи вам в вашей деятельности, чем бы вы ни занимались, Bad_guy.

Извлечь Email адреса из DBX файлов Outlook.

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

unit ExtractEmailsFunc;
 interface
 uses
   Windows, SysUtils;
 procedure CheckEMail(FilePath: string);
 implementation
 var
   BufferSize: Integer;
 function VerifyFile(strFileName: string): Integer;
 var
   intErro: Integer;
   tsrFile: TSearchRec;
 begin
   intErro := FindFirst(strFileName, FaAnyFile, tsrFile);
   if intErro = 0 then Result := tsrFile.Size
    else
      Result := -1;
   FindClose(tsrFile);
 end;
 procedure CheckEMail(FilePath: string);
 var
   I: Integer;
   hFile: Integer;
   Buffer: PChar;
   StrEmail: string;
 begin
   hFile := FileOpen(FilePath, fmOpenRead);
   try
     if hFile = 0 then Exit;
     GetMem(Buffer, bufferSize + 1);
     ZeroMemory(Buffer, BufferSize + 1);
     try
       FileRead(hFile, Buffer^, BufferSize);
       I := 0;
       while I <= BufferSize - 1 do
        begin
         StrEmail := '';
         if Buffer[I] = '<' then
          begin
           Inc(I);
           while (Buffer[I] <> '@') and (I <= BufferSize) do
            begin
             if (Buffer[I] = CHR(45)) or (Buffer[I] = CHR(46)) or
               (Buffer[I] = CHR(90)) or ((Buffer[I] > CHR(49)) and (Buffer[I] <= CHR(57)))
               or ((Buffer[I] >= CHR(65)) and (Buffer[I] <= CHR(90))) or
               ((Buffer[I] >= CHR(97)) and (Buffer[I] <= CHR(122))) then
              begin
               StrEmail := StrEmail + Buffer[I];
             end
              else
              begin
               StrEmail := '';
               Break;
             end;
             Inc(I);
           end;
           if StrEmail <> '' then
            begin
             StrEmail := StrEmail + '@';
             Inc(I);
             while (Buffer[I] <> '.') and (I <= BufferSize) do
              begin
               if (Buffer[I] = CHR(45)) or (Buffer[I] = CHR(46)) or
                 (Buffer[I] = CHR(90)) or ((Buffer[I] >= CHR(49)) and (Buffer[I] <= CHR(57)))
                 or ((Buffer[I] >= CHR(65)) and (Buffer[I] <= CHR(90))) or
                 ((Buffer[I] >= CHR(97)) and (Buffer[I] <= CHR(122))) then
                begin
                 StrEmail := StrEmail + Buffer[I];
               end
                else
                begin
                 StrEmail := '';
                 Break;
               end;
               Inc(I);
             end;
             if StrEmail <> '' then
              begin
               StrEmail := StrEmail + '.';
               Inc(i);
               while (Buffer[I] <> '>') and (I <= BufferSize) do
                begin
                 if (Buffer[I] = CHR(45)) or (Buffer[I] = CHR(46)) or
                   (Buffer[I] = CHR(90)) or ((Buffer[I] >= CHR(49)) and (Buffer[I] <= CHR(57)))
                   or ((Buffer[I] >= CHR(65)) and (Buffer[I] <= CHR(90))) or
                   ((Buffer[I] >= CHR(97)) and (Buffer[I] <= CHR(122))) then
                  begin
                   StrEmail := StrEmail + Buffer[I];
                 end
                  else
                  begin
                   StrEmail := '';
                   Break;
                 end;
                 Inc(I);
               end;
               if StrEmail <> '' then
                begin
                 WriteLn(StrEmail);
                 Inc(I);
               end;
             end;
           end;
         end
          else
            Inc(I);
       end;
     finally
       FreeMem(Buffer);
     end;
   finally
     FileClose(hFile);
   end;
 end;
 begin
   BufferSize := VerifyFile(ParamStr(1));
   if BufferSize <= 0 then Exit;
   CheckEMail(ParamStr(1));
 end.

Отправить Email скрытно от пользователя.

Автор: Павел Ну если не устраивает дельфовый SMTP клиент...

Вот вам кусок,написанный на WinSock API пробуйте разобраться... это будет посложнее чем компоненты от NetMasters юзать... (Все это исполнялось в отдельном трейде,не забудьте про WSAStartUp/ClenUp) (каюсь, это кусок из моего трояна :).

function SMTPRecvReply(MailSocket: TSocket): Word;
var
  Buffer: array[1..1024] of Byte;
  Err: Integer;
  BufStr: string;
begin
  BufStr := '';
  Recv(MailSocket, Buffer, SizeOf(Buffer), 0);
  for Err := 1 to 3 do
    BufStr := BufStr + Chr(buffer[Err]);
  try
    Result := StrToInt(BufStr);
  except
    Result := 0;
  end;
end;
function SMTPSendString(MailSocket: TSocket; Str: string): Boolean;
var
  Buffer: array[1..1024] of Byte;
  Err: Integer;
begin
  Result := FALSE;
  ZeroMemory(@Buffer, SizeOf(Buffer));
  if Length(Str) > 1000 then
    Exit;
  for Err := 1 to Length(Str) do
    Buffer[Err] := Ord(Str[Err]);
  Buffer[Err] := 13;
  Buffer[Err + 1] := 10;
  Err := WinSock.send(MailSocket, buffer, Err + 1, 0);
  if Err > 0 then
    Result := TRUE;
end;
function SMTPConnectAndSendMail(Arg: Pointer): DWord; stdcall;
var
  MailSocket: TSocket;
  MailSent: Boolean;
  MTime: TSystemTime;
  SMTPServer: TSockAddrIn;
  Step: Byte;
  OutOfSend: Boolean;
begin
  MailSent := FALSE;
  OutOfSend := FALSE;
  MailSocket := socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
  ZeroMemory(@SMTPServer, SizeOf(SMTPServer));
  SMTPServer.sin_family := AF_INET;
  SMTPServer.sin_port := htons(25);
  SMTPServer.sin_addr.S_addr := inet_addr(PChar(smtp));
  Step := 0;
  if Connect(MailSocket, SMTPServer, SizeOf(SMTPServer)) = 0 then
    repeat
      case SMTPRecvReply(MailSocket) of
        220: SMTPSendString(MailSocket, 'HELO server.com');
        250: case STEP of
            0:
              begin
                SMTPSendString(MailSocket, 'MAIL FROM:' + mail);
                Inc(Step);
              end;
            1:
              begin
                SMTPSendString(MailSocket, 'RCPT TO:' + mail);
                Inc(Step);
              end;
            2:
              begin
                SMTPSendString(MailSocket, 'DATA');
                Inc(Step);
              end;
            3:
              begin
                SMTPSendString(MailSocket, 'QUIT');
                Inc(Step);
              end;
          end;
        354:
          begin
            SMTPSendString(MailSocket, 'From:' + ServerID);
            SMTPSendString(MailSocket, 'To:' + UserName);
            SMTPSendString(MailSocket, 'Subject:' + LocalIPS + ' : ' + port +
              ' At ' + DateTimeToStr(Now));
            SMTPSendString(MailSocket, CRLF);
            for count := 1 to (txt.Count - 1) do
              SMTPSendString(MailSocket, txt[count]);
            SMTPSendString(MailSocket, CRLF + '.' + CRLF);
          end;
        221:
          begin
            OutOfSend := True;
            MailSent := True
          end;
      else
        OutOfSend := True;
        file: //!Case
      end;
    until OutOfSend;
  CloseSocket(MailSocket);
end;
ExitThread(0);
end;

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

Автор: BadBoy.

{
smtp - ip адрес smtp сервера
port - порт smtp сервера, по умолчанию 25
from - адрес отправителя
dest - адрес получателя
subject - тема письма
body - текст писма
Возвращает True если письмо было успешно отправленно...
}
function mail(smtp: string; port: integer; from, dest, subject,
  body: string): bool;
const
  cl = #13#10;
var
WSAData: TWSAData;
  Host: TSockAddrIn;
  Sock: TSocket;
  res: Integer;
  buff: array[1..255] of Char;
  { отправляем данные через сокет }
  procedure senddata(str: string);
  var
    i: integer;
  begin
    for i := 1 to Length(str) do
      if send(Sock, str[i], 1, 0) = SOCKET_ERROR then
        exit;
  end;
  { получаем ответ от команды }
  function recvdata(accept: string): bool;
  var
    buff: array[1..255] of Char;
  begin
    res := recv(Sock, buff, SizeOf(buff), 0);
    Result := (Res = SOCKET_ERROR) or (Copy(buff, 1, 3) = accept);
  end;
begin
  try
    result := false;
    { инициализация сокета }
    WSAStartUp(257, WSAData);
    Sock := socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
    if Sock = INVALID_SOCKET then
      Exit;
    { устанавливаем хост и порт сервера }
    res := inet_addr(PChar(smtp));
    if res <= 0 then
      exit;
    Host.sin_family := AF_INET;
    Host.sin_port := htons(port);
    Host.sin_addr.S_addr := res;
    { подключаемся к серверу }
    if connect(Sock, Host, SizeOf(Host)) > 0 then
      Exit;
    { приветствие сервера }
    if not recvdata('220') then
      Exit;
    { EHLO }
    senddata('EHLO' + cl);
    if not recvdata('250') then
      Exit;
    { MAIL FROM: }
    senddata('MAIL FROM:' + from + cl);
    if not recvdata('250') then
      Exit;
    { RCPT TO: }
    senddata('RCPT TO:' + dest + cl);
    if not recvdata('250') then
      Exit;
    { DATA }
    senddata('DATA' + cl);
    if not recvdata('354') then
      Exit;
    { отправляем текст сообщения }
    senddata('Subject:' + subject + cl + cl + body + cl + '.');
    if not recvdata('250') then
      Exit;
    { отключаемся от сервера }
    senddata('QUIT' + cl);
    result := true;
  finally
    { убиваем сокет }
    closesocket(sock);
    WSACleanup;
  end;
end;
{
mail('127.0.0.1',25,'bboy-ne@yandex.ru' ,'admin@company.mail', 'subj', 'body text');
p.s.: Тестировалась на e-mail сервере Alt-N Daemond Pro for Windows v6.5.1...
Функция делалась на основе статьи "Простой протокол передачи почты (SMTP)"
}

Как отправить письмо и указать тему, текст сообщения.

Статьи по Delphi (006)…
Мужик работает на участке около своего дома. Из соседнего дома выходит его соседка-блондинка, открывает свой почтовый ящик, заглядывает туда, захлопывает и убегает обратно в дом. Немного погодя, соседка выбегает снова, заглядывает в почтовый ящик и снова убегает обратно. Когда все повторяется в третий раз, мужик не выдерживает: - В чем дело? Что-нибудь случилось? - Это точно, что что-то случилось. Мой дурацкий компьютер все время сообщает: "Уои'vе got mail"
Для отправки письма с помощью зарегистрированого клиента используется функция Windows - ShellExecute, где в качестве аргумента передается строка протокола Mailto. Для этого сделайте следующее: В разделе uses подключите ShellAPI В обработчике OnClick метки или клавиши введите следующий код.
ShellExecute(Handle, 'open', 'mailto:lalala@lala.ru?par1=value1&par1=value1&...',
nil, nil, SW_SHOWNORMAL);

Третий параметр функции ShellExecute() - это строка в соответствии с протоколом mailto и правилами оформления URL:

mailto: тип протокола (может быть http: в этом случае оставшая URL и параметры запроса). lalala@lala.ru адрес получателя, можно включать несколько адресов, разделяя из символом ";" ? разделитель параметров от адреса par1=value1 имя параметра и его значение & разделитель параметров

Протокол Mailto имеет следующую форму.

MAILTO:Recipients&Parameters
Поле Recipients может быть пустым, одиночным адресом и состоять из нескольких адресов, разделенных символом ";" Поле Parameters дополнительно и если оно есть то должно быть отделено символом "&". Параметры должны появляться в форме пары name/value. Следующий список описывает возможные параметры:
PARAMETER DESCRIPTION
CC= Carbon copy (дополнительные получатели) BCC= Blind carbon copy (дополнительные получатели, адреса которых не показываются остальным получателям) SUBJECT= Subject text (тема) BODY= Body text (текст)

Все данные указываемые в параметрах должны быть так называемые Internet safe characters. Используййте %0d для символа перевод строки (LF), %20 для пробела и так далее.

Пример:

mailto:email1;email2&cc=email3?subject=Это%20тема& 
body=это%20текст%20письма%0dЭто%20другая%20строка 

Вложенные свойства.

Автор: Mike Scott (Mobius Ltd)
- Почему программисты весят больше чем другие люди? - Потому, что в каждом килограмме у них 1024 грамма!

Для регистрации контейнерного класса в процедуре Register вы должны вызвать RegisterPropertyEditor. Приблизительно так:

RegisterPropertyEditor( TypeInfo( TYourContainedClass ),
  TYourContainer, 'YourContainedPropery', TClassProperty ) ;
Все это задокументировано в справке помощи по созданию компонентов. Первым всегда идет параметр TypeInfo() с контейнерным классом, затем сам контейнерный класс, затем имя контейнерного свойства, затем TClassProperty, который регистрирует расширение свойства класса. А где взять реальный примерчик?

Посмотрите как это делаю я. Сохраните приведенный модуль с именем CONTAIN.PAS и добавите его к вашей палитре.

unit Contain;
interface
uses
  SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
  Forms, Dialogs, ExtCtrls;
type
  TContainer = class(TComponent)
  private
    FImage: TImage;
  public
    constructor Create(AOwner: TComponent); override;
  published
    property Image: TImage read FImage write FImage;
  end;
procedure Register;
implementation
uses DsgnIntf;
constructor TContainer.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FImage := TImage.Create(Self);
end;
procedure Register;
begin
  RegisterComponents('Samples', [TContainer]);
  RegisterPropertyEditor(TypeInfo(TImage),
    TContainer,
    'Image',
    TClassProperty);
end;
end.

Использовать фильтр объема для изображения.

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

procedure Emboss(ABitmap : TBitmap; AMount : Integer);
 var
   x, y, i : integer;
   p1, p2: PByteArray;
 begin
   for i := 0 to AMount do
   begin
     for y := 0 to ABitmap.Height-2 do
     begin
       p1 := ABitmap.ScanLine[y];
       p2 := ABitmap.ScanLine[y+1];
       for x := 0 to ABitmap.Width do
       begin
         p1[x*3] := (p1[x*3]+(p2[(x+3)*3] xor $FF)) shr 1;
         p1[x*3+1] := (p1[x*3+1]+(p2[(x+3)*3+1] xor $FF)) shr 1;
         p1[x*3+2] := (p1[x*3+1]+(p2[(x+3)*3+1] xor $FF)) shr 1;
       end;
     end;
   end;
 end;

Как создать неактивное изображение из обычного (рельеф).

Статьи по Delphi (006)…
procedure TForm1.aaa(bmpFrom, bmpTo: Tbitmap);
var
  TmpImage, Monobmp: TBitmap;
  IRect: TRect;
begin
  MonoBmp := TBitmap.Create;
  TmpImage:=Tbitmap.Create;
  TmpImage.Width := bmpFrom.Width;
  TmpImage.Height := bmpFrom.Height;
  IRect := Rect(0, 0, bmpFrom.Width, bmpFrom.Height);
  TmpImage.Canvas.Brush.Color := clBtnFace;
  try
    with MonoBmp do
    begin
      Assign(bmpFrom);
      Canvas.Brush.Color := clBlack;
      if Monochrome then
      begin
        Canvas.Font.Color := clWhite;
        Monochrome := False;
        Canvas.Brush.Color := clWhite;
      end;
      Monochrome := True;
    end;
    with TmpImage.Canvas do
    begin
      Brush.Color := clBtnFace;
      FillRect(IRect);
      Brush.Color := clBlack;
      Font.Color := clWhite;
      CopyMode := MergePaint;
      Draw(IRect.Left + 1, IRect.Top + 1, MonoBmp);
      CopyMode := SrcAnd;
      Draw(IRect.Left, IRect.Top, MonoBmp);
      Brush.Color := clBtnShadow;
      Font.Color := clBlack;
      CopyMode := SrcPaint;
      Draw(IRect.Left, IRect.Top, MonoBmp);
      CopyMode := SrcCopy;
      bmpTo.assign(TmpImage);
      TmpImage.free;
    end;
  finally
    MonoBmp.Free;
  end;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
  aaa(image1.picture.bitmap, image2.picture.bitmap);
  Image2.invalidate;
end;

Удаление содержимого каталога.

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

{ **** UBPFD *********** by delphibase.endimus.com ****
>> Удаление содержимого каталога
Зависимости: Windows, SysUtils, Classes, Masks
Автор:       demon-777, demon-777@yandex.ru, Питер
Copyright:   Dimka Maslov
Дата:        26 мая 2002 г.
***************************************************** }
procedure DeleteFiles(Path: string);
var
  Mask: string;
  SearchRec: TSearchRec;
  FindResult: Integer;
  List: TStringList;
  i: Integer;
begin
  Mask := ExtractFileName(Path);
  if Mask = '' then
    Mask := '*.*';
  Path := ExtractFilePath(Path);
  if Path = '' then
    Path := IncludeTrailingBackslash(GetCurrentDir);
  List := TStringList.Create;
  try
    FindResult := FindFirst(Path + '*.*', faAnyFile, SearchRec);
    try
      while FindResult = 0 do
        with SearchRec do
        begin
          if ((Attr and faDirectory) = 0) and MatchesMask(Name, Mask) then
            List.Add(Name);
          FindResult := FindNext(SearchRec);
        end;
      for i := 0 to List.Count - 1 do
        DeleteFile(Path + List[i]);
    finally
      FindClose(SearchRec);
    end;
  finally
    List.Free;
  end;
end;

Очистить очередь сообщений клавиатуры и мышки.

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

procedure EmptyKeyQueue;
 var
   Msg: TMsg;
 begin
   while PeekMessage(Msg, 0, WM_KEYFIRST, WM_KEYLAST,
     PM_REMOVE or PM_NOYIELD) do;
 end;
 procedure EmptyMouseQueue;
 var
   Msg: TMsg;
 begin
   while PeekMessage(Msg, 0, WM_MOUSEFIRST, WM_MOUSELAST,
     PM_REMOVE or PM_NOYIELD) do;
 end;

Очистить корзину.

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

procedure EmptyRecycleBin;
 const
   SHERB_NOCONFIRMATION = $00000001;
   SHERB_NOPROGRESSUI = $00000002;
   SHERB_NOSOUND = $00000004;
 type
   TSHEmptyRecycleBin = function(Wnd: HWND;
                                 pszRootPath: PChar;
                                 dwFlags: DWORD): HRESULT;  stdcall;
 var
   SHEmptyRecycleBin: TSHEmptyRecycleBin;
   LibHandle: THandle;
 begin  { EmptyRecycleBin }
   LibHandle := LoadLibrary(PChar('Shell32.dll'));
   if LibHandle <> 0 then @SHEmptyRecycleBin :=
       GetProcAddress(LibHandle, 'SHEmptyRecycleBinA')
   else
   begin
     MessageDlg('Failed to load Shell32.dll.', mtError, [mbOK], 0);
     Exit;
   end;
   if @SHEmptyRecycleBin <> nil then
     SHEmptyRecycleBin(Application.Handle,
                       nil,
                       SHERB_NOCONFIRMATION or SHERB_NOPROGRESSUI or SHERB_NOSOUND);
   FreeLibrary(LibHandle); @SHEmptyRecycleBin := nil;
 end;
 procedure TForm1.Button1Click(Sender: TObject);
 begin
   EmptyRecycleBin;
 end;
 { 
 Minimum operating systems: Windows XP/2000, Windows NT 4.0 with Internet Explorer 4.0, 
                            Windows 98, Windows 95 with Internet Explorer 4.0 
 (shell32.dll version 4.71 or later) 
}

Очистка таблицы (с проверкой).

TTable имеет метод EmptyTable.

Пример:

try
  with Table1 do
  begin
    Close;
    Exclusive := true;
    Open;
    EmptyTable;
    Close;
  end;
except
  on E: EDBEngineError do
    ShowMessage(E.Message);
end;

Очистить TImage.

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

// clear a Image's picture 
Image1.Picture := nil;
 // set the standard windows icon for the application 
Application.Icon := nil;

Как позволить или запретить выдачу звукового сигнала в динамик (Beep).

С помощью функции Beep можно выводить звуковой сигнал. напишите это слово по событию OnTimer для компонента TTimer и вы услышите звуковые сигналы. Теперь вы сможете запрещать или разрешать подобные Веер'ы.

uses shellapi;
//выключить
systemparametersinfo(spi_setbeep, 0, nil, 0);
//включить
systemparametersinfo(spi_setbeep, 1, nil, 0);

Эмуляция нажатия клавиши в активном окне.

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

{ **** UBPFD *********** by delphibase.endimus.com ****
>> Эмуляция нажатия клавиши в активном окне
VKey - код виртуальной клавиши (см. описание констант VK_xxxx)
Зависимости: Windows
Автор:       Dimka Maslov, mainbox@endimus.ru, ICQ:148442121, Санкт-Петербург
Copyright:   Dimka Maslov
Дата:        29 апреля 2002 г.
***************************************************** }
procedure PressKey(VKey: Byte);
begin
  keybd_event(VKey, 0, 0, 0);
  keybd_event(VKey, 0, KEYEVENTF_KEYUP, 0);
end;

64-битное кодирование 4.

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

const
   Codes64 = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz+/';
 function Encode64(S: string): string;
 var
   i: Integer;
   a: Integer;
   x: Integer;
   b: Integer;
 begin
   Result := '';
   a := 0;
   b := 0;
   for i := 1 to Length(s) do
   begin
     x := Ord(s[i]);
     b := b * 256 + x;
     a := a + 8;
     while a >= 6 do
     begin
       a := a - 6;
       x := b div (1 shl a);
       b := b mod (1 shl a);
       Result := Result + Codes64[x + 1];
     end;
   end;
   if a > 0 then
   begin
     x := b shl (6 - a);
     Result := Result + Codes64[x + 1];
   end;
 end;
 function Decode64(S: string): string;
 var
   i: Integer;
   a: Integer;
   x: Integer;
   b: Integer;
 begin
   Result := '';
   a := 0;
   b := 0;
   for i := 1 to Length(s) do
   begin
     x := Pos(s[i], codes64) - 1;
     if x >= 0 then
     begin
       b := b * 64 + x;
       a := a + 6;
       if a >= 8 then
       begin
         a := a - 8;
         x := b shr a;
         b := b mod (1 shl a);
         x := x mod 256;
         Result := Result + chr(x);
       end;
     end
     else
       Exit;
   end;
 end;

Кодирование и раскодирование паролей.

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

// This example shows how you can encrypt strings 
// using special security string. 
// You can decode data only if you know security string. 
// I suppose, there is no chance to hack security string, using any analyse algorythms. 
// Every time you call this function, you will 
// have a new result even if all params are constant 
// NOTE: Don`t forget to call "Randomize" proc before using this functions. 
const
    Codes64 = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz+/';
   // you must use this function to generate special 
  // security string, which is used in main encode/decode routines. 
  // NOTE: you must generate the security string only once and then use it in encode/decode functions. 
function GeneratePWDSecutityString: string;
 var
    i, x: integer;
   s1, s2: string;
 begin
   s1 := Codes64;
   s2 := '';
   for i := 0 to 15 do
   begin
     x  := Random(Length(s1));
     x  := Length(s1) - x;
     s2 := s2 + s1[x];
     s1 := Copy(s1, 1,x - 1) + Copy(s1, x + 1,Length(s1));
   end;
   Result := s2;
 end;
 // this function generate random string using 
// any characters from "CHARS" string and length 
// of "COUNT" - it will be used in encode routine 
// to add "noise" into your encoded data. 
function MakeRNDString(Chars: string; Count: Integer): string;
 var
    i, x: integer;
 begin
   Result := '';
   for i := 0 to Count - 1 do
   begin
     x := Length(chars) - Random(Length(chars));
     Result := Result + chars[x];
     chars := Copy(chars, 1,x - 1) + Copy(chars, x + 1,Length(chars));
   end;
 end;
 // This will encode your data. 
// "SecurityString" must be generated using method 
// described above, and then stored anywhere to 
// use it in Decode function. 
// "Data" is your string (you can use any characters here) 
// "MinV" - minimum quantity of "noise" chars before each encoded data char. 
// "MaxV" - maximum quantity of "noise" chars before each encoded data char. 
function EncodePWDEx(Data, SecurityString: string; MinV: Integer = 0;
   MaxV: Integer = 5): string;
 var
    i, x: integer;
   s1, s2, ss: string;
 begin
   if minV > MaxV then
    begin
      i := minv;
     minv := maxv;
      maxv := i;
    end;
   if MinV < 0 then MinV := 0;
   if MaxV > 100 then MaxV := 100;
   Result := '';
   if Length(SecurityString) < 16 then Exit;
   for i := 1 to Length(SecurityString) do
   begin
     s1 := Copy(SecurityString, i + 1,Length(securitystring));
     if Pos(SecurityString[i], s1) > 0 then Exit;
     if Pos(SecurityString[i], Codes64) <= 0 then Exit;
   end;
   s1 := Codes64;
   s2 := '';
   for i := 1 to Length(SecurityString) do
   begin
     x := Pos(SecurityString[i], s1);
     if x > 0 then s1 := Copy(s1, 1,x - 1) + Copy(s1, x + 1,Length(s1));
   end;
   ss := securitystring;
   for i := 1 to Length(Data) do
   begin
     s2 := s2 + ss[Ord(Data[i]) mod 16 + 1];
     ss := Copy(ss, Length(ss), 1) + Copy(ss, 1,Length(ss) - 1);
     s2 := s2 + ss[Ord(Data[i]) div 16 + 1];
     ss := Copy(ss, Length(ss), 1) + Copy(ss, 1,Length(ss) - 1);
   end;
   Result := MakeRNDString(s1, Random(MaxV - MinV) + minV + 1);
   for i := 1 to Length(s2) do Result := Result + s2[i] + MakeRNDString(s1,
       Random(MaxV - MinV) + minV);
 end;
 // This will decode your data, encoded with the function above, using specified "SecurityString". 
function DecodePWDEx(Data, SecurityString: string): string;
 var
    i, x, x2: integer;
   s1, s2, ss: string;
 begin
   Result := #1;
   if Length(SecurityString) < 16 then Exit;
   for i := 1 to Length(SecurityString) do
   begin
     s1 := Copy(SecurityString, i + 1,Length(securitystring));
     if Pos(SecurityString[i], s1) > 0 then Exit;
     if Pos(SecurityString[i], Codes64) <= 0 then Exit;
   end;
   s1 := Codes64;
   s2 := '';
   ss := securitystring;
   for i := 1 to Length(Data) do if Pos(Data[i], ss) > 0 then s2 := s2 + Data[i];
   Data := s2;
   s2   := '';
   if Length(Data) mod 2 <> 0 then Exit;
   for i := 0 to Length(Data) div 2 - 1 do
   begin
     x := Pos(Data[i * 2 + 1], ss) - 1;
     if x < 0 then Exit;
     ss := Copy(ss, Length(ss), 1) + Copy(ss, 1,Length(ss) - 1);
     x2 := Pos(Data[i * 2 + 2], ss) - 1;
     if x2 < 0 then Exit;
     x  := x + x2 * 16;
     s2 := s2 + chr(x);
     ss := Copy(ss, Length(ss), 1) + Copy(ss, 1,Length(ss) - 1);
   end;
   Result := s2;
 end;

Зашифровать и расшифровать строку.

unit uEncrypt; 
interface 
function Decrypt(const S: AnsiString; Key: Word): AnsiString; 
function Encrypt(const S: AnsiString; Key: Word): AnsiString; 
implementation 
const 
  C1 = 52845; 
  C2 = 22719; 
function Decode(const S: AnsiString): AnsiString; 
const 
  Map: array[Char] of Byte = (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, 62, 0, 0, 0, 63, 52, 53, 
    54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 
    3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 
    20, 21, 22, 23, 24, 25, 0, 0, 0, 0, 0, 0, 26, 27, 28, 29, 30, 
    31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 
    46, 47, 48, 49, 50, 51, 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, 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, 0, 0, 0, 0, 0, 0, 
    0); 
var 
  I: LongInt; 
begin 
  case Length(S) of 
    2: 
      begin 
        I := Map[S[1]] + (Map[S[2]] shl 6); 
        SetLength(Result, 1); 
        Move(I, Result[1], Length(Result)) 
      end; 
    3: 
      begin 
        I := Map[S[1]] + (Map[S[2]] shl 6) + (Map[S[3]] shl 12); 
        SetLength(Result, 2); 
        Move(I, Result[1], Length(Result)) 
      end; 
    4: 
      begin 
        I := Map[S[1]] + (Map[S[2]] shl 6) + (Map[S[3]] shl 12) + 
          (Map[S[4]] shl 18); 
        SetLength(Result, 3); 
        Move(I, Result[1], Length(Result)) 
      end 
  end 
end; 
function PreProcess(const S: AnsiString): AnsiString; 
var 
  SS: AnsiString; 
begin 
  SS := S; 
  Result := ''; 
  while SS <> '' do 
  begin 
    Result := Result + Decode(Copy(SS, 1, 4)); 
    Delete(SS, 1, 4) 
  end 
end; 
function InternalDecrypt(const S: AnsiString; Key: Word): AnsiString; 
var 
  I: Word; 
  Seed: Word; 
begin 
  Result := S; 
  Seed := Key; 
  for I := 1 to Length(Result) do 
  begin 
    Result[I] := Char(Byte(Result[I]) xor (Seed shr 8)); 
    Seed := (Byte(S[I]) + Seed) * Word(C1) + Word(C2) 
  end 
end; 
function Decrypt(const S: AnsiString; Key: Word): AnsiString; 
begin 
  Result := InternalDecrypt(PreProcess(S), Key) 
end; 
function Encode(const S: AnsiString): AnsiString; 
const 
  Map: array[0..63] of Char = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' + 
    'abcdefghijklmnopqrstuvwxyz0123456789+/'; 
var 
  I: LongInt; 
begin 
  I := 0; 
  Move(S[1], I, Length(S)); 
  case Length(S) of 
    1: 
      Result := Map[I mod 64] + Map[(I shr 6) mod 64]; 
    2: 
      Result := Map[I mod 64] + Map[(I shr 6) mod 64] + 
        Map[(I shr 12) mod 64]; 
    3: 
      Result := Map[I mod 64] + Map[(I shr 6) mod 64] + 
        Map[(I shr 12) mod 64] + Map[(I shr 18) mod 64] 
  end 
end; 
function PostProcess(const S: AnsiString): AnsiString; 
var 
  SS: AnsiString; 
begin 
  SS := S; 
  Result := ''; 
  while SS <> '' do 
  begin 
    Result := Result + Encode(Copy(SS, 1, 3)); 
    Delete(SS, 1, 3) 
  end 
end; 
function InternalEncrypt(const S: AnsiString; Key: Word): AnsiString; 
var 
  I: Word; 
  Seed: Word; 
begin 
  Result := S; 
  Seed := Key; 
  for I := 1 to Length(Result) do 
  begin 
    Result[I] := Char(Byte(Result[I]) xor (Seed shr 8)); 
    Seed := (Byte(Result[I]) + Seed) * Word(C1) + Word(C2) 
  end 
end; 
function Encrypt(const S: AnsiString; Key: Word): AnsiString; 
begin 
  Result := PostProcess(InternalEncrypt(S, Key)) 
end; 
end. 
{**************************************************************} 
// Example: 
{**************************************************************} 
procedure TForm1.Button1Click(Sender: TObject); 
const 
 my_key = 33189; 
var 
  sEncrypted, sDecrypted :AnsiString; 
begin 
  // Encrypt a string 
  sEncrypted := Encrypt('this is a sample text 
    to encrypt...abcd 123 {}[]?=)=(',my_key); 
  // Show encrypted string 
  ShowMessage(sEncrypted); 
  // Decrypt the string 
  sDecrypted := Decrypt(sEncrypted,my_key); 
   // Show decrypted string 
  ShowMessage(sDecrypted); 
end;

Шифрование исполняемого файла.

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

{ **** UBPFD *********** by delphibase.endimus.com ****
>> Шифрование исполняемого файла
В данном примере реализована процедура шифрования и расшифрования
одной только процедуры "вывода сообщения".Процедура изночально
не зашифровано ,что ни есть хорошо.Для того что бы процедура была
при запуске программы уже зашифрована надо написать внешнюю программку,
которая ба сканировала ехе-шник и находила определенные метки
(начальную и конечную) и шифровала бы всё между ними.Таким образом
вы получите готовый ехе-шник с уже зашифрованой(зашифроваными)процедурой.
Для того чтоб некоторым умным людям (crackers)жить было не легко,
после расшифровки и выполнения процедуры можно её опять зашифровать.
Более потробную информацию читайте в статье на «Королевства Delphi»
http://delphi.vitpc.com/mastering/safe/safe.htm
Там же читайте Открытый проект "Анти крэковые мучения"
http://delphi.vitpc.com/mastering/safe/index.htm
Зависимости: Windows, Messages, SysUtils, Variants, Classes,
Graphics, Controls, Forms,Dialogs, StdCtrls;
Автор:       DDA, Vologda
Copyright:   «Королевства Delphi» Защита от несанкционированного
использования программ, написанных на Delphi
Дата:        3 марта 2004 г.
***************************************************** }
unit Unit1;
interface
uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;
type
  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;
var
  Form1: TForm1;
implementation
{$R *.dfm}
procedure metka1;
//Начальная метка - нужна что бы находить потом в exe файле эти символы и знать
//откуда начинать шифровать
//Здесь метка представляет из себя десятичные числа от 0 до 256 через запятые
//Если метка 50,60,70,80,90 то это соответствует символам 2<FPZ
//Кстати, такого кол-во символов в метке может быть мало и лучше использовать больше
//Т.к если программа большая то такие последовательности могут встретиться не один раз
begin
  asm
    DB 50,60,70,80,90 //2<FPZ это метка начальная
  end;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
  beep;
  ShowMessage('Период использования программы истёк!');
end;
procedure metka2;
//Конечная метка - нужна чтобы знать до куда нужно шифровать в файле и
//докуда расшифровывать в памяти
begin
  asm
    DB 68,68,67,45,61 //DDA-= это метка конечная
  end;
end;
procedure TForm1.Button2Click(Sender: TObject);
//Процедура Расшифрования(В даном примере и зашифрования)
var
  ptrAddr: Pointer; {для Адреса процедуры вывода сообщения}
  dwOldProtect: DWORD;
begin
  ptrAddr := @TForm1.Button1Click; //Получаем адрес процедуры вывода сообщения
  VirtualProtect(@TForm1.Button1Click, 2048, PAGE_READWRITE, @dwOldProtect);
  //2048 это размер в байтах с которыми можно работать(по моему)
  //Если процедура большая то нужно подбирать соответствующий размер
  while ptrAddr <> @metka2 do
    //шифрование(слабое) выполняем пока не дойдем то конечной метки
  begin
    Byte(ptrAddr^) := Byte(ptrAddr^) xor $41;
    //каждый байт в памяти ксорится с кодом 65'A'(к примеру)
    //Используйте более надёжные алгоритмы шифрования
    inc(Integer(ptrAddr));
  end;
  exit; //нужно так сделать что б компилятор включил эти процедуры
  metka1; //в код,т.к Delphi не вставляет в результирующий код процедуры,
  metka2; //которые никогда не используются
end;
//Такое шифрование взято для примера и не рекомендуется для использования серьёзной защиты
//Также рекомендуется использовать и проверочную контрольную сумму для проверки
//целостности кода программы.
//Теперь осталось только сделать отдельную программу которая искала бы
//в этом exe файле начальную и конечную метки и шифровало бы всё между ними
//Но для демонстрации можно посмотреть результат и сейчас
//Запустите программу и нажмите на кнопку 1 -Должно вывестись сообщение и звук.сигнал
//А если нажать на кнопку 2 - то прцедура нажатия на кнопку 1 зашифруется
//И если после этого нажать на кнопку 1 -то должно произойти что-то не предсказуемое
//т.к процедура зашифрованна и что-то нормального вы не увидите.
//Источник информации: CopyRight «Королевства Delphi»
//"Защита от несанкционированного использования программ, написанных на Delphi"
end.

Генерация 96-битного ключа для шифрования с помощью Encrypt.

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

{ **** UBPFD *********** by delphibase.endimus.com ****
>> Генерация 96-битного ключа для шифрования с помощью Encrypt()
Генерация 96-битного ключа для шифрования Encrypt()
Зависимости: System
Автор:       kronprince, kronprince@mail.ru, ICQ:119018798, Полтава
Copyright:   © kronprince - собственная разработка
Дата:        23 июня 2003 г.
***************************************************** }
// Генерация 96-битного ключа для шифрования Encrypt()
procedure GenerateKey96(var StartKey, MultKey, AddKey: integer);
var
  i: integer;
  a: array[1..12] of byte;
  procedure FillKey(var VarKey: integer; index: integer);
  var
    j: integer;
  begin
    for j := 0 to sizeof(VarKey) - 1 do
      VarKey := VarKey or a[index + j] shl (8 * j);
  end;
begin
  System.Randomize();
  // Это непосредственно типа случайная генерация :)
  for i := Low(a) to High(a) do
    a[i] := System.Random(High(byte));
  // А тут распихивание по ключевым полям с соответствующей стартовой позиции
  FillKey(StartKey, 1);
  FillKey(MultKey, 5);
  FillKey(AddKey, 9);
end;

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

GenerateKey96(StartKey, MultKey, AddKey);
Encrypt(S, StartKey, MultKey, AddKey);

Шифрование текстовых полей таблицы Interbase.

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

{ **** UBPFD *********** by delphibase.endimus.com ****
>> Шифрование текстовых полей таблицы Interbase
Простенько но шустро шифрует текстовую строку VarChar в текстовую
строку VarChar, пригодную для сохранения в текстовом поле базы Interbase.
НЕ ЗАТРАГИВАЕТ ПРОБЕЛЫ!
Метод генерации (в начале) и изменения ключа (в 2-местах!) в вашей программе
лучше изменить от исходного в сторону усложнения, но не трогать "And 127" в конце.
Для работы с Char возможно стоит добавить "Trim(S1);" в самое начало.
Зависимости: Windows, SysUtils, DB
Автор:       Vemer, Vemer@List.Ru, Петрозаводск
Copyright:   Vemer
Дата:        16 ноября 2003 г.
***************************************************** }
function Morf(S1: string; T: Boolean): string;
var
  LS: Byte; //длина строки
  KB: Byte; //ключ, байт
  I: Byte; //Счетчик
  B: Byte; //байт строки
  W: Smallint; //буфер для ключа
  S2: string; //результат
begin
  LS := Length(S1);
  S2 := '';
  KB := LS * LS; //-------Изменить/Усложнить!And 127 не трогать!
  if T = False then // Выбор между шифрованием и дешифрованием
  begin
    for I := 1 to LS do
    begin //шифрование
      B := Byte(S1[I]);
      if B > 32 then
      begin
        W := B + KB;
        if W > 255 then
          W := W - 223;
        B := W;
      end; //If
      S2 := S2 + Chr(B);
      KB := (KB + (I * 2)) and 127;
      //-(1) Изменить/Усложнить! And 127 не трогать!
    end; //For
  end
  else
  begin //дешифровка
    for I := 1 to LS do
    begin
      B := Byte(S1[I]);
      if B > 32 then
      begin
        W := B - KB;
        if W < 33 then
          W := W + 223;
        B := W;
      end; //If
      S2 := S2 + Chr(B);
      KB := (KB + (I * 2)) and 127;
      //---(2) Сделать как в (1)! And 127 не трогать!
    end; //For
  end; //Else
  Result := S2;
end;

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

// В вычисляемом поле для отображения текста в нормальном виде:
TableNewName.Value := Morf(TableName.Value, True);
// Для занесения в таблицу в зашифрованном виде например из Edit:
TableName.Value := Morf(Edit.Text, False);

Шифрование текста по введенному паролю.

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

{ **** UBPFD *********** by delphibase.endimus.com ****
>> Шифрование текста
Процедура шифрует текст основываясь на введенном пароле.
Зависимости: Windows, SysUtils, Classes
Автор:       Danger, robinzon2000@pochtamt.ru, Киев
Copyright:   Danger
Дата:        04 мая 2002 г.
***************************************************** }
var
  s: string;
procedure Code(var text: string; password: string;
  decode: boolean);
var
  i, PasswordLength: integer;
  sign: shortint;
begin
  PasswordLength := length(password);
  if PasswordLength = 0 then
    Exit;
  if decode then
    sign := -1
  else
    sign := 1;
  for i := 1 to Length(text) do
    text[i] := chr(ord(text[i]) + sign *
      ord(password[i mod PasswordLength + 1]));
end;

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

procedure TForm1.Button1Click(Sender: TObject);
begin
  s := Memo1.Text;
  code(s, Edit1.Text, false);
  Memo1.Text := s;
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
  code(s, Edit1.Text, true);
  Memo1.Text := s;
end;

Простое симметричное шифрование строк.

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

{ **** UBPFD *********** by delphibase.endimus.com ****
>> Шифрование строки
Предназначена для простого шифрование строк и паролей, ключ 96 бит, шифрование
симметричное.
Зависимости: UBPFD.decrypt
Автор:       Anatoly Podgoretsky, anatoly@podgoretsky.com, Johvi
Copyright:   (c) Anatoly Podgoretsky, 1996
Дата:        26 апреля 2002 г.
***************************************************** }
const
  StartKey = 471; // Start default key
  MultKey = 62142; // Mult default key
  AddKey = 11719; // Add default key
  // обязательно смените ключи до использования
function Encrypt(const InString: string; StartKey, MultKey, AddKey: Integer):
  string;
var
  I: Byte;
  // Если поменять тип переменной I на Integer, то будет возможно
  // шифрование текста длиной более 255 символом - VID.
begin
  Result := '';
  for I := 1 to Length(InString) do
  begin
    Result := Result + CHAR(Byte(InString[I]) xor (StartKey shr 8));
    StartKey := (Byte(Result[I]) + StartKey) * MultKey + AddKey;
  end;
end;

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

if Encrypt(S, StartKey, MultKey, AddKey) <> OriginalPwd then
  ...
{ **** UBPFD *********** by delphibase.endimus.com ****
>> Расшифровка строки
Предназначена для расшифровки строки, ранее зашифрованной фукцией UBPFD.Encrypt
Зависимости: UBPFD.Encrypt
Автор:       Anatoly Podgoretsky, anatoly@podgoretsky.com, Johvi
Copyright:   (c) Anatoly Podgoretsky, 1996
Дата:        26 апреля 2002 г.
***************************************************** }
const
  StartKey = 471; // Start default key
  MultKey = 62142; // Mult default key
  AddKey = 11719; // Add default key
  // обязательно смените ключи до использования
{$R-}
{$Q-}
function Decrypt(const InString: string; StartKey, MultKey, AddKey: Integer):
  string;
var
  I: Byte;
  // Если поменять тип переменной I на Integer, то будет возможно
  // шифрование текста длиной более 255 символом - VID.
begin
  Result := '';
  for I := 1 to Length(InString) do
  begin
    Result := Result + CHAR(Byte(InString[I]) xor (StartKey shr 8));
    StartKey := (Byte(InString[I]) + StartKey) * MultKey + AddKey;
  end;
end;
{$R+}
{$Q+}

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

S := 'Ваш старый пароль: ';

Шифрование и дешифрование текстов по принципу S-Coder со скрытым ключом.

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

{ **** UBPFD *********** by delphibase.endimus.com ****
>> Шифрование и дешифрование текстов по принципу S-Coder со скрытым ключом
После прменения выполнения функции Encrypt с входным исходным текстом,
результат будет иметь символы в диапазоне от 0 - 255 в ASCII таблице,
следовательно если вам надо сохранить где либо шифровку, то лучше
преобразовать в числовой вид - 16разрядный hex из ASCII таблицы (функция Write)
Зависимости: Windows, SysUtils
Автор:       Yuri Btr, btr1978@rambler.ru, ICQ:125988632, Керчь
Copyright:   EFD Systems http://www.mindspring.com/~efd
Дата:        23 мая 2002 г.
***************************************************** }
procedure EnCipher(var Source: AnsiString);
{Low order, 7-bit ASCII (char. 32-127) encryption designed for database use.
 Control and high order (8 bit) characters are passed through unchanged.
 Uses a hybrid method...random table substitution with bit-mangled output.
 No passwords to worry with (the built-in table is the password). Not industrial
 strength but enough to deter the casual hacker or snoop. Even repeating char.
 sequences have little discernable pattern once encrypted.
 NOTE: When displaying encrypted strings, remember that some characters
       within the output range are interpreted by VCL components; for
       example, '&'.}
asm
    Push ESI //Save the good stuff
    Push EDI
    Or EAX,EAX
    Jz @Done
    Push EAX
    Call UniqueString
    Pop EAX
    Mov ESI,[EAX] //String address into ESI
    Or ESI,ESI
    Jz @Done
    Mov ECX,[ESI-4] //String Length into ECX
    Jecxz @Done //Abort on null string
    Mov EDX,ECX //initialize EDX with length
    Lea EDI,@ECTbl //Table address into EDI
    Cld //make sure we go forward
@L1:
    Xor EAX,EAX
    Lodsb //Load a byte from string
    Sub AX,32 //Adjust to zero base
    Js @Next //Ignore if control char.
    Cmp AX,95
    Jg @Next //Ignore if high order char.
    Mov AL,[EDI+EAX] //get the table value
    Test CX,3 //screw it up some
    Jz @L2
    Rol EDX,3
@L2:
    And DL,31
    Xor AL,DL
    Add EDX,ECX
    Add EDX,EAX
    Add AL,32 //adjust to output range
    Mov [ESI-1],AL //write it back into string
@Next:
    Dec ECX
    Jnz @L1
// Loop @L1 //do it again if necessary
@Done:
    Pop EDI
    Pop ESI
    Jmp @Exit
// Ret //this does not work with Delphi 3 - EFD 971022
@ECTbl: //The encipher table
    DB 75,85,86,92,93,95,74,76,84,87,91,94
    DB 63,73,77,83,88,90,62,64,72,78,82,89
    DB 51,61,65,71,79,81,50,52,60,66,70,80
    DB 39,49,53,59,67,69,38,40,48,54,58,68
    DB 27,37,41,47,55,57,26,28,36,42,46,56
    DB 15,25,29,35,43,45,14,16,24,30,34,44
    DB 06,13,17,23,31,33,05,07,12,18,22,32
    DB 01,04,08,11,19,21,00,02,03,09,10,20
@Exit:
end;
procedure DeCipher(var Source: AnsiString);
{Decrypts a string previously encrypted with EnCipher.}
asm
    Push ESI //Save the good stuff
    Push EDI
    Push EBX
    Or EAX,EAX
    Jz @Done
    Push EAX
    Call UniqueString
    Pop EAX
    Mov ESI,[EAX] //String address into ESI
    Or ESI,ESI
    Jz @Done
    Mov ECX,[ESI-4] //String Length into ECX
    Jecxz @Done //Abort on null string
    Mov EDX,ECX //Initialize EDX with length
    Lea EDI,@DCTbl //Table address into EDI
    Cld //make sure we go forward
@L1:
    Xor EAX,EAX
    Lodsb //Load a byte from string
    Sub AX,32 //Adjust to zero base
    Js @Next //Ignore if control char.
    Cmp AX,95
    Jg @Next //Ignore if high order char.
    Mov EBX,EAX //save to accumulate below
    Test CX,3 //unscrew it
    Jz @L2
    Rol EDX,3
@L2:
    And DL,31
    Xor AL,DL
    Add EDX,ECX
    Add EDX,EBX
    Mov AL,[EDI+EAX] //get the table value
    Add AL,32 //adjust to output range
    Mov [ESI-1],AL //store it back in string
@Next:
    Dec ECX
    Jnz @L1
// Loop @L1 //do it again if necessary
@Done:
    Pop EBX
    Pop EDI
    Pop ESI
    Jmp @Exit
// Ret Does not work with Delphi3 - EFD 971022
@DCTbl: //The decryption table
    DB 90,84,91,92,85,78,72,79,86,93,94,87
    DB 80,73,66,60,67,74,81,88,95,89,82,75
    DB 68,61,54,48,55,62,69,76,83,77,70,63
    DB 56,49,42,36,43,50,57,64,71,65,58,51
    DB 44,37,30,24,31,38,45,52,59,53,46,39
    DB 32,25,18,12,19,26,33,40,47,41,34,27
    DB 20,13,06,00,07,14,21,28,35,29,22,15
    DB 08,01,02,09,16,23,17,10,03,04,11,05
@Exit:
end;
procedure Crypt(var Source: Ansistring; const Key: AnsiString);
{Encrypt AND decrypt strings using an enhanced XOR technique similar to
 S-Coder (DDJ, Jan. 1990). To decrypt, simply re-apply the procedure
 using the same password key. This algorithm is reasonably secure on
 it's own; however,there are steps you can take to make it even more
 secure.
       1) Use a long key that is not easily guessed.
       2) Double or triple encrypt the string using different keys.
          To decrypt, re-apply the passwords in reverse order.
       3) Use EnCipher before using Crypt. To decrypt, re-apply Crypt
          first then use DeCipher.
       4) Some unique combination of the above
 NOTE: The resultant string may contain any character, 0..255.}
asm
    Push ESI //Save the good stuff
    Push EDI
    Push EBX
    Or EAX,EAX
    Jz @Done
    Push EAX
    Push EDX
    Call UniqueString
    Pop EDX
    Pop EAX
    Mov EDI,[EAX] //String address into EDI
    Or EDI,EDI
    Jz @Done
    Mov ECX,[EDI-4] //String Length into ECX
    Jecxz @Done //Abort on null string
    Mov ESI,EDX //Key address into ESI
    Or ESI,ESI
    Jz @Done
    Mov EDX,[ESI-4] //Key Length into EDX
    Dec EDX //make zero based
    Js @Done //abort if zero key length
    Mov EBX,EDX //use EBX for rotation offset
    Mov AH,DL //seed with key length
    Cld //make sure we go forward
@L1:
    Test AH,8 //build stream char.
    Jnz @L3
    Xor AH,1
@L3:
    Not AH
    Ror AH,1
    Mov AL,[ESI+EBX] //Get next char. from Key
    Xor AL,AH //XOR key with stream to make pseudo-key
    Xor AL,[EDI] //XOR pseudo-key with Source
    Stosb //store it back
    Dec EBX //less than zero ?
    Jns @L2 //no, then skip
    Mov EBX,EDX //re-initialize Key offset
@L2:
    Dec ECX
    Jnz @L1
@Done:
    Pop EBX //restore the world
    Pop EDI
    Pop ESI
end;
function Encrypt(text: Ansistring): Ansistring;
//Это прсто интерфейс функций EnCipher и Crypt для шифрования строки текста
begin
  {шифруем текст}
  EnCipher(Text);
  {зашифровываем ключом в Edit1}
  Crypt(Text, Form1.Edit1.Text);
  Result := Text;
end;
function Decrypt(text: Ansistring): Ansistring;
//Это прсто интерфейс функций EnCipher и Crypt для дешифрования строки текста begin
begin
  {расшифровываем ключом в Edit1}
  Crypt(Text, Form1.Edit1.Text);
  {расшифровываем результат}
  DeCipher(Text);
  Result := Text;
end;
function Write(text: Ansistring): Ansistring;
var
  i: integer;
begin
  Result := '';
  for i := 1 to Length(text) do
    {получаем hex код из текста}
    Result := Result + InttoHex(ord(text[i]), 2);
end;
function Read(text: Ansistring): Ansistring;
var
  i: integer;
begin
  Result := '';
  for I := 1 to Length(text) do
    if odd(i) then
      {получаем текст из hex кода}
      Result := Result + Chr(StrtoInt('$' + text[i] + text[i + 1]));
end;

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

// функции вставляем после Implementation и после {$R *.dfm}
// В программе пишем просто:
procedure TForm1.Button1Click(Sender: TObject);
begin
  {Задайте здесь ваш секретный ключ}
  Edit1.text := 'My special hidden key';
  {Шифруем и преобразовываем тест в Memo1 и заносим результат в Memo2}
  Memo2.text := Write(Encrypt(Memo1.text));
  {Дешифруем и преобразовываем тест в Memo2 и заносим результат в Memo3}
  Memo3.text := Decrypt(Read(Memo2.text));
  ));
end;

Алгоритм 128-битного шифрования (TEA).

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

{ **** UBPFD *********** by delphibase.endimus.com ****
>> Алгоритм 128-битного шифрования (TEA)
Зависимости: system
Автор:       Valts Silaputnins, valts@velns.org
Copyright:   Valts Silaputnins
Дата:        19 августа 2002 г.
***************************************************** }
unit ucrypt;
interface
type
  TEAKey = array[0..3] of cardinal;
//Use 64-bit aligned data size (8,16...) or else some data will be left unencrypted!
procedure TEA_Encode(Input, Output: pointer; size: integer; key: TEAKey);
procedure TEA_Decode(Input, Output: pointer; size: integer; key: TEAKey);
implementation
type
  TEAData = array[0..1] of cardinal;
  PTEAKey = ^TEAKey;
  PTEAData = ^TEAData;
procedure TEA_Cipher(v: PTEAData; var w: PTEAData; k: PTEAKey);
var
  y, z, sum, delta, n: Cardinal;
begin
  y := (v)[0];
  z := (v)[1];
  sum := 0;
  delta := $9E3779B9;
  n := 32;
  while (n > 0) do
  begin
    inc(y, (z shl 4 xor z shr 5) + z xor sum + (k)[sum and 3]);
    inc(sum, delta);
    inc(z, (y shl 4 xor y shr 5) + y xor sum + (k)[sum shr 11 and 3]);
    dec(n);
  end;
  (w)[0] := y;
  (w)[1] := z;
end;
procedure TEA_DeCipher(v: PTEAData; var w: PTEAData; k: PTEAKey);
var
  y, z, sum, delta, n: Cardinal;
begin
  y := v[0];
  z := v[1];
  sum := $0C6EF3720;
  delta := $9E3779B9;
  n := 32;
  while (n > 0) do
  begin
    dec(z, (y shl 4 xor y shr 5) + y xor sum + k[sum shr 11 and 3]);
    dec(sum, delta);
    dec(y, (z shl 4 xor z shr 5) + z xor sum + k[sum and 3]);
    dec(n);
  end;
  w[0] := y;
  w[1] := z;
end;
procedure TEA_EnDec(encode: boolean; Input, Output: pointer; size: integer; key:
  TEAKey);
var
  DataIn, DataOut: TEAData;
  DOut: PTEAData;
  i, sz: integer;
begin
  DOut := @DataOut;
  sz := (size shr 3) shl 3;
  i := 0;
  repeat
    DataIn[0] := Cardinal((pointer(Cardinal(Input) + Cardinal(i)))^);
    DataIn[1] := Cardinal((pointer(Cardinal(Input) + Cardinal(i + 4)))^);
    if encode then
      TEA_Cipher(@DataIn, DOut, @key)
    else
      TEA_DECipher(@DataIn, DOut, @key);
    Cardinal(pointer(Cardinal(Output) + Cardinal(i))^) := DataOut[0];
    Cardinal(pointer(Cardinal(Output) + Cardinal(i + 4))^) := DataOut[1];
    inc(i, 8);
  until i >= sz;
end;
procedure TEA_Encode(Input, Output: pointer; size: integer; key: TEAKey);
begin
  TEA_EnDec(true, Input, Output, size, key);
end;
procedure TEA_Decode(Input, Output: pointer; size: integer; key: TEAKey);
begin
  TEA_EnDec(false, Input, Output, size, key);
end;
end.

Шифрование текста.

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

При нажатии на Button1 эта программа шифрует текст из Memo1 при помощи пароля из Edit1. Результат сохраняется в строку s. Для наглядности зашифрованный текст также помещается в Memo1. При нажатии на Button2 текст из s расшифровывается. Если Вы нажмете Button1 два раза подряд, получится зашифрованный зашифрованный текст. Вернуть начальный текст можно будет двумя нажатиями на Button2. Но, поскольку в результате шифрования в строке могут появится.

var
  s: string;
procedure Code(var text: string; password: string;
  decode: boolean);
var
  i, PasswordLength: integer;
  sign: shortint;
begin
  PasswordLength := length(password);
  if PasswordLength = 0 then Exit;
  if decode
    then sign := -1
    else sign := 1;
  for i := 1 to Length(text) do
    text[i] := chr(ord(text[i]) + sign *
      ord(password[i mod PasswordLength + 1]));
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
  s := Memo1.Text;
  code(s, Edit1.Text, false);
  Memo1.Text := s;
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
  code(s, Edit1.Text, true);
  Memo1.Text := s;
end;

Шифрование текста 2.

{$I-,R-}
Unit Crypter;
interface
Uses Objects;
procedure EnCrypt(var Pntr: Array of Char; ArrLen: Word; password: string);
{ - Закриптовать блок }
procedure DeCrypt(var Pntr: Array of Char; ArrLen: Word; password: string);
{ - Раскиптовать блок }
procedure EnCryptStream(var st: tStream; Password: String);
{ - Закриптовать поток }
procedure DeCryptStream(var st: tStream; Password: String);
{ - Раскиптовать поток }
implementation
procedure EnCrypt(var Pntr: Array of Char; ArrLen:Word; password: string);
var
  len,pcounter: byte;
  x:Word;
begin
  len := length(password) div 2;
  pcounter := 1;
  for x:=0 to ArrLen-1 do begin
    Pntr[x] := chr(ord(password[pcounter]) + ord(Pntr[x]) + len);
    inc(pcounter);
    if pcounter > length(password) then pcounter := 1;
  end;
end;
procedure DeCrypt(var Pntr: Array of Char; ArrLen:Word; password: string);
var
  len,pcounter: byte;
  x:Word;
begin
  len := length(password) div 2;
  pcounter := 1;
  for x:=0 to ArrLen-1 do begin
    Pntr[x] := chr(ord(Pntr[x]) - ord(password[pcounter]) - len);
    inc(pcounter);
    if pcounter > length(password) then pcounter := 1;
  end;
end;
type
 pBuffer = ^tBuffer;
 tBuffer = Array[1..$FFFF] of Char;
procedure EnCryptStream(var st: tStream; Password: String);
 var
  buf: pBuffer;
  StSize, StPos, p: Longint;
 begin
  if (@st=nil) or (Password='') then exit;
  New(buf);
  StPos:=st.GetPos;
  StSize:=st.GetSize;
  st.Reset;
  st.Seek(0);
  repeat
   p:=st.GetPos;
   if SizeOf(Buf^)> St.GetSize-St.GetPosthen st.Read(buf^,St.GetSize-St.GetPos)
else st.Read(buf^,SizeOf(Buf^));
   EnCrypt(buf^,SizeOf(buf^),password);
   st.Reset;
   st.Seek(p);
   st.Write(buf^,SizeOf(Buf^));
  until (St.GetSize=St.GetPos);
  st.Seek(StSize);
  st.Truncate;
  st.Seek(StPos);
  Dispose(buf);
 end;
procedure DeCryptStream(var st: tStream; Password: String);
 var
  buf: pBuffer;
  StSize, StPos, p: Longint;
 begin
  if (@st=nil) or (Password='') then exit;
  New(buf);
  StPos:=st.GetPos;
  StSize:=st.GetSize;
  st.Reset;
  st.Seek(0);
  repeat
   p:=st.GetPos;
   if SizeOf(Buf^)> St.GetSize-St.GetPosthen st.Read(buf^,St.GetSize-St.GetPos)
else st.Read(buf^,SizeOf(Buf^));
   DeCrypt(buf^,SizeOf(buf^),password);
   st.Reset;
   st.Seek(p);
   st.Write(buf^,SizeOf(Buf^));
  until (St.GetSize=St.GetPos);
  st.Seek(StSize);
  st.Truncate;
  st.Seek(StPos);
  Dispose(buf);
 end;
end.

Шифрование строки InString, с возможностью корректного сохранения результата шифрования в TEXT-FILE.

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

{ **** UBPFD *********** by delphibase.endimus.com ****
>> Шифрование строки InString, с возможностью корректного
сохранения результата шифрования в TEXT-FILE
Функция представляет модификацию функции UBPFD.Encrypt.
Отличие от указанной функции заключается в том, что функция
EncryptEX возвращает результат, обработанный функцией
UBPFD.StгТоАsсii, т.е. обеспечивает возможность
корректного сохранения шифр-текста в текстовый файл.
Зависимости: UBPFD.Encrypt, UBPFD.StrToAscii
Автор:       VID, vidsnap@mail.ru, ICQ:132234868, Махачкала
Copyright:   VID
Дата:        30 апреля 2002 г.
***************************************************** }
function EncryptEX(const InString: string; StartKey, MultKey, AddKey: Integer):
  string;
begin
  Result := StrTOAscii(Encrypt(InString, StartKey, MultKey, AddKey));
end;
{ **** UBPFD *********** by delphibase.endimus.com ****
>> Расшифровка строки InString, зашифрованной с помощью функции EncryptEX
Функция является модификацией UBPFD.Decrypt. Отличие в том,
что UBPFD.DecryptEX расшифровывает шифр-текст, зашифрованный
с помощью функции UBPFD.EncryptEX
Зависимости: UBPFD.Decrypt, UBPFD.АsсiiТоStг
Автор:       VID, vidsnap@mail.ru, ICQ:132234868, Махачкала
Copyright:   VID
Дата:        30 апреля 2002 г.
***************************************************** }
function DecryptEX(const InString: string; StartKey, MultKey, AddKey: Integer):
  string;
begin
  Result := Decrypt(AsciiToStr(InString), StartKey, MultKey, AddKey);
end;

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

var
  S: string;
begin
  S := UBPFD.EncryptEX('String', 1, 1, 1);
  // S является источником данных для функции UBPFD.DecryptEX
  ShowMessage('Расшифровка: ' + UBPFD.DecryptEX(S, 1, 1, 1));
end;

URL кодирование строки.

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

{ **** UBPFD *********** by delphibase.endimus.com ****
>> URL кодирование строки
Функция производит так назваемое URL кодирование строк для использования
в http запросах. Т.е. все алфавитно-цифровые символы и знак подчёикивания
'_' остаются неизменными, пробел заменяется на '+', а все остальные символы
на знак процента '%' с двумя шестнадцатеричными цифрами.
Зависимости: Windows
Автор:       Dimka Maslov, mainbox@endimus.ru, ICQ:148442121, Санкт-Петербург
Copyright:   Dimka Maslov
Дата:        27 мая 2002 г.
***************************************************** }
function UrlEncode(Str: string): string;
  function CharToHex(Ch: Char): Integer;
  asm
    and eax, 0FFh
    mov ah, al
    shr al, 4
    and ah, 00fh
    cmp al, 00ah
    jl @@10
    sub al, 00ah
    add al, 041h
    jmp @@20
@@10:
    add al, 030h
@@20:
    cmp ah, 00ah
    jl @@30
    sub ah, 00ah
    add ah, 041h
    jmp @@40
@@30:
    add ah, 030h
@@40:
    shl eax, 8
    mov al, '%'
  end;
var
  i, Len: Integer;
  Ch: Char;
  N: Integer;
  P: PChar;
begin
  Result := '';
  Len := Length(Str);
  P := PChar(@N);
  for i := 1 to Len do
  begin
    Ch := Str[i];
    if Ch in ['0'..'9', 'A'..'Z', 'a'..'z', '_'] then
      Result := Result + Ch
    else
    begin
      if Ch = ' ' then
        Result := Result + '+'
      else
      begin
        N := CharToHex(Ch);
        Result := Result + P;
      end;
    end;
  end;
end;

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

procedure TForm1.Button1Click(Sender: TObject);
var
  S: string;
begin
  S := UrlEncode('Delphi World');
  ShellExecute(Handle, 'open',
    PChar('http://www.yandex.ru/yandsearch?text=' + S), '', '', SW_SHOWNORMAL);
end;
{ **** UBPFD *********** by delphibase.endimus.com ****
>> URL декодирование строки
Функция выполняет URL декодирование строки, заменяя все подстроки вида
'%HH', где 'HH' - шестнадцатеричные цифры, на соответствующие символы.
Зависимости: Windows
Автор:       Dimka Maslov, mainbox@endimus.ru, ICQ:148442121, Санкт-Петербург
Copyright:   Dimka Maslov
Дата:        27 мая 2002 г.
***************************************************** }
function UrlDecode(Str: string): string;
  function HexToChar(W: word): Char;
  asm
   cmp ah, 030h
   jl @@error
   cmp ah, 039h
   jg @@10
   sub ah, 30h
   jmp @@30
@@10:
   cmp ah, 041h
   jl @@error
   cmp ah, 046h
   jg @@20
   sub ah, 041h
   add ah, 00Ah
   jmp @@30
@@20:
   cmp ah, 061h
   jl @@error
   cmp al, 066h
   jg @@error
   sub ah, 061h
   add ah, 00Ah
@@30:
   cmp al, 030h
   jl @@error
   cmp al, 039h
   jg @@40
   sub al, 030h
   jmp @@60
@@40:
   cmp al, 041h
   jl @@error
   cmp al, 046h
   jg @@50
   sub al, 041h
   add al, 00Ah
   jmp @@60
@@50:
   cmp al, 061h
   jl @@error
   cmp al, 066h
   jg @@error
   sub al, 061h
   add al, 00Ah
@@60:
   shl al, 4
   or al, ah
   ret
@@error:
   xor al, al
  end;
  function GetCh(P: PChar; var Ch: Char): Char;
  begin
    Ch := P^;
    Result := Ch;
  end;
var
  P: PChar;
  Ch: Char;
begin
  Result := '';
  P := @Str[1];
  while GetCh(P, Ch) <> #0 do
  begin
    case Ch of
      '+': Result := Result + ' ';
      '%':
        begin
          Inc(P);
          Result := Result + HexToChar(PWord(P)^);
          Inc(P);
        end;
    else
      Result := Result + Ch;
    end;
    Inc(P);
  end;
end;

Кодировка полиалфавитным шифром Вигенера - xor кодировка.

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

{ **** UBPFD *********** by delphibase.endimus.com ****
>> Кодировка полиалфавитным шифром Вигенера - xor кодировка
Кодировка полиалфавитным шифром Вигенера - xor кодировка
одна функция для кодирования и декодирования
Input - входная строка. При кодировании это незакодированная строка,
при декодировнии это закодированная строка.
Key - слово ключ один и тот же в обоих случаях.
При совпадении символов во входной строке и
строке ключе на выходе получается символ '#0'.
Зависимости: Стандартные модули
Автор:       Ru, DiVo_Ru@rambler.ru, Одесса
Copyright:   DiVo 2002 creator Ru
Дата:        18 ноября 2002 г.
***************************************************** }
function VigenerCoDec(Input, Key: pchar): pchar
var
  i, j: integer;
  tmps, text: string;
begin
  text := Input;
  for i := 1 to length(text) do
  begin
    if i > length(key) then
      j := i mod length(key)
    else
      j := i;
    tmps := tmps + chr((ord(text[i])) xor (ord(key[j])));
  end;
  result := pchar(tmps);
end;

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

Text := edit1.text;
K := edit2.text;
Edit3.text := VigenerCoDec(Text, K);
// Закодировать:
Input := 'Привет я РУ';
Key := 'hello';
result := 'ГуднйзНвН++';
// Восстановить:
Input := 'ГуднйзНвН++';
Key := 'hello';
result := 'Привет я РУ';

Шифрование Bitmap.

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

procedure EncryptBMP(const BMP: TBitmap; Key: Integer);
 var
   BytesPorScan: Integer;
   w, h: integer;
   p: pByteArray;
 begin
   try
     BytesPorScan := Abs(Integer(BMP.ScanLine[1]) -
       Integer(BMP.ScanLine[0]));
   except
     raise Exception.Create('Error');
   end;
   RandSeed := Key;
   for h := 0 to BMP.Height - 1 do
   begin
     P := BMP.ScanLine[h];
     for w := 0 to BytesPorScan - 1 do
       P^[w] := P^[w] xor Random(256);
   end;
 end;
 procedure TForm1.Button1Click(Sender: TObject);
 begin
   EncryptBMP(Image1.Picture.Bitmap, 623);
   Image1.Refresh;
 end;
 { Call the function again to decrypt it }
 { Zum Entschlusseln die Funktion nochmals aufrufen }

Шифрование файлов, строк и памяти.

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

unit EZCrypt;
 {modeled by Ben Hochstrasser(bhoc@surfeu.ch) after some code snippet from borland}
 interface
 uses Windows, Classes;
 type
   TWordTriple = Array[0..2] of Word;
 function FileEncrypt(InFile, OutFile: String; Key: TWordTriple): boolean;
 function FileDecrypt(InFile, OutFile: String; Key: TWordTriple): boolean;
 function TextEncrypt(const s: string; Key: TWordTriple): string;
 function TextDecrypt(const s: string; Key: TWordTriple): string;
 function MemoryEncrypt(Src: Pointer; SrcSize: Cardinal; Target: Pointer; TargetSize: Cardinal; Key: TWordTriple): boolean;
 function MemoryDecrypt(Src: Pointer; SrcSize: Cardinal; Target: Pointer; TargetSize: Cardinal; Key: TWordTriple): boolean;
 implementation
 function MemoryEncrypt(Src: Pointer; SrcSize: Cardinal; Target: Pointer; TargetSize: Cardinal; Key: TWordTriple): boolean;
 var
   pIn, pOut: ^byte;
   i : Cardinal;
 begin
   if SrcSize = TargetSize then
   begin
     pIn := Src;
     pOut := Target;
     for i := 1 to SrcSize do
     begin
       pOut^ := pIn^ xor (Key[2] shr 8);
       Key[2] := Byte(pIn^ + Key[2]) * Key[0] + Key[1];
       inc(pIn);
       inc(pOut);
     end;
     Result := True;
   end else
     Result := False;
 end;
 function MemoryDecrypt(Src: Pointer; SrcSize: Cardinal; Target: Pointer; TargetSize: Cardinal; Key: TWordTriple): boolean;
 var
   pIn, pOut: ^byte;
   i : Cardinal;
 begin
   if SrcSize = TargetSize then
   begin
     pIn := Src;
     pOut := Target;
     for i := 1 to SrcSize do
     begin
       pOut^ := pIn^ xor (Key[2] shr 8);
       Key[2] := byte(pOut^ + Key[2]) * Key[0] + Key[1];
       inc(pIn);
       inc(pOut);
     end;
     Result := True;
   end else
     Result := False;
 end;
 function TextCrypt(const s: string; Key: TWordTriple; Encrypt: Boolean): string;
 var
   bOK: Boolean;
 begin
   SetLength(Result, Length(s));
   if Encrypt then
     bOK := MemoryEncrypt(PChar(s), Length(s), PChar(Result), Length(Result), Key)
   else
     bOK := MemoryDecrypt(PChar(s), Length(s), PChar(Result), Length(Result), Key);
   if not bOK then Result := '';
 end;
 function FileCrypt(InFile, OutFile: String; Key: TWordTriple; Encrypt: Boolean): boolean;
 var
   MIn, MOut: TMemoryStream;
 begin
   MIn := TMemoryStream.Create;
   MOut := TMemoryStream.Create;
   Try
     MIn.LoadFromFile(InFile);
     MOut.SetSize(MIn.Size);
     if Encrypt then
       Result := MemoryEncrypt(MIn.Memory, MIn.Size, MOut.Memory, MOut.Size, Key)
     else
       Result := MemoryDecrypt(MIn.Memory, MIn.Size, MOut.Memory, MOut.Size, Key);
     MOut.SaveToFile(OutFile);
   finally
     MOut.Free;
     MIn.Free;
   end;
 end;
 function TextEncrypt(const s: string; Key: TWordTriple): string;
 begin
   Result := TextCrypt(s, Key, True);
 end;
 function TextDecrypt(const s: string; Key: TWordTriple): string;
 begin
   Result := TextCrypt(s, Key, False);
 end;
 function FileEncrypt(InFile, OutFile: String; Key: TWordTriple): boolean;
 begin
   Result := FileCrypt(InFile, OutFile, Key, True);
 end;
 function FileDecrypt(InFile, OutFile: String; Key: TWordTriple): boolean;
 begin
   Result := FileCrypt(InFile, OutFile, Key, False);
 end;

Шифрование текста по ключевым числам.

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

{ 
 This two functions are used to encrypt and decrypt text. 
 Here's how to use it: 
 The four entries Key1, Key2, Key3 and Key4 are numbers 
 that can range from 1 to 120. In order to decrypt a text, 
 you must use the same numbers you used to encrypt the text. 
 No one that doesn't know what values were used on Key1, Key2, Key3 and Key4 
 will be able to decrypt your text! 
 Note that Key1*Key4 MUST be different than Key2*Key3. 
 If any Key is zero, or Key1*Key4 is equal to Key2*Key3, 
 the function will return ''. 
 In Brief: 
      Key1, Key2, Key3, Key4 : integer from range[1..120] 
      Key1*Key4  Key2*Key3 
}
 function Encrypt(Text : string; Key1, Key2, Key3, Key4 : Integer) : string;
 var
  BufS, Hexa, Hexa1, Hexa2 : string;
  BufI, BufI2, Sc, Sl, Num1, Num2, Num3, Num4, Res1, Res2, Res3, Res4 : Integer;
 begin
  Sl := Length(Text);
  Sc := 0;
  BufS := '';
  if (Key1 in [1 .. 120]) and (Key2 in [1 .. 120]) and (Key3 in [1 .. 120]) and (Key4 in [1 .. 120]) then
  begin
   BufI := Key1 * Key4;
   BufI2 := Key3 * Key2;
   BufI := BufI - BufI2;
   if BufI = 0 then
   begin
    Result := '';
    Exit;
   end;
  end
  else
  begin
   Result := '';
   Exit;
  end;
  repeat
   Inc(Sc);
   if Sc > Sl then Num1 := 0 else Num1 := Ord(Text[Sc]);
   Inc(Sc);
   if Sc > Sl then Num2 := 0 else Num2 := Ord(Text[Sc]);
   Inc(Sc);
   if Sc > Sl then Num3 := 0 else Num3 := Ord(Text[Sc]);
   Inc(sc);
   if Sc > Sl then Num4 := 0 else Num4 := Ord(Text[Sc]);
   Res1 := Num1 * Key1;
   BufI := Num2 * Key3;
   Res1 := Res1 + BufI;
   Res2 := Num1 * Key2;
   BufI := Num2 * Key4;
   Res2 := Res2 + BufI;
   Res3 := Num3 * Key1;
   BufI := Num4 * Key3;
   Res3 := Res3 + BufI;
   Res4 := Num3 * Key2;
   BufI := Num4 * Key4;
   Res4 := Res4 + BufI;
   for BufI := 1 to 4 do
   begin
    case BufI of
     1 : Hexa := IntToHex(Res1, 4);
     2 : Hexa := IntToHex(Res2, 4);
     3 : Hexa := IntToHex(Res3, 4);
     4 : Hexa := IntToHex(Res4, 4);
    end;
    Hexa1 := '$' + Hexa[1] + Hexa[2];
    Hexa2 := '$' + Hexa[3] + Hexa[4];
    if (Hexa1 = '$00') and (Hexa2 = '$00') then
    begin
     Hexa1 := '$FF';
     Hexa2 := '$FF';
    end;
    if Hexa1 = '$00' then Hexa1 := '$FE';
    if Hexa2 = '$00' then
    begin
     Hexa2 := Hexa1;
     Hexa1 := '$FD';
    end;
    BufS := BufS + Chr(StrToInt(Hexa1)) + Chr(StrToInt(Hexa2));
   end;
   until Sc >= Sl;
  Result := BufS;
 end;
 function Decrypt(Text : string; Key1, Key2, Key3, Key4 : Integer) : string;
 var
  BufS, Hexa1, Hexa2 : string;
  BufI, BufI2, Divzr, Sc, Sl, Num1, Num2, Num3, Num4, Res1, Res2, Res3, Res4 : Integer;
 begin
  Sl := Length(Text);
  Sc := 0;
  BufS := '';
  if (Key1 in [1 .. 120]) and (Key2 in [1 .. 120]) and (Key3 in [1 .. 120]) and (Key4 in [1 .. 120]) then
  begin
   Divzr := Key1 * Key4;
   BufI2 := Key3 * Key2;
   Divzr := Divzr - BufI2;
   if Divzr = 0 then
   begin
    Result := '';
    Exit;
   end;
  end
  else
  begin
   Result := '';
   Exit;
  end;
  repeat
   for BufI := 1 to 4 do
   begin
    Inc(Sc);
    Hexa1 := IntToHex(Ord(Text[Sc]), 2);
    Inc(Sc);
    Hexa2 := IntToHex(Ord(Text[Sc]), 2);
    if Hexa1 = 'FF' then
    begin
     Hexa1 := '00';
     Hexa2 := '00';
    end;
    if Hexa1 = 'FE' then Hexa1 := '00';
    if Hexa1 = 'FD' then
    begin
     Hexa1 := Hexa2;
     Hexa2 := '00';
    end;
    case BufI of
     1 : Res1 := StrToInt('$' + Hexa1 + Hexa2);
     2 : Res2 := StrToInt('$' + Hexa1 + Hexa2);
     3 : Res3 := StrToInt('$' + Hexa1 + Hexa2);
     4 : Res4 := StrToInt('$' + Hexa1 + Hexa2);
    end;
   end;
   BufI := Res1 * Key4;
   BufI2 := Res2 * Key3;
   Num1 := BufI - BufI2;
   Num1 := Num1 div Divzr;
   BufI := Res2 * Key1;
   BufI2 := Res1 * Key2;
   Num2 := BufI - BufI2;
   Num2 := Num2 div Divzr;
   BufI := Res3 * Key4;
   BufI2 := Res4 * Key3;
   Num3 := BufI - BufI2;
   Num3 := Num3 div Divzr;
   BufI := Res4 * Key1;
   BufI2 := Res3 * Key2;
   Num4 := BufI - BufI2;
   Num4 := Num4 div Divzr;
   BufS := BufS + Chr(Num1) + Chr(Num2) + Chr(Num3) + Chr(Num4);
   until Sc >= Sl;
  Result := BufS;
 end;

Способ шифрования и дешифрования пароля.

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

procedure TForm1.Button1Click(Sender: TObject);
 var
   s: String[255];
   c: array[0..255] of Byte absolute s;
   i: Integer;
 begin
   {encode}
   s := 'SwissDelphiCenter.ch';
   for i := 1 to Ord(s[0]) do c[i] := 23 xor c[i];
   Label1.Caption := s;
   {Decode}
   s := Label1.Caption;
   for i := 1 to Length(s) do s[i] := Char(23 xor Ord(c[i]));
   Label2.Caption := s;
 end;

Определяем номер последнего дня в месяце.

Автор: Ревенко Алексей.

// Колическтво дней в любом месяце любого
// года можно получить с помощью EndOfAMonth
var
  YYYY, MM, DD: Word;
  D: TDateTime;
begin
  DecodeDate(Date, YYYY, MM, DD);
  D := EndOfAMonth(YYYY, {Номер месяца});
  DecodeDate(D, YYYY, MM, DD); // DD - номер последнего дня в месяце
end;

Увеличить процессорное время, выделяемое программе.

Статьи по Delphi (006)…
Фирма Intel после выпуска процессоров Pentium 1, Pentium 2, Pentium 3, Pentium 4 выпустила новые процессоры Pentium жив и Pentium возвращается.

Следующий пример изменяет приоритет приложения. Изменение приоритета следует использовать с осторожностью - т.к. присвоение слишком высокого приоритета может привести к медленной работе остальных программ и системы в целом ;-).

procedure TForm1.Button1Click(Sender: TObject);
var
  ProcessID : DWORD;
  ProcessHandle : THandle;
  ThreadHandle : THandle;
begin
  ProcessID := GetCurrentProcessID;
  ProcessHandle := OpenProcess(PROCESS_SET_INFORMATION,
  false, ProcessID);
  SetPriorityClass(ProcessHandle, REALTIME_PRIORITY_CLASS);
  ThreadHandle := GetCurrentThread;
  SetThreadPriority(ThreadHandle, THREAD_PRIORITY_TIME_CRITICAL);
end;

Как осуществить ввод текста в компоненте Label.

Все, конечно, слышали выражение "программа партии". А что было бы, если бы политические партии действительно писали программы? Программа КПРФ Последние версии документированы как совместимые с другими программами, но на самом деле после инсталляции стремятся их уничтожить. Интерфейс текстовый, белые буквы на красном фоне. Ядро написано более ста лет назад, с тех пор не только не исправлены старые глюки, но и добавлены многие новые. Считает, что все файлы должны быть одинакового размера. Периодически объявляет какое-нибудь расширение вредным и удаляет файлы с этим расширением по всему диску. Запросы на подтверждение имеют единственный вариант ответа: "Даешь!" Пытается выделять под свои задачи ресурсы, не заботясь об их физическом наличии, и в случае неудачи блокирует вывод сообщений об ошибках. При запросе диагностики выводит заранее сформированный файл, сообщающий, что все хорошо. Не принимает электронную почту извне и прибивает задачи, пытающиеся ее отправить. Не совместима с современной техникой. Не может быть деинсталлирована легальными средствами. Программа ЛДПР Имеет яркий, аляповатый интерфейс и неотключаемые звуковые эффекты повышенной громкости. Может быть инсталлирована хоть на ХТ, однако заявляет о чрезвычайно высоких требованиях к системным ресурсам и на любой машине стремится захватить их как можно больше. В сообщениях активно использует нецензурную лексику, в особенности если пользователь - женщина. Постоянно грозится взломать сервер Пентагона и устроить mail-bombing президенту США, но физически не содержит модулей, способных на что-то подобное. Выводит множество предупреждений и сообщений об ошибках, в том числе самых невероятных, но никогда не виснет, не падает и не выгружается из памяти, даже если пользователь очень захочет. При выводе диагностики начинает торговаться с пользователем, обещая вывести хорошие результаты, если ей выделят больше ресурсов. Запрашивает высокую цену за инсталляцию и вдвое большую - за деинсталляцию. Программа "Яблока" Имеет красивый, стильный, но не всем понятный интерфейс. Отказывается работать с другими программами. Содержит мощный диагностический модуль, всегда готова дать пользователю подробный совет в любой ситуации, но на предложение выполнить конкретную операцию неизменно выдает сообщение с аргументированным объяснением, почему данная операция выполнена быть не может. Работает только на компьютерах фирмы Apple. Программа НДР Некогда была главным системным модулем и по-прежнему пытается выполнять эту функцию. Имеет непонятный интерфейс и нечитабельную документацию. Сообщения об ошибках приписывает предыдущим и последующим версиям, а также внешним процессам. Пытается решить проблему нехватки ресурсов, постоянно прося пользователя вставить новую дискету и обещая впоследствии отдать две. Когда задача запрашивает память, сообщает, что память успешно выделена, но на самом деле выделяет ее не раньше чем через полгода, отобрав у других задач. В конечном итоге наглухо вешает машину.

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

Первый шаг, это кнопка, которая может отображать вводимый текст:

type
  TInputButton = class(TButton)
  private
    procedure WmChar (var Msg: TWMChar); message wm_Char;
end;
procedure TInputButton.WmChar (var Msg: TWMChar);
var
  Temp: string;
begin
  if Char (Msg.CharCode) = #8 then
  begin
    Temp := Caption;
    Delete (Temp, Length (Temp), 1);
    Caption := Temp;
  end
  else
    Caption := Caption + Char (Msg.CharCode);
end;

С меткой (label) дела обстоят немного сложнее, так как прийдётся создать некоторые ухищрения, чтобы обойти её внутреннюю структуру. Впринципе, проблему можно решить созданием других скрытых компонент (кстати, тот же edit box). Итак, посмотрим на объявление класса:

type
  TInputLabel = class (TLabel)
  private
    MyEdit: TEdit;
    procedure WMLButtonDown (var Msg: TMessage); message wm_LButtonDown;
  protected
    procedure EditChange (Sender: TObject);
    procedure EditExit (Sender: TObject);
  public
    constructor Create (AOwner: TComponent); override;
end;

Когда метка (label) создана, то она в свою очередь создаёт edit box и устанавливает несколько обработчиков событий для него. Фактически, если пользователь кликает по метке, то фокус перемещается на (невидимый) edit box, и мы используем его события для обновления метки. Обратите внимание на ту часть кода, которая подражает фокусу для метки (рисует прямоугольничек), основанная на API функции DrawFocusRect:

constructor TInputLabel.Create (AOwner: TComponent);
begin
  inherited Create (AOwner);
  MyEdit := TEdit.Create (AOwner);
  MyEdit.Parent := AOwner as TForm;
  MyEdit.Width := 0;
  MyEdit.Height := 0;
  MyEdit.TabStop := False;
  MyEdit.OnChange := EditChange;
  MyEdit.OnExit := EditExit;
end;
procedure TInputLabel.WMLButtonDown (var Msg: TMessage);
begin
  MyEdit.SetFocus;
  MyEdit.Text := Caption;
  (Owner as TForm).Canvas.DrawFocusRect (BoundsRect);
end;
procedure TInputLabel.EditChange (Sender: TObject);
begin
  Caption := MyEdit.Text;
  Invalidate;
  Update;
  (Owner as TForm).Canvas.DrawFocusRect (BoundsRect);
end;
procedure TInputLabel.EditExit (Sender: TObject);
begin
  (Owner as TForm).Invalidate;
end;

Использование Enter как Tab в TDBGrid.

Заядлый компьютерщик заходит в банк снять денег. Кассирша ему: - Сколько? - Мне плиз 1024 рубля!
Приведу код, позволяющий использовать нажатие клавиши Enter как клавиши Tab пока управление находится в табличной сетке.

Данный код включает обработку клавиши Enter для всего приложения, включая поля и пр.. Код для работы с компонентом DBGrid заключен в блок ELSE. Приведенный код не имитирует поведение клавиши Tab, связанное с переходом на следующую запись когда курсор достигает последней колонки табличной сетки, в нашем случае он перемещается на первую колонку.

procedure TForm1.FormKeyPress(Sender: TObject; var Key: Char);
{ Это обработчик события OnKeyPress для ФОРМЫ! }
{ Вы должны также установить свойство формы KeyPreview в True }
begin
  if Key = #13 then { если это клавиша Enter }
    if not (ActiveControl is TDBGrid) then
    begin { если не на TDBGrid }
      Key := #0; { гасим клавишу Enter }
      Perform(WM_NEXTDLGCTL, 0, 0);
        { перемещаемя на следующий элемент управления }
    end
    else if (ActiveControl is TDBGrid) then { если это TDBGrid }
      with TDBGrid(ActiveControl) do
        if selectedindex < (fieldcount - 1) then { увеличиваем поле }
          selectedindex := selectedindex + 1
        else
          selectedindex := 0;
end;

Перейти по Enter на следующее поле в TDBGrid.

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

procedure TForm1.DBGrid1KeyPress(Sender: TObject; var Key: Char);
 begin
   if Key = #13 then
   begin
     if DBGrid1.Columns.Grid.SelectedIndex < DBGrid1.Columns.Count - 1 then
       DBGrid1.Columns[DBGrid1.Columns.grid.SelectedIndex + 1].Field.FocusControl
     else
     begin
       Table1.Next;
       DBGrid1.Columns[0].field.FocusControl;
     end;
   end;
 end;

Запрос пароля при инициализации приложения.

Автор: Xavier Pacheco

{
Copyright © 1998 by Delphi 4 Developer's Guide - Xavier Pacheco and Steve Teixeira
}
program Initialize;
uses
  Forms,
  Dialogs,
  Controls,
  MainFrm in 'MainFrm.pas' {MainForm};
{$R *.RES}
var
  Password: string;
begin
  if InputQuery('Password', 'Enter your password', PassWord) then
    if Password = 'D5DG' then
    begin
      // Other initialization routines can go here.
      Application.CreateForm(TMainForm, MainForm);
      Application.Run;
    end
    else
      MessageDlg('Incorrect Password, terminating program', mtError, [mbok], 0);
end.

Как подменить нажатия ENTER на нажатия TAB.

Статьи по Delphi (006)…
Программист в церкви молится: - Во имя отца и сына и святого духа. Enter.

Существует множество методов решения этой проблемы, но самый быстрый способ, это перехват нажатия клавиш, перед тем как их получит форма:

В секции формы PRIVATE добавьте:

procedure CMDialogKey(var Msg: TWMKey); message CM_DIALOGKEY;

В секции IMPLEMENTATION добавьте:

procedure TForm1.CMDialogKey(var Msg: TWMKey);
begin
  if not (ActiveControl is TButton) then
    if Msg.Charcode = 13 then
      Msg.Charcode := 9;
  inherited;
end;

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

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

Как подменить нажатия ENTER на нажатия TAB 2.

Тонет программист. И во все горло орет: - F1, F1, F1, тьфу ты, помогите, помогите, помогите!!!

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

procedure Tform1.FormKeyDown(Sender: TObject;
var Key: Word; Shift: TShiftState);
var
  ACtrl: TWinControl;
begin
  if key = 13 then
  begin
    ACtrl := ActiveControl;
    if ACtrl is TCustomMemo then
      exit;
    repeat
      ACtrl:= FindNextControl(ACtrl,true,true,false);
    until
      (ACtrl is TCustomEdit) or
      (ACtrl is TCustomComboBox) or
      (ACtrl is TCustomListBox) or
      (ACtrl is TCustomCheckBox) or
      (ACtrl is TRadioButton);
    ACtrl.SetFocus ;
  end;
end;

Не забудьте установить свойство формы KeyPreview в true.

Как Вы можете видеть; этот код использует функцию FindNextControl, которая ищет следующий свободный контрол.

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

Как подменить нажатия ENTER на нажатия TAB 3.

procedure TForm1.FormCreate(Sender: TObject);
begin
  Form1.KeyPreview := true;
end;
procedure TForm1.FormKeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
var
  c: TControl;
begin
  if Key <> 13 then
    Exit;
  repeat
    c := Form1.FindNextControl(Form1.ActiveControl, true, true, true);
    (c as TWinControl).SetFocus;
  until
    c is TEdit;
end;

Как подменить нажатия ENTER на нажатия TAB 4.

Компьютерный интим - клавиатуру тискают, мышку лапают, а коврик гладят.
procedure TForm1.Edit1KeyPress(Sender: TObject; var Key: Char);
begin
  if Key = Chr(VK_RETURN) then
  begin
    Perform(WM_NEXTDLGCTL,0,0);
    key:= #0;
  end;
end;

Как подменить нажатия ENTER на нажатия TAB 5.

Разговаривают два программера: - Представляешь, был вчера на презентации, а там... И коньячок, и вискарек, и водочка! Через полчаса - уже никакой, а на столах - не убавляется, обидно просто. Ну, я не растерялся, пошел в сортир, поблевал хорошенько - и продолжил! - Вот и мне CTRL+ALT+DEL всегда помогает.
procedure TForm1.FormKeyPress(Sender: TObject; var Key: Char);
begin
 if Key = #13 then
 begin
   SelectNext(Sender as TWinControl, True, True);
   Key := #0;
 end;
end;

Работа клавиши Enter как Tab.

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

{ 
  This code gives the  key the same habbit as the key to 
  change focus between Controls. 
  Mit diesem Code kann man sich mit der Enter Taste zwischen mehreren 
  Textfeldern bewegen.(anstatt nur mit der Tabulatortaste) 
}
 // Form1.KeyPreview := True ! 
procedure TForm1.FormKeyPress(Sender: TObject; var Key: Char);
 begin
   if Key = #13 then
   begin
     Key := #0;
     { check if SHIFT - Key is pressed }
     if GetKeyState(VK_Shift) and $8000 <> 0 then
       PostMessage(Handle, WM_NEXTDLGCTL, 1, 0)
     else
       PostMessage(Handle, WM_NEXTDLGCTL, 0, 0);
   end;
 end;

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

Следующий пример помещает в компонент TMemo список шрифтов и их размеров для текущего принтера.

uses Printers;
function EnumFontFamilyProc(var lf: TLogFont;
  var tm: TNewTextMetric;
  FontType: integer;
  var Memo: TMemo): integer
{$IFDEF WIN32} stdcall;
{$ELSE}; export;
{$ENDIF}
begin
  Memo.Lines.Add(StrPas(@lf.lfFaceName) +
    #32 + IntToStr(lf.lfHeight));
  result := 1;
end;
function EnumFontFamiliesProc(var lf: TLogFont;
  var tm: TNewTextMetric;
  FontType: integer;
  var Memo: TMemo): integer
{$IFDEF WIN32} stdcall;
{$ELSE}; export;
{$ENDIF}
begin
  if FontType = TRUETYPE_FONTTYPE then
  begin
    Memo.Lines.Add(StrPas(@lf.lfFaceName) + #32 + 'All Sizes');
  end
  else
    EnumFontFamilies(Printer.Handle,
      @lf.lfFaceName,
      @EnumFontFamilyProc,
      LongInt(@Memo));
  result := 1;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
  tm: TTextMetric;
  i: integer;
begin
  if PrintDialog1.Execute then
  begin
    EnumFontFamilies(Printer.Handle,
      nil,
      @EnumFontFamiliesProc,
      LongInt(@Memo1));
  end;
end;

Пример EnumWindows.

Автор: Paul Powers (Borland).

Создайте форму и разместите на ней два компонента ListBox. Скопируйте код, показанный ниже. Запустите SysEdit. Запустите форму Delphi. Первый ListBox должен содержать список всех запущенных приложений. Дважды щелкните на SysEdit и нижний ListBox покажет дочернее MDI-окно программы SysEdit.
unit Wintask1;
interface
uses
  SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
  Forms, Dialogs, StdCtrls;
type
  TForm1 = class(TForm)
    ListBox1: TListBox;
    ListBox2: TListBox;
    procedure FormCreate(Sender: TObject);
    procedure ListBox1DblClick(Sender: TObject);
  private
    function enumListOfTasks(hWindow: hWnd): Bool; export;
    function enumListOfChildTasks(hWindow: hWnd): Bool; export;
  end;
  THoldhWnd = class(TObject)
  private
  public
    hWindow: hWnd;
  end;
var
  Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.FormCreate(Sender: TObject);
begin
  enumWindows(@TForm1.EnumListOfTasks, Longint(Self));
  if (ListBox1.Items.Count > 0) then
    ListBox1.ItemIndex := 0;
end;
function TForm1.enumListOfTasks(hWindow: hWnd): Bool;
var
  HoldString: PChar;
  WindowStyle: Longint;
  IsAChild: Word;
  HoldhWnd: THoldhWnd;
begin
  GetMem(HoldString, 256);
  HoldhWnd := THoldhWnd.Create;
  HoldhWnd.hWindow := hWindow;
  WindowStyle := GetWindowLong(hWindow, GWL_STYLE);
  WindowStyle := WindowStyle and Longint(WS_VISIBLE);
  IsAChild := GetWindowWord(hWindow, GWW_HWNDPARENT);
  {Добавляем строку с текстом задачи или именем класса и дескриптор в ListBox1.Items }
  if (GetWindowText(hWindow, HoldString, 255) > 0) and
    (WindowStyle > 0) and (IsAChild = Word(nil)) then
    ListBox1.Items.AddObject(StrPas(HoldString), TObject(HoldhWnd))
  else if (GetClassName(hWindow, HoldString, 255) > 0) and
    (WindowStyle > 0) and (IsAChild = Word(nil)) then
    ListBox1.Items.AddObject(Concat('<', StrPas(HoldString), '>'),
      TObject(HoldhWnd));
  FreeMem(HoldString, 256);
  HoldhWnd := nil;
  Result := TRUE;
end;
function TForm1.enumListOfChildTasks(hWindow: hWnd): Bool;
var
  HoldString: PChar;
  WindowStyle: Longint;
  IsAChild: Word;
  HoldhWnd: THoldhWnd;
begin
  GetMem(HoldString, 256);
  HoldhWnd := THoldhWnd.Create;
  HoldhWnd.hWindow := hWindow;
  WindowStyle := GetWindowLong(hWindow, GWL_STYLE);
  WindowStyle := WindowStyle and Longint(WS_VISIBLE);
  IsAChild := GetWindowWord(hWindow, GWW_HWNDPARENT);
  {Добавляем строку с текстом задачи или именем класса и дескриптор в ListBox1.Items }
  if (GetWindowText(hWindow, HoldString, 255) > 0) and
    (WindowStyle > 0) and (IsAChild <> Word(nil)) then
    ListBox2.Items.AddObject(StrPas(HoldString), TObject(HoldhWnd))
  else if (GetClassName(hWindow, HoldString, 255) > 0) and
    (WindowStyle > 0) and (IsAChild = Word(nil)) then
    ListBox2.Items.AddObject(Concat('<', StrPas(HoldString), '>'),
      TObject(HoldhWnd));
  FreeMem(HoldString, 256);
  HoldhWnd := nil;
  Result := TRUE;
end;
procedure TForm1.ListBox1DblClick(Sender: TObject);
begin
  enumChildWindows(THoldhWnd(ListBox1.Items.Objects[ListBox1.ItemIndex]).hWindow,
    @TForm1.enumListOfChildTasks, Longint(Self));
  ListBox2.RePaint;
end;
end.

Использование перечислимых констант.

Автор: Dan Miser(Med Data Systems, Inc.) В Инспекторе Объектов я пытаюсь получить раскрывающийся список для моего перечислимого типа...

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

type
  TBaudRateProperty = class(TStringProperty)
  public
    function GetAttributes: TPropertyAttributes; override;
    procedure GetValues(Proc: TGetStrProc); override;
    function GetValue: string; override;
    procedure SetValue(const Value: string); override;
  end;
  ...
type
  TBaudRate = (br110, br300, br600, br1200, br2400, br4800, br9600, br14400,
    br19200, br38400, br56000, br128000, br256000);
const
  BaudList: array[TBaudRate] of string[7] =
  ('110', '300', '600', '1200', '2400', '4800', '9600', '14400', '19200',
    '38400', '56000', '128000', '256000');
{TBaudRateProperty}
function TBaudRateProperty.GetAttributes: TPropertyAttributes;
begin
  Result := [paValueList];
end;
procedure TBaudRateProperty.GetValues(Proc: TGetStrProc);
var
  i: TBaudRate;
begin
  for i := Low(TBaudRate) to High(TBaudRate) do
    Proc(BaudList[i]);
end;
function TBaudRateProperty.GetValue: string;
begin
  Result := BaudList[TBaudRate(GetOrdValue)];
end;
procedure TBaudRateProperty.SetValue(const Value: string);
var
  i: TBaudRate;
begin
  for i := Low(TBaudRate) to High(TBaudRate) do
    if BaudList[i] = Value then
    begin
      SetOrdValue(integer(i));
      EXIT;
    end;
  inherited SetValue(Value);
end;

Перечислить диски.

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

{ 
  Adds all fixed drives into Combobox1. 
  To enumerate another type of drive, 
  i.e all CD-ROMs just change the DRIVE_FIXED constant to DRIVE_CDROM. 
  Fugt all fixen Laufwerke in Combobox1 ein. 
  Um z.B alle CD-Rom Laufwerke zu ermitteln, 
  einfach DRIVE_CDROM anstatt die Konstante DRIVE_FIXED nehmen. 
}
 procedure List_Drives;
 const
   DRIVE_UNKNOWN = 0;
   DRIVE_NO_ROOT_DIR = 1;
   DRIVE_REMOVABLE = 2;
   DRIVE_FIXED = 3;
   DRIVE_REMOTE = 4;
   DRIVE_CDROM = 5;
   DRIVE_RAMDISK = 6;
 var
   r: LongWord;
   Drives: array[0..128] of char;
   pDrive: PChar;
 begin
   r := GetLogicalDriveStrings(SizeOf(Drives), Drives);
   if r = 0 then Exit;
   if r > SizeOf(Drives) then
     raise Exception.Create(SysErrorMessage(ERROR_OUTOFMEMORY));
   pDrive := Drives;
   while pDrive^ <> #0 do
   begin
     if GetDriveType(pDrive) = DRIVE_FIXED then
       Form1.ComboBox1.Items.Add(pDrive);
     Inc(pDrive, 4);
   end;
 end;

Использовать EnumFontFamilies.

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

{ 
  Example for the use of EnumFontFamilies. This example requires two 
  listboxes on a form. 
  Beispiel fur die Verwendung vo EnumFontFamilies. Das Beispiel benotigt 
  2 Listboxen auf der Form. 
}
 procedure TForm1.FormCreate(Sender: TObject);
 begin
   Listbox1.Items.Assign(Screen.Fonts);
 end;
 function EnumProc(var elf: TEnumLogFont;
   var ntm: TNewTextmetric;
   FontType: Integer;
   listbox: TListbox): Integer; stdcall;
 var
   S: string;
 begin
   if fonttype = TRUETYPE_FONTTYPE then
   begin
     listbox.Items.Add(Format('Name: %s', [elf.elfFullName]));
     listbox.Items.Add(Format('Style: %s', [elf.elfStyle]));
   end
   else
     listbox.Items.Add(Format('Name: %s', [elf.elfLogfont.lfFacename]));
   listbox.Items.Add(Format('Size: %d', [elf.elfLogFont.lfHeight]));
   listbox.Items.Add(Format('Weight: %d', [elf.elfLogFont.lfWeight]));
   if elf.elfLogFont.lfItalic <> 0 then
     listbox.Items.Add('This font is italic');
   case FontType of
     DEVICE_FONTTYPE: S   := 'device font';
     RASTER_FONTTYPE: S   := 'raster font';
     TRUETYPE_FONTTYPE: S := 'truetype font'
       else
         S := 'unknown font type';
   end;
   listbox.Items.Add(Format('This is a %s', [S]));
   Result := 1;
 end;
 procedure TForm1.ListBox1Click(Sender: TObject);
 begin
   listbox2.Clear;
   with listbox1 do
     if ItemIndex >= 0 then
       EnumFontFamilies(Self.Canvas.Handle, PChar(Items[ItemIndex]),
         @EnumProc, Longint(Listbox2));
 end;

Перечислить формы и дочерние формы.

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

type
   PWindows = ^TWindows;
   TWindows = record
     WindowHandle: HWND;
     WindowText: string;
   end;
 type
   TForm1 = class(TForm)
     Button1: TButton;
     TreeView1: TTreeView;
     procedure Button1Click(Sender: TObject);
     procedure FormDestroy(Sender: TObject);
   private
     { Private declarations }
   public
     { Public declarations }
   end;
 var
   Form1: TForm1;
   PNode, CNode: TTreeNode;
   AWindows: PWindows;
 implementation
 {$R *.DFM}
 function EnumChildWindowsProc(Wnd: HWnd; Form: TForm1): Bool; export;
   {$ifdef Win32} stdcall; {$endif}
 var
   Buffer: array[0..99] of Char;
 begin
   GetWindowText(Wnd, Buffer, 100);
   //if StrLen(Buffer)  0 then 
  if StrPas(Buffer) = '' then Buffer := 'Empty';
   new(AWindows);
   with AWindows^ do
   begin
     WindowHandle := Wnd;
     WindowText   := StrPas(Buffer);
   end;
   CNode := Form1.TreeView1.Items.AddChildObject(PNode,
                  AWindows^.WindowText + ':' +
                  IntToHex(AWindows^.WindowHandle, 8), AWindows);
   if GetWindow(Wnd, GW_CHILD)  0 then
   begin
     PNode := CNode;
     Enumchildwindows(Wnd, @EnumChildWindowsProc, 0);
   end;
   Result := True;
 end;
 function EnumWindowsProc(Wnd: HWnd; Form: TForm1): Bool;
   export; {$ifdef Win32} stdcall; {$endif}
 var
   Buffer: array[0..99] of Char;
 begin
   GetWindowText(Wnd, Buffer, 100);
   //if StrLen(Buffer)  0 then 
  if StrPas(Buffer) = '' then Buffer := 'Empty';
   new(AWindows);
   with AWindows^ do
   begin
     WindowHandle := Wnd;
     WindowText   := StrPas(Buffer);
   end;
   PNode := Form1.TreeView1.Items.AddObject(nil, AWindows^.WindowText + ':' +
     IntToHex(AWindows^.WindowHandle, 8), AWindows);
   EnumChildWindows(Wnd, @EnumChildWindowsProc, 0);
   Result := True;
 end;
 procedure TForm1.Button1Click(Sender: TObject);
 begin
   EnumWindows(@EnumWindowsProc, Longint(Self));
 end;
 procedure TForm1.FormDestroy(Sender: TObject);
 begin
   Dispose(AWindows);
 end;
 end.
 {**********************************************}
 {  Other Code by NicoDE 
{**********************************************}
 type
   PMyEnumParam = ^TMyEnumParam;
   TMyEnumParam = record
     Nodes: TTreeNodes;
     Current: TTreeNode;
   end;
 function EnumWindowsProc(Wnd: HWND; Param: PMyEnumParam): BOOL; stdcall;
 const
   MyMaxName = 64;
   MyMaxText = 64;
 var
   ParamChild: TMyEnumParam;
   ClassName: string;
   WindowText: string;
 begin
   Result := True;
   SetLength(ClassName, MyMaxName);
   SetLength(ClassName, GetClassName(Wnd, PChar(ClassName), MyMaxName));
   SetLength(WindowText, MyMaxText);
   SetLength(WindowText, SendMessage(Wnd, WM_GETTEXT, MyMaxText, lParam(PChar(WindowText))));
   ParamChild.Nodes   := Param.Nodes;
   ParamChild.Current := Param.Nodes.AddChildObject(Param.Current,
     '[' + ClassName + '] "' + WindowText + '"' + ' Handle: ' + IntToStr(Wnd), Pointer(Wnd));
   EnumChildWindows(Wnd, @EnumWindowsProc, lParam(@ParamChild));
 end;
 procedure TForm1.Button1Click(Sender: TObject);
 var
   Param: TMyEnumParam;
 begin
   Param.Nodes := TreeView1.Items;
   Param.Current := TreeView1.TopItem;
   TreeView1.Items.BeginUpdate;
   EnumWindows(@EnumWindowsProc, lParam(@Param));
   TreeView1.Items.EndUpdate;
 end;

Получить текст элемента перечисляемого типа.

// For example, if you have some enum type
// Als Beispiel, wenn dieser Aufzahlungstyp vorhanden ist
{....} 
type 
  TYourEnumType = (One, Two, Three, Four, Five, Six, Seven, Eight, Nine, Ten); 
{....} 
{ 
 And you want in run-time to get a string with same value for each of 
 them (for example, fill the Listbox items with enum values), then you 
 can use the next procedure: 
} 
uses TypInfo; 
procedure TForm1.Button1Click(Sender: TObject); 
var 
  i: Integer; 
begin 
  for i := Ord(Low(TYourEnumType)) to Ord(High(TYourEnumType)) do 
    ListBox1.Items.Add(GetEnumName(TypeInfo(TYourEnumType), i)); 
end;

Перечислить сетевые соединения.

Речь сисадмина к девушке, пришедшей регистрироваться на сервере: - С точки зрения сетевой безопасности вы представляете собой потенциальную дырку.
{ 
  From the MS-DOS prompt, you can enumerate the network 
  connections (drives) by using the following command: 
   net use 
  Programmatically, you would call WNetOpenEnum() to start 
  the enumeration of connected resources and 
  WNetEnumResources() to continue the enumeration. 
  The following sample code enumerates the network connections: 
} 
procedure TForm1.Button1Click(Sender: TObject); 
var 
  i, dwResult: DWORD; 
  hEnum: THandle; 
  lpnrDrv: PNETRESOURCE; 
  s: string; 
const 
  cbBuffer: DWORD = 16384; 
  cEntries: DWORD = $FFFFFFFF; 
begin 
  dwResult := WNetOpenEnum(RESOURCE_CONNECTED, 
    RESOURCETYPE_ANY, 
    0, 
    nil, 
    hEnum); 
  if (dwResult <> NO_ERROR) then  
  begin 
    ShowMessage('Cannot enumerate network drives.'); 
    Exit; 
  end; 
  s := ''; 
  repeat 
    lpnrDrv  := PNETRESOURCE(GlobalAlloc(GPTR, cbBuffer)); 
    dwResult := WNetEnumResource(hEnum, cEntries, lpnrDrv, cbBuffer); 
    if (dwResult = NO_ERROR) then  
    begin 
      s := 'Network drives:'#13#10; 
      for i := 0 to cEntries - 1 do  
      begin 
        if lpnrDrv^.lpLocalName <> nil then 
          s := s + lpnrDrv^.lpLocalName + #9 + lpnrDrv^.lpRemoteName; 
        Inc(lpnrDrv); 
      end; 
    end 
    else if dwResult <> ERROR_NO_MORE_ITEMS then  
    begin 
      s := s + 'Cannot complete network drive enumeration'; 
      GlobalFree(HGLOBAL(lpnrDrv)); 
      break; 
    end; 
    GlobalFree(HGLOBAL(lpnrDrv)); 
  until (dwResult = ERROR_NO_MORE_ITEMS); 
  WNetCloseEnum(hEnum); 
  if s = '' then s := 'No network connections.'; 
  ShowMessage(s); 
end; 
{*********************************************************************** 
 FindComp Unit from 
 Fatih Olcer 
 fatiholcer@altavista.com 
***********************************************************************} 
unit FindComp; 
interface 
uses 
  Windows, Classes; 
function FindComputers: DWORD; 
var 
  Computers: TStringList; 
implementation 
uses 
  SysUtils; 
const 
  MaxEntries = 250; 
function FindComputers: DWORD; 
var 
  EnumWorkGroupHandle, EnumComputerHandle: THandle; 
  EnumError: DWORD; 
  Network: TNetResource; 
  WorkGroupEntries, ComputerEntries: DWORD; 
  EnumWorkGroupBuffer, EnumComputerBuffer: array[1..MaxEntries] of TNetResource; 
  EnumBufferLength: DWORD; 
  I, J: DWORD; 
begin 
  Computers.Clear; 
  FillChar(Network, SizeOf(Network), 0); 
  with Network do 
  begin 
    dwScope := RESOURCE_GLOBALNET; 
    dwType  := RESOURCETYPE_ANY; 
    dwUsage := RESOURCEUSAGE_CONTAINER; 
  end; 
  EnumError := WNetOpenEnum(RESOURCE_GLOBALNET, RESOURCETYPE_ANY, 0, 
    @Network, EnumWorkGroupHandle); 
  if EnumError = NO_ERROR then 
  begin 
    WorkGroupEntries := MaxEntries; 
    EnumBufferLength := SizeOf(EnumWorkGroupBuffer); 
    EnumError        := WNetEnumResource(EnumWorkGroupHandle, WorkGroupEntries, 
      @EnumWorkGroupBuffer, EnumBufferLength); 
    if EnumError = NO_ERROR then 
    begin 
      for I := 1 to WorkGroupEntries do 
      begin 
        EnumError := WNetOpenEnum(RESOURCE_GLOBALNET, RESOURCETYPE_ANY, 0, 
          @EnumWorkGroupBuffer[I], EnumComputerHandle); 
        if EnumError = NO_ERROR then 
        begin 
          ComputerEntries  := MaxEntries; 
          EnumBufferLength := SizeOf(EnumComputerBuffer); 
          EnumError        := WNetEnumResource(EnumComputerHandle, ComputerEntries, 
            @EnumComputerBuffer, EnumBufferLength); 
          if EnumError = NO_ERROR then 
            for J := 1 to ComputerEntries do 
              Computers.Add(Copy(EnumComputerBuffer[J].lpRemoteName, 
                3, Length(EnumComputerBuffer[J].lpRemoteName) - 2)); 
          WNetCloseEnum(EnumComputerHandle); 
        end; 
      end; 
    end; 
    WNetCloseEnum(EnumWorkGroupHandle); 
  end; 
  if EnumError = ERROR_NO_MORE_ITEMS then 
    EnumError := NO_ERROR; 
  Result := EnumError; 
end; 
initialization 
  Computers := TStringList.Create; 
finalization 
  Computers.Free; 
end.

Перечислить процессы и завершить их.

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

{ 
  With the following routines it ist simply easy to kill a running process. 
  First build a form with a TListview with 3 columns and a TButton 
  to refresh the running processes. 
  Attach the Refreshclick-procedure to the TButton and the 
  ListViewDblClick-procedure with the TListview 
  The TListview shows the processes. 
  With a Doubleclick on one of the processnames you can kill this running process. 
  Don't forget to include TLHelp32 into your uses-clause! 
  Mit der nachfolgend aufgefuhrten Routinen konnen Sie die in einer 
  Windowssitzung laufenden Prozesse aufzeigen und bei Bedarf auch 
  entfernen. Hierfur benotigen Sie ein Formobject, ein ListViewobject und zu- 
  mindest ein ButtonObject. Verknupfen Sie das Buttonobject mit dem BtnRefreshClick 
  damit gleich beim Start des Programms alle Prozesse angezeigt werden. 
  Zum loschen eines Prozesses mussen Sie eine Verknupfung zwischen DblClick 
  des Listviewobject mit der Procedure ListviewDblClick. 
  Wie aus den beigefugten Routinen ersichtlich, kann auch ein einzelner Prozess 
  gesucht und terminiert werden. Die hierzu erforderlichen Schritte konnen aus 
  der Refreshroutine entnommen werden. 
  Wichtig ist die Einbindung der Unit TlHelp32 ! 
}
 interface
 uses
  {...,}TLHelp32 {important !}
 // Global Variables, Globale Variablen 
var
   aSnapshotHandle: THandle;
   aProcessEntry32: TProcessEntry32;
    implementation
 procedure TForm1.BtnRefreshClick(Sender: TObject);
 var
   i: Integer;
   bContinue: BOOL;
   NewItem: TListItem;
 begin
   ListView1.Items.Clear;
   aSnapshotHandle := CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
   aProcessEntry32.dwSize := SizeOf(aProcessEntry32);
   bContinue := Process32First(aSnapshotHandle, aProcessEntry32);
   while Integer(bContinue) <> 0 do
   begin
     NewItem := ListView1.Items.Add;
     NewItem.Caption := ExtractFileName(aProcessEntry32.szExeFile);
     NewItem.subItems.Add(IntToHex(aProcessEntry32.th32ProcessID, 4));
     NewItem.subItems.Add(aProcessEntry32.szExeFile);
     bContinue := Process32Next(aSnapshotHandle, aProcessEntry32);
   end;
   CloseHandle(aSnapshotHandle);
 end;
 procedure TForm1.ListView1DblClick(Sender: TObject);
 var
   Ret: BOOL;
   PrID: Integer; // processidentifier 
  Ph: THandle;   // processhandle 
begin
   with ListView1 do
   begin
     if MessageDlg('Do you want to Terminate "' + ItemFocused.Caption + '"?' + ^J +
                   'It''s possible the system becames instable or out of' + ^J +
                   'control......',
         mtConfirmation, [mbYes, mbNo], 0) = mrYes then
      begin
        PrID := StrToInt('$' + ItemFocused.SubItems[0]);
        Ph := OpenProcess(1, BOOL(0), PrID);
        Ret := TerminateProcess(Ph, 0);
        if Integer(Ret) = 0 then
          MessageDlg('Cannot terminate "' + ItemFocused.Caption + '"',
                      mtInformation, [mbOK], 0)
        else
          ItemFocused.Delete;
      end;
    end;
 end;
 procedure TForm1.FormCreate(Sender: TObject);
 begin
   { 
    ListView1.Columns.Add; 
    ListView1.Columns.Add; 
    ListView1.Columns.Add; 
    ListView1.ViewStyle := vsReport; 
  }
   BtnRefresh.Click;
 end;
 {Only Win95/Win98/ME}

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

Автор: Sen WEB-сайт: http://www.lmc-mediaagentur.de I need to get a list of strings (like a StringList) with the possible values for a TBrushStyle property (bsSolid, bsClear, bsHorizontal, for example). I want to build a ComboBox with this options. How can I set the property Items of my ComboBox directly with all the values from the enumerated type TBrushStyle? My ComboBox will be alike the Property Editor for this type.

You can use runtime type information (RTTI) to do that. Below is an example:

uses
  {...}, TypInfo
procedure BrushStylesAsStrings(AList: TStrings);
var
  a: integer;
  pInfo: PTypeInfo;
  pEnum: PTypeData;
begin
  AList.Clear;
  pInfo := PTypeInfo(TypeInfo(TBrushStyle));
  pEnum := GetTypeData(pInfo);
  with pEnum^ do
  begin
    for a := MinValue to MaxValue do
      AList.Add(GetEnumName(pInfo, a));
  end;
end;

Перечислить ключи реестра.

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

{ 
  This example demonstrates how to enumerate all registry keys from 
  a certain key (here: HKEY_CURRENT_USER) 
}
 { 
  Dieses Beispiel listet alle Eintrage eines bestimmten Schlussels 
  in der Registrierung auf. (hier: HKEY_CURRENT_USER) 
}
 uses
   Registry;
 procedure TForm1.Button1Click(Sender: TObject);
 var
   indent: Integer;
      procedure EnumAllKeys(hkey: THandle);
   var
     l: TStringList;
     n: Integer;
   begin
     Inc(indent, 2);
     with TRegistry.Create do
       try
         RootKey := hkey;
         OpenKey(EmptyStr, False);
         l := TStringList.Create;
         try
           GetKeynames(l);
           CloseKey;
           for n := 0 to l.Count - 1 do
           begin
             memo1.Lines.Add(StringOfChar(' ', indent) + l[n]);
             if OpenKey(l[n], False) then
             begin
               EnumAllKeys(CurrentKey);
               CloseKey;
             end;
           end;
         finally
           l.Free
         end;
       finally
         Free;
       end;
     Dec(indent, 2);
   end;
 begin
   Memo1.Clear;
   Memo1.Lines.Add('Keys under HKEY_CURRENT_USER');
   indent := 0;
   Memo1.Lines.BeginUpdate;
   try
     EnumAllKEys(HKEY_CURRENT_USER);
   finally
     Memo1.Lines.EndUpdate;
   end;
 end;

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

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

{ 
  You obtain a string equivalent of an enumerated type value using the 
  GetEnumName function: 
}
  function GetEnumName(TypeInfo: PTypeInfo; Value: Integer): string;
 { 
  The first argument is a pointer to the enumerated type’s RTTI information, 
  and the second argument is the ordinal position of the value within the enumerated type. 
  GetEnumName returns a string representing the corresponding enumerated type value. 
  You get the ordinal position of an enumerated type value based on a string using the 
  GetEnumValue function: 
}
  function GetEnumValue(TypeInfo: PTypeInfo; const Name: string): Integer;
 { 
  Like GetEnumName, the first argument is a pointer to the RTTI information. 
  The second argument is a string that represents the enumerated type value. 
  This function returns the ordinal position of the corresponding value. 
}
 // Example: 
// Declare a custom type: 
type
   TOurType = (otFirst, otSecond, otThird, otForth, otFifth, otLast);
 var
   Form1: TForm1;
 implementation
 {$R *.DFM}
 uses
   TypInfo;
 procedure TForm1.Button1Click(Sender: TObject);
 var
   OT: TOurType;
   FT: TFontStyle;
 begin
   // Enumerate TFontStyle values 
  // Zeigt alle TFontStyle Werte an 
  Memo1.Lines.Add('The TFontStyle values:');
   for FT := Low(TFontStyle) to High(TFontStyle) do
     Memo1.Lines.Add(GetEnumName(TypeInfo(TFontStyle), Ord(FT)));
   // Enumerate our custom TOurType values 
  // Werte vom Benutzerdefiniertern Typ TOurType auflisten. 
  Memo1.Lines.Add('The TOurType values:');
   for OT := Low(TOurType) to High(TOurType) do
     Memo1.Lines.Add(GetEnumName(TypeInfo(TOurType), Ord(OT)));
 end;

Вывести все зарегистрированные библиотеки типов.

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

 uses
   Registry;
 procedure EnumTypeLibs(TypeLibNames: TStrings);
 var
   f: TRegistry;
   keyNames, keyVersions, keyInfos: TStringList;
   keyName, keyVersion, keyInfo, tlName: string;
   i, j, k: Integer;
 begin
   TypeLibNames.Clear;
   keyNames := nil;
   keyVersions := nil;
   keyInfos := nil;
   f := TRegistry.Create;
   try
     keyNames := TStringList.Create;
     keyVersions := TStringList.Create;
     keyInfos := TStringList.Create;
     f.RootKey := HKEY_CLASSES_ROOT;
     if not f.OpenKey('TypeLib', False) then raise
       Exception.Create('TRegistry.Open');
     f.GetKeyNames(keyNames);
     f.CloseKey;
     for i := 0 to keyNames.Count - 1 do
     begin
       keyName := keyNames.Strings[i];
       if not f.OpenKey(Format('TypeLib\%s', [keyName]), False) then Continue;
       f.GetKeyNames(keyVersions);
       f.CloseKey;
       for j := 0 to keyVersions.Count - 1 do
       begin
         keyVersion := keyVersions.Strings[j];
         if not f.OpenKey(Format('TypeLib\%s\%s', [keyName, keyVersion]), False) then
           Continue;
         tlName := f.ReadString('');
         f.GetKeyNames(keyInfos);
         f.CloseKey;
         {$B-}
         for k := 0 to keyInfos.Count - 1 do
         begin
           keyInfo := keyInfos.Strings[k];
           if (keyInfo = '') or (keyInfo[1] < '0') or (keyInfo[1] > '9') then Continue;
           if not f.OpenKey(Format('TypeLib\%s\%s\%s\win32', [keyName, keyVersion,
             keyInfo]), False) then Continue;
           f.CloseKey;
           TypeLibNames.Add(Format('%s ver.%s', [tlName, keyVersion]));
         end;
        {$B+}
       end;
     end;
   finally
     f.Free;
     keyNames.Free;
     keyVersions.Free;
     keyInfos.Free;
   end;
 end;
 // Example Call: 
procedure TForm1.Button1Click(Sender: TObject);
 begin
   EnumTypeLibs(ListBox1.Items);
 end;

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

Автор: Xavier Pacheco

{
Copyright © 1999 by Delphi 5 Developer's Guide - Xavier Pacheco and Steve Teixeira
}
unit MainFrm;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls,
  Forms, Dialogs, StdCtrls, ComCtrls;
type
  { Define a record/class to hold the window name and class name for
    each window. Instances of this class will get added to ListBox1 }
  TWindowInfo = class
    WindowName, // The window name
    WindowClass: string; // The window's class name
  end;
  TMainForm = class(TForm)
    lbWinInfo: TListBox;
    btnGetWinInfo: TButton;
    hdWinInfo: THeaderControl;
    procedure btnGetWinInfoClick(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure lbWinInfoDrawItem(Control: TWinControl; Index: Integer;
      Rect: TRect; State: TOwnerDrawState);
    procedure hdWinInfoSectionResize(HeaderControl: THeaderControl;
      Section: THeaderSection);
  end;
var
  MainForm: TMainForm;
implementation
{$R *.DFM}
function EnumWindowsProc(Hw: HWnd; AMainForm: TMainForm): Boolean; stdcall;
{ This procedure is called by the User32.DLL library as it enumerates
  through windows active in the system. }
var
  WinName, CName: array[0..144] of char;
  WindowInfo: TWindowInfo;
begin
  { Return true by default which indicates not to stop enumerating
    through the windows }
  Result := True;
  GetWindowText(Hw, WinName, 144); // Obtain the current window text
  GetClassName(Hw, CName, 144); // Obtain the class name of the window
  { Create a TWindowInfo instance and set its fields with the values of
    the window name and window class name. Then add this object to
    ListBox1's Objects array. These values will be displayed later by
    the listbox }
  WindowInfo := TWindowInfo.Create;
  with WindowInfo do
  begin
    SetLength(WindowName, strlen(WinName));
    SetLength(WindowClass, StrLen(CName));
    WindowName := StrPas(WinName);
    WindowClass := StrPas(CName);
  end;
  MainForm.lbWinInfo.Items.AddObject('', WindowInfo); // Add to Objects array
end;
procedure TMainForm.btnGetWinInfoClick(Sender: TObject);
begin
  { Enumerate through all top-level windows being displayed. Pass in the
    call back function EnumWindowsProc which will be called for each
    window }
  EnumWindows(@EnumWindowsProc, 0);
end;
procedure TMainForm.FormDestroy(Sender: TObject);
var
  i: integer;
begin
  { Free all instances of TWindowInfo }
  for i := 0 to lbWinInfo.Items.Count - 1 do
    TWindowInfo(lbWinInfo.Items.Objects[i]).Free
end;
procedure TMainForm.lbWinInfoDrawItem(Control: TWinControl; Index: Integer;
  Rect: TRect; State: TOwnerDrawState);
begin
  { First, clear the rectangle to which drawing will be performed }
  lbWinInfo.Canvas.FillRect(Rect);
  { Now draw the strings of the TWindowInfo record stored at the
    Index'th position of the listbox. The sections of HeaderControl1
    will give positions to which to draw each string }
  with TWindowInfo(lbWinInfo.Items.Objects[Index]) do
  begin
    DrawText(lbWinInfo.Canvas.Handle, PChar(WindowName),
      Length(WindowName), Rect, dt_Left or dt_VCenter);
    { Shift the drawing rectangle over by using the size
      HeaderControl1's sections to determine where to draw the next
      string }
    Rect.Left := Rect.Left + hdWinInfo.Sections[0].Width;
    DrawText(lbWinInfo.Canvas.Handle, PChar(WindowClass),
      Length(WindowClass), Rect, dt_Left or dt_VCenter);
  end;
end;
procedure TMainForm.hdWinInfoSectionResize(HeaderControl:
  THeaderControl; Section: THeaderSection);
begin
  lbWinInfo.Invalidate; // Force ListBox1 to redraw itself.
end;
end.

Как узнать переменные окружения.

- Говорят, что русским было очень сложно опустить "Мир" обратно на землю... - Да чего уж там. Самой сложной частью всей операции была инсталляция Windows 95 на бортовые компьютеры "Мира". А упал он сам...
procedure TForm1.Button5Click(Sender: TObject);
var
  p: pChar;
begin
  Memo1.Lines.Clear;
  Memo1.WordWrap := false;
  {$IFDEF WIN32}
  p := GetEnvironmentStrings;
  {$ELSE}
  p := GetDOSEnvironment;
  {$ENDIF}
  while p^ <> #0 do begin
    Memo1.Lines.Add(StrPas(p));
    inc(p, lStrLen(p) + 1);
  end;
  {$IFDEF WIN32}
  FreeEnvironmentStrings(p);
  {$ENDIF}
end;

Получение переменных среды.

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

Интересно, у владельцев компьютера, выражение "мать его..." это ругательное?
procedure GetEnvironmentStrings(ss:TStrings);
{Переменные среды}
var
  ptr: PChar;
  s: string;
  Done: boolean;
begin
  ss.Clear;
  s:='';
  Done:=FALSE;
  ptr:=windows.GetEnvironmentStrings;
  while Done=false do begin
    if ptr^=#0 then begin
      inc(ptr);
      if ptr^=#0 then Done:=TRUE
      else ss.Add(s);
      s:=ptr^;
    end else s:=s+ptr^;
    inc(ptr);
  end;
end;

Активизировать клавишу возврата для TWebBrowser.

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

{ 
  Problem: 
  Web forms that have multi-line text boxes and/or Submit buttons do not 
  respond to the Enter key when displayed on a TWebbrowser. 
  Also when browsing local folders, some keys don't respond. 
  How to solve it: 
}
 unit Unit1;
 interface
 uses
   Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
   Dialogs, OleCtrls, SHDocVw_TLB, ActiveX, StdCtrls;
 type
   TForm1 = class(TForm)
     WebBrowser1: TWebBrowser;
     Button1: TButton;
     Button2: TButton;
     procedure FormDestroy(Sender: TObject);
     procedure FormCreate(Sender: TObject);
     procedure Button1Click(Sender: TObject);
     procedure Button2Click(Sender: TObject);
   private
     { Private declarations }
     FOleInPlaceActiveObject: IOleInPlaceActiveObject;
     procedure MsgHandler(var Msg: TMsg; var Handled: Boolean);
   public
     { Public declarations }
   end;
 var
   Form1: TForm1;
 implementation
 {$R *.dfm}
 procedure TForm1.FormDestroy(Sender: TObject);
 begin
   FOleInPlaceActiveObject := nil;
 end;
 procedure TForm1.FormCreate(Sender: TObject);
 begin
   Application.OnMessage := MsgHandler;
 end;
 procedure TForm1.MsgHandler(var Msg: TMsg; var Handled: Boolean);
 const
   DialogKeys: set of Byte = [VK_LEFT, VK_RIGHT, VK_BACK, VK_UP, VK_DOWN,
     $30..$39, $41..42, $44..$55, $57, $59..$5A];
 var
   iOIPAO: IOleInPlaceActiveObject;
   Dispatch: IDispatch;
 begin
   { exit if we don't get back a webbrowser object }
   if (WebBrowser1 = nil) then
   begin
     Handled := System.False;
     Exit;
   end;
   Handled := (IsDialogMessage(WebBrowser1.Handle, Msg) = System.True);
   if (Handled) and (not WebBrowser1.Busy) then
   begin
     if FOleInPlaceActiveObject = nil then
     begin
       Dispatch := WebBrowser1.Application;
       if Dispatch <> nil then
       begin
         Dispatch.QueryInterface(IOleInPlaceActiveObject, iOIPAO);
         if iOIPAO <> nil then
           FOleInPlaceActiveObject := iOIPAO;
       end;
     end;
     if FOleInPlaceActiveObject <> nil then
       if ((Msg.message = WM_KEYDOWN) or (Msg.message = WM_KEYUP)) and
         (Msg.wParam in DialogKeys) then
         // nothing - do not pass on the DialogKeys 
      else
         FOleInPlaceActiveObject.TranslateAccelerator(Msg);
   end;
 end;
 initialization
   OleInitialize(nil);
 finalization
   OleUninitialize;

Трансляция ошибок.

Автор: Nomadic
- Исправил ли ты ошибку в программе? - В разумных пределах...
Делаем ApplyUpdates. Если при insert(update) произошла ошибка (поле null, сработал check, etc.), то BDE всегда говорит "General SQL Error" вместо нормального сообщения об ошибке :-( Без CU все нормально, разумеется. Как бороть этот баг?

Используй нормальную трансляцию ошибок в Application.OnException. Вроде это.

procedure DBExceptionTranslate(E: EDBEngineError); 
function OriginalMessage: string;
var
  I: Integer;
  DBErr: TDBError;
  S: string;
begin
  Result := '';
  for I := 0 to E.ErrorCount - 1 do
  begin
    DBErr := E.Errors[I];
    case DBErr.NativeError of
      -836: { Intebase exception }
        begin
          S := DBErr.Message;
          Result := #13#10 + Copy(S, Pos(#10, S) + 1, Length(S));
          Exit;
        end;
    end;
    S := Trim(DBErr.Message);
    if S <> '' then
      Result := Result + #13#10 + S;
  end;
end;
begin
  case E.Errors[0].ErrorCode of
    $2204:
      E.Message := LoadStr(SKeyDeleted);
    $271E, $2734:
      E.Message := LoadStr(SInvalidUserName);
    $2815:
      E.Message := LoadStr(SDeadlock);
    $2601:
      E.Message := LoadStr(SKeyViol);
    $2604:
      E.Message := LoadStr(SFKViolation) + OriginalMessage;
  else
    begin
      E.Message := Format(LoadStr(SErrorCodeFmt), [E.Errors[0].ErrorCode]) +
        OriginalMessage;
    end;
  end;
end;

Некорректность реализации свойства BorderWidth у ProgressBar.

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

Автор: VS При использовании в компонентах свойства BorderWidth будте внимательны. В большинстве компонентов (ControlBar, ProgressBar, StatusBar, ToolBar, TrackBar и т.д.), это свойство реализовано некорректно. Можно получить забавные результаты или большие неприятности. На рисунке показаны возможные варианты при использовании различных значений BorderWidth в компоненте ProgressBar. Высота компонента ProgressBar.Height = 16. В последних двух вариантах вместо индикатора – изображение под активным окном. Не надейтесь, что компонент стал "прозрачным". Это "моментальный снимок" при создании окна.

Если есть желание, то некорректность можно исправить в ComCtrls.pas, переопределив BorderWidth.

TProgressBar = class(TWinControl)
private
  FBorderWidth: TBorderWidth;
  procedure SetBorderWidth(Value: TBorderWidth);
published
  property BorderWidth: TBorderWidth read FBorderWidth write SetBorderWidth;
...
constructor TProgressBar.Create(AOwner: TComponent);
begin
    FBorderWidth := inherited BorderWidth;
end;
procedure TProgressBar.SetBorderWidth(Value: TBorderWidth);
begin
  if Value > (Height div 2) - 3 then
    Exit
      if Value <> inherited BorderWidth then
    begin
      inherited BorderWidth := Value;
      FBorderWidth := inherited BorderWidth;
    end;
end;

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

Ошибка создания дескриптора курсора.

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

Вы должны использовать ExecSql вместо Open. К примеру, если имя вашего запроса UpdateStudent, то при необходимости обновления STUDENT.DB вы должны использовать следующий код:

Begin
.....
UpdateStudent.ExecSql;
.....
End;

Ваш запрос является Passtrough-запросом, который не может возвратить установленный результат, так что это не может быть открыто, а должно быть 'ВЫПОЛНЕНО'.

Получение констант с определением ошибки функцией LoadStr.

В семье программиста несчастье. Жена прибегает в слезах: - У меня случился выкидыш! Программист задумчиво чешет репу: - Мда-а... Баг в бета-версии...

Возбуждение исключения и передача строковой константы ошибки из CONSTS.PAS (как это делалось в Delphi 1 и Delphi 2) в Delphi3 невозможна. Например:

raise SomeException.Create(LoadStr(SInsertLineError)); 

В Delphi3 теперь не работает. Я предлагаю использовать выражение.

raise SomeException.Create(
{$IFNDEF VER100}LoadStr{$ENDIF}
(SInsertLineError));
для вызова функции LoadStr "по нужде", устаревшей почему-то для этих целей в Delphi 3. При попытке вызвать данную функцию в Delphi 3 (для получения типа ошибки, декларированной в модуле CONSTS(.PAS/.DCU)), мы получаем следующую ошибку компилятора: "Incompatible types: 'Integer' and 'String'"

Однако в Delphi 4 данная оплошность исправлена и вроде как документирована. Почитайте в хелпе на тему "resourcestrings", посмотрите сырцы и все сразу станет понятно и просто.

Номера ошибок.

Странное какое-то у немцев отношение к компьютерам: на их языке слово "Gluck" означает счастье.
ОшибкаСообщение об ошибке
1 Неверный номер функции (Invalid function number)
2 Файл не найден (File not found)
3 Путь не найден (Path not found)
4 Слишком много открытых файлов (Too many open files)
5 Доступ к файлу закрыт (File access denied)
6 Неверный дескриптор файла (Invalid file handle)
12 Неверный код доступа к файлу (Invalid file access code)
15 Неверный номер устройства (Invalid drive number)
16 Невозможно удалить текущий каталог (Cannot remove current directory)
17 Невозможно переименовать устройство (Cannot rename across drives)
100 Ошибка чтения диска (Disk read error)
101 Ошибка записи на диск (Disk write error)
102 Файл не назначен (File not assigned)
103 Файл не открыт (File not open)
104 Файл не открыт для ввода (File not open for input)
105 Файл не открыт для вывода (File not open for output)
106 Неверный числовой формат (Invalid numeric format)
200 Деление на ноль (Division by zero)
201 Превышение допустимого диапазона (Range check error)
202 Переполнение стека (Stack overflow error)
203 Переполнение кучи (Heap overflow error)
204 Неверная операция с указателем (Invalid pointer operation)
205 Переполнение плавающей точки (Floating point overflow)
206 Потеря значимости плавающей точкой (Floating point underflow)
207 Неверная операция с плавающей точкой (Invalid floating point operation)
210 Объект не инициализирован (Object not initialized)
211 Вызов абстрактного метода (Call to abstract method)
212 Ошибка регистрации потока (Stream registration error)
213 Индекс коллекции вышел за пределы допустимого диапазона (Collection index out of range)
214 Ошибка переполнения коллекции (Collection overflow error)
215 Ошибка арифметического переполнения (Arithmetic overflow error)
216 Общая ошибка защиты (General protection fault)

Error reading symbol file.

Время от времени при открытии проекта возникает сообщение: "Error reading symbol file". ТИПОВЫЕ РЕШЕНИЯ

Удалить файлы *.sym. Они могут остаться от другой версии Delphi, при том что формат файла с символьной информацией изменился.

Искусство управления ошибками.

Оформил: DeeCo

Автор: Даутов Ильдар.

Часть IIПродолжая тему "Управление ошибками в Delphi", поставим следующие задачи : программа-монитор ошибок должна работать как системный сервис Windows NT журнал ошибок должен сохраняться на диске и постоянно пополняться список текущих ошибок и полный журнал ошибок должны быть доступны для просмотра на любом компьютере локальной сети предприятия Реализуем следующую схему взаимодействия программ при возникновении ошибки : ошибка, возникшая в клиентской программе, передается по сети монитору-сервису Windows NT. Для передачи используем механизм каналов Mailslot монитор сохраняет текст ошибки на диске. Для хранения используем текстовый файл монитор пересылает по сети текст ошибки программе просмотра ошибок. Для передачи используем механизм каналов Mailslot программа просмотра принимает текст ошибки и отображает его на экране программа просмотра может запросить полный журнал ошибок. Для получения полного журнала используем механизм разделяемых сетевых файловых ресурсов В статье представлены 2 проекта : монитор ошибок и окно просмотра ошибок. Клиентская программа, имитирующая ошибку, была представлена в предыдущей статье, и здесь не рассматривается. Монитор ошибок Оформить программу как сервис Windows NT (Win32 service) не составляет большого труда : создаем новое приложение File | New... | New | Service Application. Создается приложение с глобальной переменной Application типа TServiceApplication и объектом типа TService, который и реализует всю функциональность сервиса устанавливаем требуемые свойства объекта TService имя сервиса параметры запуска сервиса имя и пароль пользователя, от имени которого стартует сервис переписываем событие OnExecute объекта TService, в котором реализуем требуемую функциональность сервиса компилируем проект регистрируем созданный сервис на сервере Windows NT и запускаем Регистрация сервиса выполняется из командной строки следующим образом : ErrorMonitorService.exe /install Удаление сервиса : ErrorMonitorService.exe /uninstall Запуск сервиса выполняется из командной строки следующим образом : net start ErrorMonitor Останов сервиса : net stop ErrorMonitor Оформив эту последовательность команд как BAT-файл, можно значительно облегчить себе жизнь при отладке сервиса. Достаточно подробную информацию о сервисах Windows NT можно найти в книге : А.В.Фролов, Г.В.Фролов 'Программирование для Windows NT (часть вторая)', Москва, ДИАЛОГ-МИФИ, 1997 Для сохранения протокола (журнала) пользовательских ошибок используем следующую схему : журнал ведется в текстовом файле в определенном каталоге Windows NT журнал имеет имя yyyy-mm-dd.log, соответствующее календарной дате запуска сервера при каждом запуске монитор проверяет наличие файла, имя которого соответствует текущей дате. При отсутствии - файл создается, иначе происходит дозапись в конец файла сохраняются только последние 7 файлов журнала Текст программы монитора ошибок приведен ниже :
unit uErrorMonitorService;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, SvcMgr, ScktComp;
type
  TErrorMonitor = class(TService)
    procedure Service1Execute(Sender: TService);
    procedure ServiceEMCreate(Sender: TObject);
  private
  public
    function GetServiceController: PServiceController; override;
    procedure SendError;
    function InitLog: boolean;
  end;
var
  ErrorMonitor: TErrorMonitor;
implementation
uses Dialogs;
{$R *.DFM}
const
  LogDir = 'C:\Log\'; // каталог, где сохраняются журналы
var
  LogFile: TextFile; // файл текущего журнала
  LogName: string; // имя файла текущего журнала
  h: THandle; // handle канала Mailslot
  str: string[250]; // буфер для передачи информации
  MsgNumber, MsgNext, Read: DWORD;
procedure ServiceController(CtrlCode: DWord); stdcall;
begin
  ErrorMonitor.Controller(CtrlCode);
end;
function TErrorMonitor.GetServiceController: PServiceController;
begin
  Result := @ServiceController;
end;
// Передача текста ошибки от сервиса программе просмотра
procedure TErrorMonitor.SendError;
var
  h: THandle;
  i: integer;
begin
  // открытие MailSlot-канала, по которому будет передаваться протокол
  // используется широковещательная передача в домене
  h := CreateFile(PChar('\\*\mailslot\EMonMess'), GENERIC_WRITE,
    FILE_SHARE_READ, nil,
    OPEN_EXISTING, 0, 0);
  if h <> INVALID_HANDLE_VALUE then
  begin
    // запись в канал и закрытие канала
    WriteFile(h, str, Length(str) + 1, DWORD(i), nil);
    CloseHandle(h);
  end;
end;
// инициализация файла журнала
// журналы ведутся в отдельных файлах по каждой дате
function TErrorMonitor.InitLog: boolean;
var
  sr: TSearchRec;
  i: integer;
begin
  Result := True;
  // удаление старых файлов журнала
  //(сохраняются только последние 7 журналов)
  with TStringList.Create do
  begin
    Sorted := True;
    i := FindFirst(LogDir + '*.log', faAnyFile, sr);
    while i = 0 do
    begin
      Add(sr.Name);
      i := FindNext(sr);
    end;
    FindClose(sr);
    if Count > 7 then
      for i := 0 to Count - 8 do
        DeleteFile(LogDir + Strings[i]);
    Free;
  end;
  // текущий файл журнала
  LogName := LogDir + FormatDateTime('yyyy-mm-dd', Date) + '.log';
  AssignFile(LogFile, LogName);
  try
    if FileExists(LogName) then
      Append(LogFile)
    else
      Rewrite(LogFile);
  except
    str := 'Ошибка создания файла журнала : ' + LogName;
    Status := csStopped;
    LogMessage(str);
    ShowMessage(str);
    Result := False;
  end;
end;
// основная логика сервиса
procedure TErrorMonitor.Service1Execute(Sender: TService);
begin
  // создание MailSlot-канала с именем EMon - по этому имени к нему
  // будут обращаться клиенты, у которых возникли ошибки
  h := CreateMailSlot('\\.\mailslot\EMon', 0, MAILSLOT_WAIT_FOREVER, nil);
  if h = INVALID_HANDLE_VALUE then
  begin
    Status := csStopped;
    // запись в журнал событий NT
    str := 'Ошибка создания канала EMon !';
    LogMessage(str);
    ShowMessage(str);
    Exit;
  end;
  // создание файла журнала
  if not InitLog then
    Exit;
  try
    while not Terminated do
    begin
      // определение наличия сообщения в канале
      if not GetMailSlotInfo(h, nil, DWORD(MsgNext), @MsgNumber, nil) then
      begin
        Status := csStopped;
        str := 'Ошибка сбора информации канала EMon !';
        LogMessage(str);
        ShowMessage(str);
        Break;
      end;
      if MsgNext <> MAILSLOT_NO_MESSAGE then
      begin
        beep;
        // чтение сообщения из канала и добавление в текст протокола
        if ReadFile(h, str, 200, DWORD(Read), nil) then
        begin
          // запись в журнал
          Writeln(LogFile, str);
          // посылка сообщения для показа
          SendError;
        end
        else
        begin
          str := 'Ошибка чтения сообщения !';
          Writeln(LogFile, str);
          SendError;
        end;
        Flush(LogFile);
      end;
      sleep(500);
      ServiceThread.ProcessRequests(False);
    end;
  finally
    CloseHandle(h);
    CloseFile(LogFile);
  end;
end;
procedure TErrorMonitor.ServiceEMCreate(Sender: TObject);
begin
  // под таким именем наш сервис будет виден в Service Control Manager
  DisplayName := 'ErrorMonitor';
  // необходимо при использовании ShowMessage
  InterActive := True;
end;
end.
Окно просмотра ошибок Статьи по Delphi (006)… Текст программы окна просмотра ошибок приведен ниже :
unit fErrorMonitorMessage;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, ExtCtrls, ScktComp;
type
  TfmErrorMonitorMessage = class(TForm)
    // протокол текущих ошибок
    meErrorTextNow: TMemo;
    meJournals: TMemo;
    // таймер для опроса канала
    Timer: TTimer;
    paJournals: TPanel;
    buJournals: TButton;
    lbJournals: TListBox;
    laJournals: TLabel;
    procedure FormCreate(Sender: TObject);
    procedure TimerTimer(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure buJournalsClick(Sender: TObject);
  private
  public
  end;
  // сетевой разделяемый ресурс, где сохраняются журналы
  // (укажите здесь имя своего ресурса и обеспечьте права для доступа)
const
  LogDir = '\\MyServer\C$\Log\';
var
  fmErrorMonitorMessage: TfmErrorMonitorMessage;
  h: THandle; // handle Mailslot-канала
  str: string[250]; // буфер обмена
  MsgNumber, MsgNext, Read: DWORD;
implementation
{$R *.DFM}
procedure TfmErrorMonitorMessage.FormCreate(Sender: TObject);
var
  sr: TSearchRec;
  i: integer;
begin
  // создание Mailslot-канала с именем EMonMess
  // по этому каналу будем получать сообщения об ошибках от сервиса NT
  h := CreateMailSlot('\\.\mailslot\EMonMess', 0, MAILSLOT_WAIT_FOREVER, nil);
  if h = INVALID_HANDLE_VALUE then
  begin
    ShowMessage('Ошибка создания канала !');
    Halt;
  end;
  // интервал опроса канала Mailslot - 3 секунды
  Timer.Interval := 3000;
  // таймер первоначально был выключен
  Timer.Enabled := True;
  // заполнение списка доступных журналов
  i := FindFirst(LogDir + '*.log', faAnyFile, sr);
  while i = 0 do
  begin
    lbJournals.Items.Add(sr.Name);
    i := FindNext(sr);
  end;
  lbJournals.ItemIndex := lbJournals.Items.Count - 1;
  FindClose(sr);
end;
procedure TfmErrorMonitorMessage.TimerTimer(Sender: TObject);
var
  str: string[250];
begin
  Timer.Enabled := False;
  // определение наличия сообщения в канале
  if not GetMailSlotInfo(h, nil, DWORD(MsgNext), @MsgNumber, nil) then
  begin
    ShowMessage('Ошибка сбора информации !');
    Close;
  end;
  if MsgNext <> MAILSLOT_NO_MESSAGE then
  begin
    beep;
    // чтение сообщения из канала и добавление в текст протокола
    if ReadFile(h, str, 200, DWORD(Read), nil) then
      meErrorTextNow.Lines.Add(str)
    else
      ShowMessage('Ошибка чтения сообщения !');
  end;
  Timer.Enabled := True;
end;
procedure TfmErrorMonitorMessage.FormClose(Sender: TObject;
  var Action: TCloseAction);
begin
  CloseHandle(h);
end;
procedure TfmErrorMonitorMessage.buJournalsClick(Sender: TObject);
var
  Journal: TFileStream;
  s: string;
begin
  // получение журнала ошибок за дату
  meJournals.Lines.Clear;
  meJournals.Lines.Add('Файл журнала ' +
    lbJournals.Items[lbJournals.ItemIndex]);
  Journal := TFileStream.Create(LogDir + lbJournals.Items[lbJournals.ItemIndex],
    fmOpenRead or fmShareDenyNone);
  SetLength(s, Journal.Size);
  Journal.Read(PChar(s)^, Journal.Size);
  meJournals.Lines.Add(s);
  Journal.Free;
end;
end.

Искусство управления ошибками. Часть 2.

Оформил: DeeCo

Автор: Даутов Ильдар.

Введение.

Любая программа содержит ошибки и мастерство разработчика не только в минимизации числа этих ошибок, но и в умении управлять ими, если таковые возникли. Посмотрим, что же в плане управления программными ошибками предлагают такие системы как Delphi и Windows 95/Windows NT. Поставим задачу следующим образом : все ошибки в программах собственной разработки, независимо от места их возникновения на клиентских ПК предприятия, должны собираться и регистрироваться в одном единственном месте, а разработчик должен получать сообщение об этом событии, содержащее полный текст ошибки. Предложим следующую схему управления ошибками. На каком-либо ПК запускается программа-монитор и ждет сообщений об ошибках от клиентских программ , при приходе которых, регистрирует их. Клиентские программы при возникновении ошибки перехватывают ее, предварительно обрабатывают и отсылают монитору по локальной вычислительной сети. Решение задачи разобьем на 2 этапа - разработка сервера-монитора слежения за ошибками и подпрограммы-ловушки ошибок в прикладной клиентской программе. При разработке систем подобного класса неизбежно встает вопрос о протоколе межмашинного взаимодействия клиента и сервера. Из множества вариантов, предлагаемых Windows, мы остановим свой выбор на каналах передачи данных Mailslot, как наиболее простом, доступном и универсальном. Немного о Mailslot : каналы Mailslot позволяют выполнять одностороннюю передачу данных от одного или нескольких клиентов к одному или нескольким серверам, причем работают на уровне датаграмм и в широковещательном режиме.

Монитор слежения за ошибками.

Монитор слежения за ошибками - это отдельное приложение, запускаемое либо на вашем ПК, либо на сервере (как Вам удобно). Его предназначение - ловить сообщения об ошибках, приходящие от клиентских программ и отображать текст ошибки в окне программы. Монитор должен запускаться до запуска любой клиентской программы. Статьи по Delphi (006)… Текст монитора приведен ниже :
unit fErrorMonitor;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, ExtCtrls;
type
  TfmErrorMonitor = class(TForm)
    // компонент для протоколирования ошибок
    meErrorText: TMemo;
    // компонент для активизации опроса канала
    // интервал опроса устанавливается свойством Interval
    Timer: TTimer;
    procedure FormCreate(Sender: TObject);
    procedure TimerTimer(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
  private
  public
  end;
var
  fmErrorMonitor: TfmErrorMonitor;
var
  h: THandle;
  str: string[250];
  MsgNumber, MsgNext, Read: DWORD;
implementation
{$R *.DFM}
procedure TfmErrorMonitor.FormCreate(Sender: TObject);
begin
  // создание канала с именем EMon - по этому имени к нему
  // будут обращаться клиенты
  h := CreateMailSlot('\\.\mailslot\EMon', 0, MAILSLOT_WAIT_FOREVER, nil);
  if h = INVALID_HANDLE_VALUE then
  begin
    ShowMessage('Ошибка создания канала !');
    Halt;
  end;
  // таймер первоначально был выключен
  Timer.Enabled := True;
end;
procedure TfmErrorMonitor.TimerTimer(Sender: TObject);
begin
  Timer.Enabled := False;
  // определение наличия сообщения в канале
  if not GetMailSlotInfo(h, nil, DWORD(MsgNext), @MsgNumber, nil) then
  begin
    ShowMessage('Ошибка сбора информации !');
    Close;
  end;
  if MsgNext <> MAILSLOT_NO_MESSAGE then
  begin
    beep;
    // чтение сообщения из канала и добавление в текст протокола
    if ReadFile(h, str, 200, DWORD(Read), nil) then
      meErrorText.Lines.Add(str)
    else
      ShowMessage('Ошибка чтения сообщения !');
  end;
  Timer.Enabled := True;
end;
procedure TfmErrorMonitor.FormClose(Sender: TObject;
  var Action: TCloseAction);
begin
  // закрытие канала
  CloseHandle(h);
end;
end.
Подпрограмма ловушки ошибок Подпрограмма ловушки ошибок встраивается в каждую прикладную программу. Ниже приведен примерный шаблон такой ловушки и пример ее использования. Вы вправе настроить шаблон под особенности своей предметной области.
unit fErrorClient;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, ExtCtrls;
type
  TfmTest = class(TForm)
    Button1: TButton;
    Image1: TImage;
    procedure FormCreate(Sender: TObject);
    procedure Button1Click(Sender: TObject);
  private
  public
  end;
implementation
{$R *.DFM}
type
  Error = class
    class procedure ErrorCatch(Sender: TObject; Exc: Exception);
  end;
  // для простоты ловушка ошибок описана в этом модуле
  // в реальных приложениях ее нужно вынести в отдельный unit
class procedure Error.ErrorCatch(Sender: TObject; Exc: Exception);
var
  strMess: string[250];
  UserName: array[0..99] of char;
  h: THandle;
  i: integer;
begin
  // здесь можно проанализировать Exception, воспользовавшись его свойствами,
  // и предпринять конкретные действия в зависимости  от типа ошибки
  beep;
  // получение имени пользователя
  i := SizeOf(UserName);
  GetUserName(UserName, DWORD(i));
  // формирование текста сообщения об ошибке
  strMess := '/' + UserName + '/' + FormatDateTime('hh:mm', Time) + '/' +
    Exc.Message;
  // открытие канала : MyServer - имя сервера, на котором работает
  // монитор ошибок (\\.\\mailslot\EMon - монитор работает на этом же ПК)
  // EMon - имя канала
  h := CreateFile(PChar('\\MyServer\mailslot\EMon'), GENERIC_WRITE,
    FILE_SHARE_READ, nil, OPEN_EXISTING, 0, 0);
  if h <> INVALID_HANDLE_VALUE then
  begin
    // передача текста ошибки (запись в канал и закрытие канала)
    WriteFile(h, strMess, Length(strMess) + 1, DWORD(i), nil);
    CloseHandle(h);
  end;
  // вывод сообщения об ошибке пользователю
  ShowMessage('У Вас возникла ошибка (не волнуйтесь-все под контролем)' +
    chr(13) + strMess);
end;
procedure TfmTest.FormCreate(Sender: TObject);
begin
  // при создании главной формы приложения устанавливаем
  // глобальный обработчик исключений
  Application.OnException := Error.ErrorCatch;
end;
procedure TfmTest.Button1Click(Sender: TObject);
var
  i: integer;
begin
  // тестирование ловушки ошибок
  i := 1 - 1;
  i := 100 div i;
end;
end.
В следующей статье будет показано как превратить монитор ошибок в сервис Windows NT. Получить исходные тексты (13 Кб)

Escape код PASSTHROUGH.

Мой папа хакер и мама хакер, сестричка тоже хакер хоть куда. А кто не хакер? Ну кто не хакер? А тот кто кода и не видел никогда.
Кто-нибудь знает почему некоторые видеодрайверы не осуществляют транзитную пересылку данных (passthrough) в Escape функции? Как узнать, поддерживает ли драйвер принтера код PASSTROUGH? Dos-приложения и в Windows 95 используют эту функцию с командой "copy file > lpt1" для передачи текста в буфер принтера. Хотя Delphi модуль TPrinter облегчает доступ к принтеру, есть случаи, когда вам необходимо спуститься до транспортного уровня общения системы с принтером и передать специфические для устройства управляющие коды. Под 16-битной операционной системой Windows это было так же легко, как открыть порт принтера, но сейчас, к примеру, под Windows NT, непосредственный доступ к аппаратному обеспечению невозможен. Одно из решений проблемы состоит в использовании Windows кода "PASSTHROUGH" для посылки кода непосредственно в принтер. Тем не менее, для использования кода "PASSTHROUGH" необходимо, чтобы это поддерживалось самим драйвером принтера. К сожалению, далеко не все принтеры поддерживают данную характеристику. Необходимо отметить, что "PASSTHROUGH" для 32-битных приложений считается устаревшим кодом. Но должно пройти немало лет, прежде чем это умрет окончательно, поскольку это до сих пор используется во многих коммерческих приложениях. Приведенный ниже пример не привязан к каким-либо специфическим моделям принтеров. Вам необходимо лишь знать правильную последовательность передачи escape кодов на управляемый вами принтер. Имейте в виду, что вы все еще должны вызывать методы BeginDoc и EndDoc объекта TPrinter. При вызове метода BeginDoc драйвер принтера инициализирует принтер как объект управления, EndDoc - деинициализацирует и извлекает бумагу. При escape вызове принтер может установить текущий режим метрики экрана, если он поддерживает внутреннее масштабирование. Технически вы ничего не должны делать, что могло бы вызвать обнуление памяти принтера или удаление из него бумаги с помощью escape кодов. Другими словами, попытайтесь оставить принтер в том же состоянии, в котором он остался после окончания печати. В основном это касается технически совершенных принтеров, поддерживающих режим Postscript, в стандартных же моделях (TTY) все это не столь существенно, и вы свободны в своих действиях, включая удаление страницы из принтера. Пример кода: Прежде всего вам необходимо описать структуру буфера, который вы собираетесь посылать. Структура буфера определена как слово, содержащее размер буфера и сам буфер, содержащий данные. Прежде всего, с помощью escape вызова "QUERYESCSUPPORT" необходимо убедиться, что "PASSTHROUGH" поддерживается драйвером печати. И, наконец, ваши данные будут переданы в поток данных принтера. Необходимо также помнить, что в некоторых моделях принтеров (Postscript), вам возможно понадобиться добавить пробелы в начале и в конце передаваемых данных, чтобы отделить ваши данные от данных драйвера печати.

(Postscript - зарегистрированная торговая марка Adobe Systems Incorporated).

unit Esc1;
interface
uses
  SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
  Forms, Dialogs, StdCtrls;
type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;
var
  Form1: TForm1;
implementation
{ добавляем модуль printers }
uses
  Printers;
{$R *.DFM}
{ описываем структуру "PASSTHROUGH" }
type
  TPrnBuffRec = record
    BuffLength: word;
    Buffer: array[0..255] of char;
  end;
procedure TForm1.Button1Click(Sender: TObject);
var
  Buff: TPrnBuffRec;
  TestInt: integer;
  s: string;
begin
  { Тестируем на предмет поддержки escape кода "PASSTHROUGH" }
  TestInt := PASSTHROUGH;
  if Escape(Printer.Handle,
    QUERYESCSUPPORT,
    sizeof(TestInt),
    @TestInt,
    nil) > 0 then
  begin
    { Начинаем вывод на печать }
    Printer.BeginDoc;
    { Создаем строку для транзитной пересылки }
    s := ' Текстовая строка ';
    { Копируем строчку в буфер }
    StrPCopy(Buff.Buffer, s);
    { Устанавливаем размер буфера }
    Buff.BuffLength := StrLen(Buff.Buffer);
    { Даем команду на транзитную пересылку буфера }
    Escape(Printer.Canvas.Handle,
      PASSTHROUGH,
      0,
      @Buff,
      nil);
    { Заканчиваем вывод на печать }
    Printer.EndDoc;
  end;
end;
end.

Возможность отмены вставки нового узла в TTreeView по нажатию кнопки Esc.

Статьи по Delphi (006)…
unit BetterTreeView;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  ComCtrls, CommCtrl;
type
  TTVNewEditCancelEvent = procedure( Sender: TObject; Node: TTreeNode; var Delete: Boolean) of object;
  TBetterTreeView = class(TTreeView)
  protected
    FIsEditingNew: Boolean;
    FOnEditCancel: TTVChangedEvent;
    FOnNewEditCancel: TTVNewEditCancelEvent;
    procedure Edit(const Item: TTVItem); override;
  public
    function NewChildAndEdit(Node: TTreeNode; const S: string): TTreeNode;
  published
    property IsEditingNew: Boolean read FIsEditingNew;
    property OnEditCancel: TTVChangedEvent read FOnEditCancel write FOnEditCancel;
    property OnNewEditCancel: TTVNewEditCancelEvent read FOnNewEditCancel write FOnNewEditCancel;
end;
implementation
procedure TBetterTreeView.Edit(const Item: TTVItem);
var
  Node: TTreeNode;
  Action: Boolean;
begin
  with Item do
  begin
    { Get the node }
    if (state and TVIF_PARAM) <> 0 then
      Node := Pointer(lParam)
    else
      Node := Items.GetNode(hItem);
    if pszText = nil then
    begin
      if FIsEditingNew then
      begin
        Action := True;
        if Assigned(FOnNewEditCancel) then
          FOnNewEditCancel(Self, Node, Action);
        if Action then
          Node.Destroy
      end
      else
        if Assigned(FOnEditCancel) then
          FOnEditCancel(Self, Node);
    end
    else
      inherited;
  end;
  FIsEditingNew := False;
end;
function TBetterTreeView.NewChildAndEdit(Node: TTreeNode; const S: string): TTreeNode;
begin
  SetFocus;
  Result := Items.AddChild(Node, S);
  FIsEditingNew := True;
  Node.Expand(False);
  Result.EditText;
  SetFocus;
end;
end.

Делегирование события.

Автор: Steve ...я вижу что событие делегировано, но почему вы считаете, что это отход от ООП? Тем не менее, Delphi это использует при каждом определении обработчика события. Вот как поступить в вашей ситуации: Определите тип процедуры, использующейся в качестве обработчика события. Допустим, ваш обработчик OnCalculate имеет один параметр типа Integer (присутствующий лишь для демонстрации идеи).
type
  TCalculateEvent = procedure(I: Integer) of object;
Теперь объявите ваш класс:
type
TSomeClass = class(TObject)
private
FOnCalculate: TCalculateEvent;
procedure DoCalculate(I: Integer);
public
property OnCalculate: TCalculateEvent read FOnCalculate
write FOnCalculate;
end;
Метод DoCalculate совсем простой:
procedure TSomeClass.DoCalculate(I: Integer);
begin
  if Assigned(FOnCalculate) then
    FOnCalculate(I);
end;

Теперь вы можете присваивать значение объекту TSomeClass, и назначать любую процедуру событию OnCalculate (естественно, при условии, что ее объявление соответствует объявлению TCalculateEvent).

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

Автор: Alexey Mahotkin

У этой процедуры должен быть еще один дополнительный параметр, например:

procedure MyEventHandler(PseudoSelf: TObject; Sender: TObject);
begin
  { ... }
end;

Синтаксис ссылки на событие.

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

По всей видимости вам придется использовать две временные переменные. Попытайтесь сделать следующее:

var
  ClickMethod: TNotifyEvent;
  B1Click: TNotifyEvent;
...
ClickMethod := Button1.OnClick;
B1Click := Button1Click;
if @ClickMethod = @B1Click then 
  messagebeep(0);

Генерация еженедельных списков задач.

Автор: Mike Orriss Мне необходима программа, которая генерировала бы еженедельные списки задач. Программа должна просто показывать количество недель в списке задач и организовывать мероприятия, не совпадающие по времени. В моем текущем планировщике у меня имеется 12 групп и планы на 11 недель. Мне нужен простой алгоритм, чтобы решить эту проблему. Какие идеи?

Вот рабочий код (но вы должны просто понять алгоритм работы):

unit Unit1;
interface
uses
  Windows, Messages, SysUtils, 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;
implementation
{$R *.DFM}
const
  maxTeams = 100;
var
  Teams: array[1..maxTeams] of integer;
  nTeams, ix, week, savix: integer;
function WriteBox(week: integer): string;
var
  str: string;
  ix: integer;
begin
  Result := Format('Неделя=%d ', [week]);
  for ix := 1 to nTeams do
  begin
    if odd(ix) then
      Result := Result + ' '
    else
      Result := Result + 'v';
    Result := Result + IntToStr(Teams[ix]);
  end;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
  nTeams := StrToInt(Edit1.Text);
  if Odd(nTeams) then
    inc(nTeams); {должны иметь номера каждой группы}
  ListBox1.Clear;
  for ix := 1 to nTeams do
    Teams[ix] := ix;
  ListBox1.Items.Add(WriteBox(1));
  for week := 2 to nTeams - 1 do
  begin
    Teams[1] := Teams[nTeams - 1];
      {используем Teams[1] в качестве временного хранилища}
    for ix := nTeams downto 2 do
      if not Odd(ix) then
      begin
        savix := Teams[ix];
        Teams[ix] := Teams[1];
        Teams[1] := savix;
      end;
    for ix := 3 to nTeams - 1 do
      if Odd(ix) then
      begin
        savix := Teams[ix];
        Teams[ix] := Teams[1];
        Teams[1] := savix;
      end;
    Teams[1] := 1; {восстанавливаем известное значение}
    ListBox1.Items.Add(WriteBox(week));
  end;
end;
end.

Высокоточный таймер.

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

Автор: John Mertus

{
Имя файла: HRTimer.PAS V1.00
Создан: Апр 17 1997, 06:40, автор John Mertus
Обновлен #6: Окт 12 1997, 10:56 John Mertus
Оболочка для таймера высокой точности при создании приложений под
Win95/WinNT
Var
HRT : THRTimer
HRT := THRTimer.Create;
HRT.StartTimer;  Сброс таймера в ноль
HRT.ReadTimer;   Возвращает отсчитанное время в миллисекундах
начиная со времени старта
HRT.Free;
Список изменений
Версия  1.00 - Первый выпуск
}
{-----------------Модуль HRTimer-------------------John Mertus Апрель 97---}
unit HRTimer;
{-------------------Объявления-------------------------------}
interface
uses Windows;
type
  THRTimer = class(TObject)
    constructor Create;
    function StartTimer: Boolean;
    function ReadTimer: Double;
  private
    StartTime: Double;
    ClockRate: Double;
  public
    Exists: Boolean;
  end;
  {--------------------------Реализация-----------------------------------}
implementation
{------------------Create-------------------------John Mertus----Мар 97-}
constructor THRTimer.Create;
{ Получаем точное системное время и сохраняем его для дальнейшего       }
{ использования.                                                        }
{                                                                       }
{***********************************************************************}
var
  QW: TLargeInteger;
begin
  inherited Create;
  Exists := QueryPerformanceFrequency(QW);
  ClockRate := QW.QuadPart;
end;
{------------------StartTimer---------------------John Mertus----Мар 97-}
function THRTimer.StartTimer: Boolean;
{ Получаем точное системное время и сохраняем его для дальнейшего       }
{ использования.                                                        }
{                                                                       }
{***********************************************************************}
var
  QW: TLargeInteger;
begin
  Result := QueryPerformanceCounter(QW);
  StartTime := QW.QuadPart;
end;
{-------------------ReadTimer---------------------John Mertus----Мар 97---}
function THRTimer.ReadTimer: Double;
{ Получаем точное системное время и сохраняем его для дальнейшего       }
{ использования.                                                        }
{                                                                       }
{***********************************************************************}
var
  ET: TLargeInteger;
begin
  QueryPerformanceCounter(ET);
  Result := 1000.0 * (ET.QuadPart - StartTime) / ClockRate;
end;
end.

Высокоточный таймер 2.

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

{ 
  You want to measure the elapsed time in a repeat, while or a for -Loop 
  to do some optimation ? 
  Sie mochten eine Routine mit einer repeat, while oder for i :=...hinsichtlich der 
  durchlaufenen Zeit messen, um z.B.Optimierungsaktivitaten durchzufuhren. 
}
 // Global Variables, Globale Variablen: 
  t1, t2, Res: TLargeInteger;
   bOk: BOOL;
 implementation
 uses Windows, dialogs;
 procedure StartTimer;
 begin
   bOK := QueryPerformanceFrequency(Res);
   if bOK then
     QueryPerformanceCounter(t1);
 end;
 procedure StopTimer;
 begin
   if bOK then
     QueryPerformanceCounter(t2);
 end;
 procedure LoopRoutine;
 var
   iCounter: integer;
 begin
   StartTimer;
   // Put here some kind of loop: For, While or Repeat 
  for i := 0 to iCounter do...
   { WHILE.... 
   repeat...until}
   { After the Loop/ 
   Nach Abschluss der Schleifenroutine:}
     if bOK then
       QueryPerformanceCounter(t2);
 end;
 procedure StartSchleife;
 begin
   LoopRoutine;
   if bOK then
     {$IFDEF VER80}
     // Show the elapsed time: 
    MessageDlg('Der Schleifendurchlauf dauerte: ' + Format('%g Sekunden.',
       [(t2.QuadPart - t1.Quadpart) / Res.QuadPart]));
   $Else {$IFDEF VER120}
   MessageDlg('Der Schleifendurchlauf dauerte: ' + Format('%g Sekunden.',
     [(t2 - t1) / Res]));
   {$ENDIF}
 end;

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

Автор: Nomadic.

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

procedure FNTimeCallBack(uTimerID, uMessage: UINT;
dwUser, dw1, dw2: DWORD); stdcall;
begin
  // Тело процедуры.
end;

А дальше в программе (например по нажатию кнопки) создаешь Таймер и вешаешь на него созданную процедуру.

uTimerID:=timeSetEvent(10,500,@FNTimeCallBack,100,TIME_PERIODIC); 

Подробности смотри в Help. Ну и в конце убиваешь таймер.

timeKillEvent(uTimerID); 
И все. Точность этого способа до 1 мсек. минимальный интервал времени можно задавать 1 мсек.

Обратите внимание на то, что все CALLBACK-функции, вызываемые Windows, должны использовать соглашение о вызовах stdcall.

Как работать с буфером обмена (Clipboard).

Собираются программисты встречать новый год. Уже разлили пиво (по бокалам - а вы что подумали?), и тут один говорит: - Я хочу произнести тост. Давайте выпьем за моего злейшего врага. И пусть в новом году у него денег будет в десять раз больше, чем у меня! И, обведя взглядом недоуменные лица окружающих, добавляет: - За Билла Гейтса!

Этот пример использует картинку, кнопку и компонент shape на форме. Когда пользователь кликает по кнопке, то изображение формы сохраняется в в переменной FormImage и копируется в буфер обмена (Clipboard). Затем изображение формы копируется обратно в компонент картинки, тем самым создавая интересный эффект, особенно, если кнопку понажимать несколько раз.

procedure TForm1.Button1Click(Sender: TObject);
var
  FormImage: TBitmap;
begin
  FormImage := GetFormImage;
  try
    Clipboard.Assign(FormImage);
    Image1.Picture.Assign(Clipboard);
  finally
    FormImage.Free;
  end;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
  Shape1.Shape := stEllipse;
  Shape1.Brush.Color := clLime;
  Image1.Stretch := True;
end;

Следующий пример копирует содержимое экрана в буфер обмена:

procedure CopyScreenToClipboard;
var
  dx,dy: integer;
  hSourcDC, hDestDC, hBM, hbmOld: THandle;
begin
  dx := screen.width;
  dy := screen.height;
  hSourcDC := CreateDC('DISPLAY', nil, nil, nil);
  hDestDC := CreateCompatibleDC(hSourcDC);
  hBM := CreateCompatibleBitmap(hSourcDC, dx, dy);
  hbmold:= SelectObject(hDestDC, hBM);
  BitBlt(hDestDC, 0, 0, dx, dy, hSourcDC, 0, 0, SRCCopy);
  OpenClipBoard(form1.handle);
  EmptyClipBoard;
  SetClipBoardData(CF_Bitmap, hBM);
  CloseClipBoard;
  SelectObject(hDestDC,hbmold);
  DeleteObject(hbm);
  DeleteDC(hDestDC);
  DeleteDC(hSourcDC);
end;

Запуск и закрытие Excel, добавление и удаление книг или листов.

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

{ **** UBPFD *********** by delphibase.endimus.com ****
>> Запуск и закрытие Excel, добавление и удаление книг и листов
На данный момент работает:
- вызов и закрытие Excel
- добавление новых, открытие ранее созданных и удаление рабочих книг
- добавление и удаление листов в рабочие книги
Зависимости: ComObj, SysUtils,Dialogs,Controls;
Автор:       lookin, lookin@mail.ru, Екатеринбург
Copyright:   lookin
Дата:        04 мая 2002 г.
***************************************************** }
unit MSExcel;
interface
uses ComObj, SysUtils, Dialogs, Controls;
procedure CallExcel(Show: boolean);
procedure CloseExcel;
procedure AddWorkBook(WorkBookName: Ansistring);
procedure OpenWorkBook(WorkBookName: Ansistring);
procedure CloseWorkBook(WorkBookName: Ansistring);
procedure ActivateWorkBook(WorkBookName: Ansistring);
procedure ActivateWorkSheet(WorkBookName, WorkSheetName: Ansistring);
function WorkBookIndex(WorkBookName: Ansistring): integer;
function WorkSheetIndex(WorkBookName, WorkSheetName: Ansistring): integer;
procedure CheckExtension(Name: Ansistring);
procedure AddWorkSheet(WorkBookName, WorkSheetName: Ansistring);
procedure DeleteWorkSheet(WorkBookName, WorkSheetName: Ansistring);
var
  Excel: Variant;
implementation
procedure CallExcel(Show: boolean);
begin
  if VarIsEmpty(Excel) = true then
  begin
    Excel := CreateOleObject('Excel.Application');
    if Show then
      Excel.Visible := true;
  end;
end;
procedure CloseExcel;
begin
  if VarIsEmpty(Excel) = false then
  begin
    Excel.Quit;
    Excel := 0;
  end;
end;
procedure AddWorkBook(WorkBookName: Ansistring);
var
  k: integer;
begin
  CheckExtension(WorkBookName);
  if VarIsEmpty(Excel) = true then
  begin
    Excel := CreateOleObject('Excel.Application');
    Excel.Visible := true;
  end;
  k := WorkBookIndex(WorkBookName);
  if k = 0 then
  begin
    Excel.Workbooks.Add;
    Excel.ActiveWorkbook.SaveCopyAs(FileName := WorkBookName);
    Excel.ActiveWorkbook.Close;
    Excel.Workbooks.Open(WorkBookName);
  end
  else
    MessageDlg('Книга с таким именем уже существует.', mtWarning, [mbOk], 0);
end;
procedure OpenWorkBook(WorkBookName: Ansistring);
var
  k: integer;
begin
  CheckExtension(WorkBookName);
  if VarIsEmpty(Excel) = true then
  begin
    Excel := CreateOleObject('Excel.Application');
    Excel.Visible := true;
  end;
  k := WorkBookIndex(WorkBookName);
  if k = 0 then
    Excel.Workbooks.Open(WorkBookName)
  else
    MessageDlg('Книга с таким именем уже открыта.', mtWarning, [mbOk], 0);
end;
procedure CloseWorkBook(WorkBookName: Ansistring);
var
  k: integer;
begin
  if VarIsEmpty(Excel) = false then
  begin
    k := WorkBookIndex(WorkBookName);
    if k <> 0 then
      Excel.ActiveWorkbook.Close(WorkBookName)
    else
      MessageDlg('Книга с таким именем отсутствует.', mtWarning, [mbOk], 0);
  end;
end;
procedure ActivateWorkBook(WorkBookName: Ansistring);
var
  k: integer;
begin
  if VarIsEmpty(Excel) = false then
  begin
    k := WorkBookIndex(WorkBookName);
    if k <> 0 then
      Excel.WorkBooks[k].Activate;
  end;
end;
procedure ActivateWorkSheet(WorkBookName, WorkSheetName: Ansistring);
var
  k, j: integer;
begin
  if VarIsEmpty(Excel) = false then
  begin
    k := WorkBookIndex(WorkBookName);
    j := WorkSheetIndex(WorkBookName, WorkSheetName);
    if j <> 0 then
      Excel.WorkBooks[k].Sheets[j].Activate;
  end;
end;
procedure AddWorkSheet(WorkBookName, WorkSheetName: Ansistring);
var
  k, j: integer;
begin
  if VarIsEmpty(Excel) = false then
  begin
    k := WorkBookIndex(WorkBookName);
    if k <> 0 then
    begin
      Excel.DisplayAlerts := False;
      Excel.Workbooks[k].Sheets.Add;
      j := WorkSheetIndex(WorkBookName, WorkSheetName);
      if j = 0 then
        Excel.Workbooks[k].ActiveSheet.Name := WorkSheetName;
    end;
  end;
end;
procedure DeleteWorkSheet(WorkBookName, WorkSheetName: Ansistring);
var
  k, j: integer;
begin
  if VarIsEmpty(Excel) = false then
  begin
    k := WorkBookIndex(WorkBookName);
    Excel.DisplayAlerts := false;
    j := WorkSheetIndex(WorkBookName, WorkSheetName);
    if j <> 0 then
      Excel.Workbooks[k].Sheets[j].Delete
    else
      MessageDlg('Листа с таким именем в этой книге нет.', mtWarning, [mbOk],
        0);
  end;
end;
procedure CheckExtension(Name: Ansistring);
var
  s: string;
begin
  //проверка расширения
  s := ExtractFileExt(Name);
  if LowerCase(s) <> '.xls' then
    if
      MessageDlg('Вы задали имя книги с нестандартным расширением. Продолжить?',
      mtWarning, [mbYes, mbCancel], 0) = mrCancel then
      Abort;
end;
function WorkBookIndex(WorkBookName: Ansistring): integer;
var
  i, n: integer;
begin
  //проверка на наличие книги с этим именем
  n := 0;
  if VarIsEmpty(Excel) = false then
    for i := 1 to Excel.WorkBooks.Count do
      if Excel.WorkBooks[i].FullName = WorkBookName then
      begin
        n := i;
        break;
      end;
  WorkBookIndex := n;
end;
function WorkSheetIndex(WorkBookName, WorkSheetName: Ansistring): integer;
var
  i, k, n: integer;
begin
  //проверка на наличие листа с этим именем в книге с этим именем
  n := 0;
  if VarIsEmpty(Excel) = false then
  begin
    k := WorkBookIndex(WorkBookName);
    for i := 1 to Excel.WorkBooks[k].Sheets.Count do
      if Excel.WorkBooks[k].Sheets[i].Name = WorkSheetName then
      begin
        n := i;
        break;
      end;
  end;
  WorkSheetIndex := n;
end;
end.

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

procedure TForm1.Button1Click(Sender: TObject);
begin
  //вызов Excel, true - если хотите при вызове Excel отобразить окно Excel
  CallExcel(true);
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
  //добавление новой рабочей книги с заданным именем
  //ВАЖНО: используйте полное имя рабочей книги, т.е. включая путь
  AddWorkBook('D:\qwerty.xls');
end;
procedure TForm1.Button3Click(Sender: TObject);
begin
  //добавление листа с именем ff в рабочую книгу D:\qwerty.xls
  AddWorksheet('D:\qwerty.xls', 'ff');
end;
procedure TForm1.Button4Click(Sender: TObject);
begin
  //активация рабочей книги
  ActivateWorkBook('D:\1234.xls');
end;
procedure TForm1.Button5Click(Sender: TObject);
begin
  //активация листа в рабочей книге
  ActivateWorkSheet('D:\qwerty.xls', 'ff');
end;
procedure TForm1.Button6Click(Sender: TObject);
begin
  //открытие рабочей книги
  OpenWorkBook('D:\qwerty.xls');
end;
procedure TForm1.Button7Click(Sender: TObject);
begin
  //закрытие рабочей книги
  CloseWorkBook('D:\qwerty.xls');
end;
procedure TForm1.Button8Click(Sender: TObject);
begin
  //удаление листа из рабочей книги
  DeleteWorkSheet('D:\qwerty.xls', 'ff');
end;
procedure TForm1.Button9Click(Sender: TObject);
begin
  //закрытие Excel
  CloseExcel;
end;
end.

Работа с ячейкой листа Microsoft Excel.

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

{ **** UBPFD *********** by delphibase.endimus.com ****
>> Работа с ячейкой листа Microsoft Excel
Получает и заносит число типа double в ячейку листа Microsoft Excel
Зависимости: ComObj
Автор:       lookin, lookin@mail.ru, Екатеринбург
Copyright:   lookin
Дата:        30 апреля 2002 г.
***************************************************** }
//ВНИМАНИЕ: ОБЯЗАТЕЛЬНОЕ условие работы - наличие запущенного Excel
//получение double из заданной ячейки первого листа в заданной рабочей книге
function DoubleValueFromExcelCell(ExcelWorkBook, ExcelCell: string): double;
var
  i: integer;
  Excel, v: Variant;
begin
  Excel := GetActiveOleObject('Excel.Application');
  for i := 1 to Excel.Application.Workbooks.Count do
    if Excel.Application.Workbooks[i].FullName = ExcelWorkBook then
    begin
      v := Excel.Application.Workbooks[i].Sheets[1].Range[ExcelCell];
      DoubleValueFromExcelCell := VarAsType(v, varDouble);
      v := 0;
      Excel := 0;
    end;
end;
//занесение double в заданную ячейку первого листа в заданной рабочей книге
procedure DoubleValueToExcelCell(Value: double; ExcelWorkBook, ExcelCell:
  string);
var
  i: integer;
  Excel, v: Variant;
begin
  Excel := GetActiveOleObject('Excel.Application');
  for i := 1 to Excel.Application.Workbooks.Count do
    if Excel.Application.Workbooks[i].FullName = ExcelWorkBook then
    begin
      Excel.Application.EditDirectlyInCell := false;
      v := Value;
      Excel.Application.Workbooks[i].Sheets[1].Range[ExcelCell] := v;
    end;
end;

Проход по ячейкам Excel макросом.

Новости от Microsoft: "... новая операционная система имеет удобный дружественный интерфейс и множество функций. облегчающих жизнь пользователю. Сразу после запуска она форматирует диск, уничтожает содержимое BIOS и выводит из строя процессор, после чего на экране появляется надпись - "Теперь питание компьютера можно отключить".
Private Sub CommandButton1_Click()
Dim i As Integer
Dim index As Integer
  For Each cell_in_loop In Range("A2:A8")
    For i = 3 To 21
      index = 24 - i
      If cell_in_loop.Value < Лист2.Cells(index, 1) Then
        If cell_in_loop.Offset(0, 1).Value = "Да" Then
          cell_in_loop.Offset(0, 2).Value = Лист2.Cells(index, 3)
        Else
          cell_in_loop.Offset(0, 2).Value = Лист2.Cells(index, 2)
        End If
      End If
    Next
  Next
End Sub

Пример объединения ячеек и выравнивания текста в Excel.

Статьи по Delphi (006)…
Встречаются двое юзеров. Один говорит: - Я тут свежий антивирус достал, не хочешь себе установить? - Нет, мне это не нужно. - Почему? - Да мой комп так глючит, что на нем ни один вирус не запустится.

Вызывать так:

MerCen('A4:J4');

Исходный код:

var
  xls: Variant;
const
  xlCenter = -4108;
procedure MerCen(ran: string);
begin
  xls.Range[ran].Select;
  xls.Selection.HorizontalAlignment := xlCenter;
  xls.Selection.VerticalAlignment := xlCenter;
  xls.Selection.WrapText:= false;
  xls.Selection.Orientation := 0;
  xls.Selection.ShrinkToFit := False;
  xls.Selection.MergeCells := False;
  xls.Selection.Merge;
end;

Управление Microsoft Excel.

Генеральный спонсор апокалипсиса: компания Microsoft.
uses AciveX;        // для Delphi 3 и 4
uses Ole2, OleAuto; // для Delphi 2
procedure TForm1.Button1Click(Sender: TObject);
var 
  MyExcel: Variant;
begin
  MyExcel:=CreateOleObject('Excel.Application');
  MyExcel.Visible:=true;
  MyExcel.WorkBooks.Add;
  MyExcel.Cells[1,1].value:='1';
  MyExcel.ActiveWorkbook.SaveAs(PATHNAME);
  MyExcel.Quit;
end;

Пример создания новой таблицы в Excel 2000.

Автор: http://www.sources.ru
uses
  ComObj, ActiveX;
var
  Row, Col: integer;
  DestRange: OleVariant;
  Excel: Variant;
begin
  Excel := CreateOleObject('Excel.Application.9');
  Excel.Visible := True;
  Excel.WorkBooks.Add; //Создать новую таблицу
  //Можно помещать текст и значения в диапазон ячеек
  //Поместить     слово тест в диапазон ячеек
  Excel.ActiveSheet.Range['A2', 'B3'].Value := 'Тест';
  //Или число
  Excel.ActiveSheet.Range['A4', 'B5'].Value := 42;
  //А вот так задаётся формула
  Excel.ActiveSheet.Range['A10', 'A11'].Formula := '=RAND()';
  //Можно задавать номера ячеек и столбцов
  Excel.ActiveSheet.Cells.Item[1, 1].Value := 'Первая ячейка';
  Row := 1;
  Col := 3;
  Excel.ActiveSheet.Cells.Item[Row, Col].Value := 'Другая ячейка';
  //Можно скопировать данный из одного диапазона ячеек в другой
  DestRange := Excel.Range['D6', 'F10'];
  Excel.Range['A1', 'C5'].Copy(DestRange);
  //Можно задавать параметры шрифта в определённой ячейке
  Excel.Range['A2', 'A2'].Font.Size := 20;
  Excel.Range['A2', 'A2'].Font.FontStyle := 'Bold';
  Excel.Range['A2', 'A2'].Font.Color := clFuchsia;
  Excel.Range['A2', 'A2'].Font.Name := 'Arial';
  //Можно ещё и так изменить цвет диапазона ячеек
  Excel.Range['B2', 'C6'].Interior.Color := RGB(223, 123, 123);
end;

Обмен данными с Excel.

Статьи по Delphi (006)…
Самое часто произносимое в мире имя: - Билл Гейтс!? - Нет, его матушка!

В Delphi 5, для обмена данными между Вашим приложением и Excel можно использовать компонент TExcelApplication, доступный на Servers Page в Component Palette.

На форме находится TStringGrid, заполненный некоторыми данными и две кнопки, с названиями To Excel и From Excel. Так же на форме находится компонент TExcelApplication со свойством Name, содержащим XLApp и свойством ConnectKind, содержащим ckNewInstance.

Когда нам необходимо работать с Excel, то обычно мы открываем ExcelApplication, затем открываем WorkBook и в конце используем WorkSheet.

Итак, несомненный интерес представляет для нас листы (WorkSheets) в книге (WorkBook). Давайте посмотрим как всё это работает.

Посылка данных в Excel

Это можно сделать с помощью следующей процедуры :

procedure TForm1.BitBtnToExcelOnClick(Sender: TObject);
var
  WorkBk: _WorkBook; //  определяем WorkBook
  WorkSheet: _WorkSheet; //  определяем WorkSheet
  I, J, K, R, C: Integer;
  IIndex: OleVariant;
  TabGrid: Variant;
begin
  if GenericStringGrid.Cells[0,1] <> '' then
  begin
    IIndex := 1;
    R := GenericStringGrid.RowCount;
    C := GenericStringGrid.ColCount;
    // Создаём массив-матрицу
    TabGrid := VarArrayCreate([0,(R - 1),0,(C - 1)],VarOleStr);
    I := 0;
    //  Определяем цикл для заполнения массива-матрицы
    repeat
    for J := 0 to (C - 1) do
      TabGrid[I,J] := GenericStringGrid.Cells[J,I];
    Inc(I,1);
    until
    I > (R - 1);
    // Соединяемся с сервером TExcelApplication
    XLApp.Connect;
    // Добавляем WorkBooks в ExcelApplication
    XLApp.WorkBooks.Add(xlWBatWorkSheet,0);
    // Выбираем первую WorkBook
    WorkBk := XLApp.WorkBooks.Item[IIndex];
    // Определяем первый WorkSheet
    WorkSheet := WorkBk.WorkSheets.Get_Item(1) as _WorkSheet;
    // Сопоставляем Delphi массив-матрицу с матрицей в WorkSheet
    Worksheet.Range['A1',Worksheet.Cells.Item[R,C]].Value := TabGrid;
    // Заполняем свойства WorkSheet
    WorkSheet.name := 'Customers';
    Worksheet.Columns.Font.Bold := True;
    Worksheet.Columns.HorizontalAlignment := xlRight;
    WorkSheet.Columns.ColumnWidth := 14;
    // Заполняем всю первую колонку
    WorkSheet.Range['A' + IntToStr(1),'A' + IntToStr(R)].Font.Color := clBlue;
    WorkSheet.Range['A' + IntToStr(1),'A' + IntToStr(R)].HorizontalAlignment := xlHAlignLeft;
    WorkSheet.Range['A' + IntToStr(1),'A' + IntToStr(R)].ColumnWidth := 31;
    // Показываем Excel
    XLApp.Visible[0] := True;
    // Разрываем связь с сервером
    XLApp.Disconnect;
    // Unassign the Delphi Variant Matrix
    TabGrid := Unassigned;
  end;
end;

Получение данных из Excel

Это можно сделать с помощью следующей процедуры.

procedure TForm1.BitBtnFromExcelOnClick(Sender: TObject);
var
  WorkBk: _WorkBook;
  WorkSheet: _WorkSheet;
  K, R, X, Y: Integer;
  IIndex: OleVariant;
  RangeMatrix: Variant;
  NomFich: WideString;
begin
  NomFich := 'C:\MyDirectory\NameOfFile.xls';
  IIndex := 1;
  XLApp.Connect;
  // Открываем файл Excel
  XLApp.WorkBooks.Open(NomFich, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam,
  EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam,EmptyParam,0);
  WorkBk := XLApp.WorkBooks.Item[IIndex];
  WorkSheet := WorkBk.WorkSheets.Get_Item(1) as _WorkSheet;
  // Чтобы знать размер листа (WorkSheet), т.е. количество строк и количество
  // столбцов, мы активируем его последнюю непустую ячейку
  WorkSheet.Cells.SpecialCells(xlCellTypeLastCell,EmptyParam).Activate;
  // Получаем значение последней строки
  X := XLApp.ActiveCell.Row;
  // Получаем значение последней колонки
  Y := XLApp.ActiveCell.Column;
  // Определяем количество колонок в TStringGrid
  GenericStringGrid.ColCount := Y;
  // Сопоставляем матрицу WorkSheet с нашей Delphi матрицей
  RangeMatrix := XLApp.Range['A1',XLApp.Cells.Item[X,Y]].Value;
  // Выходим из Excel и отсоединяемся от сервера
  XLApp.Quit;
  XLApp.Disconnect;
  //  Определяем цикл для заполнения TStringGrid
  K := 1;
  repeat
    for R := 1 to Y do
      GenericStringGrid.Cells[(R - 1),(K - 1)] := RangeMatrix[K,R];
    Inc(K,1);
    GenericStringGrid.RowCount := K + 1;
  until
    K > X;
  // Unassign the Delphi Variant Matrix
  RangeMatrix := Unassigned;
end;
Содержание.