| Value | Meaning | | EVENTLOG_ERROR_TYPE | Error event | | EVENTLOG_WARNING_TYPE | Warning event | | EVENTLOG_INFORMATION_TYPE | Information event | | EVENTLOG_AUDIT_SUCCESS | Success Audit event | | EVENTLOG_AUDIT_FAILURE | Failure Audit event | For more information, see Event Types. NumStringsSpecifies the number of strings present in the log (at the position indicated by StringOffset). These strings are merged into the message before it is displayed to the user. EventCategorySpecifies a subcategory for this event. This subcategory is source specific. ReservedFlagsReserved ClosingRecordNumberReserved StringOffsetSpecifies the offset of the strings within this event log entry. UserSidLengthSpecifies the length, in bytes, of the UserSid member. This value can be zero if no security identifier was provided. UserSidOffsetSpecifies the offset of the security identifier (SID) within this event record. To obtain the user name for this SID, use the LookAccountSid function. DataLength Specifies the length, in bytes, of the event-specific data (at the position indicated by DataOffset). DataOffsetSpecifies the offset of the event-specific information within this event record. This information could be something specific (a disk driver might log the number of retries, for example), followed by binary information specific to the event being logged and to the source that generated the entry. SourceName Contains the variable-length null-terminated string specifying the name of the source (application, service, driver, subsystem) that generated the entry. This is the name used to retrieve from the registry the name of the file containing the message strings for this source. It is used, together with the event identifier, to get the message string that describes this event. ComputernameContains the variable-length null-terminated string specifying the name of the computer that generated this event. There may also be some pad bytes after this field to ensure that the UserSid is aligned on a DWORD boundary. UserSidSpecifies the security identifier of the active user at the time this event was logged. This member may be empty if the UserSidLength member is zero. Remarks The defined members are followed by the replacement strings for the message identified by the event identifier, the binary information, some pad bytes to make sure the full entry is on a DWORD boundary, and finally the length of the log entry again. Because the strings and the binary information can be of any length, no structure members are defined to reference them. The event identifier together with SourceName and a language identifier identify a message string that describes the event in more detail. The strings are used as replacement strings and are merged into the message string to make a complete message. The message strings are contained in a message file specified in the source entry in the registry. To obtain the appropriate message string from the message file, load the message file with the LoadLibraryEx function and use the FormatMessage function. The binary information is information that is specific to the event. It could be the contents of the processor registers when a device driver got an error, a dump of an invalid packet that was received from the network, a dump of all the structures in a program (when the data area was detected to be corrupt), and so on. This information should be useful to the writer of the device driver or the application in tracking down bugs or unauthorized breaks into the application. Как видно структура содержит несколько полей, некоторые из которых необходимо тоже форматировать. Для идентификации пользователя используеться функция LookAccountSid. А описание свойства (event) содержит лишь параметры для детального описания сообщения, которое форматируется с помощью функции FormatMessage. Схему форматирования иллюстрирует следующая диаграмма:
Также необходимо учитывать что EventID должны быть получены при наложении маски $0000FFFF (EventID And $0000FFFF). А время форматировать из Unix формата со смещением от нулевого меридиана.
Здесь находиться исходный текст свободно расспространяемой программы: (14K) Ниже перечислены все функция для работы с журналом событий BackupEventLog ClearEventLog CloseEventLog DeregisterEventSource GetEventLogInformation GetNumberOfEventLogRecords GetOldestEventLogRecord NotifyChangeEventLog OpenBackupEventLog OpenEventLog ReadEventLog RegisterEventSource ReportEvent В статье использованы материалы из MSDN Library - January 2000, Copyright (c) 1995-2000 Microsoft Corp. All rights reseved.
Cпециально для
Просто и ясно о PageMaker и Delphi
Дорогие коллеги, данной статьей я хочу показать вам основные принципы работы с Adobe Pagemaker из Delphi.
Итак небольшой экскурс - Adobe Pagemaker - довольно распространенный в нашей стране пакет издательской верстки, в основном он применяется в газетах и журналах для подготовки материала к отпечатке в типографию. Данный пакет имеет в свое роде некоторые преимущества перед например Microsoft Word - ом при подготовке журнальных полос или верстке книг. На примере компонента TKDPageMaker я хочу показать вам возможность управления данной программой из Delphi.
Возможная область применения данного пакета - по работе мне приходилось создавать отчеты в виде брошюр
Итак начнем. В документации поставляемой фирмой Adobe к данному пакету написано что PageMaker поддерживает динамический обмен данными (DDE) с любыми приложениями.
В данном компоненте реализована 3 основных метода , это - Property PathPageMaker : String Read FPathPageMaker Write SetPathPageMaker; метод служащий для указания пути к исполняемому файлу Pagemaker, используется для того что-бы TDDEClientConv запустил данное приложение в виде DDE сервера
Property Enable : Boolean Read FEnable Write SetEnable; метод используется для запуска Adobe PageMaker , т.е фактически для установки DDE соединения
Property UnitMeasurement : TUnitMeasurement Read FunitMeasurement Write SetUnitMeasurement; метод определяет единицы измерения которыми будет оперировать PageMaker при передаче команд. Например команда PageMaker Script Language PageSize позволяет установить размер страницы как в миллиметрах так и в дюймах - что бы не заботится об установки единиц измерения в командах и создан этот метод позволяющий гибко реализовать задание размеров в той форме в какой установил программист.
Рабочий пример
Ну, теперь - о главном. Вниманию уважаемой публики предлагается простой модуль с иерархией объектов, реализующих вышеописанные методы нотификации. Для удобства использования серверами, находящимися в DLL, и экспортирующими только функции (не классы), необходимые клиенту определения вынесены в отдельный unit: | (********************************************************************* * Notifier object definitions * *********************************************************************) unit NotifyDef; interface uses Windows; const tnEvent = 0; // Use kernel object Event (SetEvent) tnThreadMsg = 1; // Use message to thread ID (PostThreadMessage) tnWinMsg = 2; // Use message to window HWND (PostMessage) tnCallBack = 3; // Asynchronous call user function TNotifierProc tnCallEvent = 4; // Asynchronous call object event handler TNotifierEvent type TNotifierProc = procedure(Owner: THandle; Msg,UserParam : dword); TNotifierEvent = procedure(Sender : TObject; Msg,UserParam : dword) of object; implementation end. |
Для создания объекта нотификатора заданного типа сервер может использовать функцию (см. модуль ): function MakeNotifier(hEventObj,hSender : THandle; EventType : byte; MsgID,UserParam : dword) : TNotify; Описание параметров ПараметрНазначение Значение, использование в способах нотификации Событие Сообщение потоку Сообщение окну Процедура клиента | hEventObj | Хэндл низкоуровнего объекта, используемого для нотификации | хэндл события - результат вызова CreateEvent | ID потока (можно узнать GetCurrentThreadID) | хэндл окна | адрес процедуры | | hSender | Условный хэндл объекта сервера - то, что сервер хочет сообщить о себе | не используется | TMessage.LParam | TMessage.LParam | Owner в TNotifierProc | | EventType | Тип объекта нотификатора - см. константы в модуле | tnEvent | tnThreadMsg | tnWinMsg | tnCallBack | | MsgID | Идентификатор сообщения | не используется | TMessage.Msg | TMessage.Msg | Msg в TNotifierProc | | UserParam | Пользовательский параметр | не используется | TMessage.WParam | TMessage.WParam | UserParam в TNotifierProc | Собственно, параметры hSender,MsgID,UserParam могут быть заряжены произвольными данными на усмотрение программиста, нотификаторы не используют их для своих нужд.
Нотификатор типа "асинхронный вызов обработчика события" (tnCallEvent) нельзя создать через функцию MakeNotifier - требуется явный вызов конструктора. Это связано с тем, что адрес TNotifierEvent нельзя привести к четырехбайтному типу THandle. Параметры конструктора аналогичны параметрам MakeNotifier при EventType=tnCallback.
Далее приводится собственно текст юнита реализации библиотеки нотификаторов.
| (****************************************************************************** * The Collection of Notifier objects. * ******************************************************************************) unit Notify; interface uses Windows,NotifyDef; type TNotify = class protected hNotify : THandle; hOwner : THandle; Message, UParam : dword; public constructor Create(hEventObj : THandle); procedure Execute; virtual; property Owner : THandle read hOwner write hOwner; property Param : dword read UParam write UParam; end; TThreadNotify = class(TNotify) public constructor Create(hEventObj,hSender : THandle; MsgID,UserParam : dword); procedure Execute; override; end; TWinNotify = class(TThreadNotify) public procedure Execute; override; end; TCallBackNotify = class(TThreadNotify) public procedure Execute; override; end; TCallEventNotify = class(TThreadNotify) private fOnNotify : TNotifierEvent; public constructor Create(hEventObj : TNotifierEvent; hSender : THandle; MsgID,UserParam : dword); property OnNotify : TNotifierEvent read fOnNotify write fOnNotify; procedure Execute; override; end; function MakeNotifier(hEventObj,hSender : THandle; EventType : byte; MsgID,UserParam : dword) : TNotify; implementation function MakeNotifier(hEventObj,hSender : THandle; EventType : byte; MsgID,UserParam : dword) : TNotify; begin case EventType of tnEvent : result := TNotify.Create(hEventObj); tnThreadMsg : result := TThreadNotify.Create(hEventObj, hSender, MsgID, UserParam); tnWinMsg : result := TWinNotify.Create(hEventObj, hSender, MsgID, UserParam); tnCallBack : result := TCallBackNotify.Create(hEventObj, hSender, MsgID, UserParam); else result := nil; end; end; (*** TNotify ***) constructor TNotify.Create(hEventObj : THandle); begin hNotify := hEventObj; end; procedure TNotify.Execute; begin SetEvent(hNotify); end; (*** TThreadNotify ***) constructor TThreadNotify.Create(hEventObj,hSender : THandle; MsgID,UserParam : dword); begin inherited Create(hEventObj); Owner := hSender; Message := MsgID; UParam := UserParam; end; procedure TThreadNotify.Execute; begin PostThreadMessage(hNotify, Message, UParam, hOwner); end; (*** TWinNotify ***) procedure TWinNotify.Execute; begin PostMessage(hNotify, Message, UParam, hOwner); end; (*** TCallbackNotify ***) procedure TCallbackNotify.Execute; begin TNotifierProc(hNotify)(hOwner, Message, UParam); end; (*** TCalleventNotify ***) constructor TCalleventNotify.Create(hEventObj : TNotifierEvent; hSender : THandle; MsgID,UserParam : dword); begin OnNotify := hEventObj; Owner := hSender; Message := MsgID; UParam := UserParam; end; procedure TCalleventNotify.Execute; begin if assigned(OnNotify) then OnNotify(TObject(Owner), Message, UParam); end; end. | Порядок работы очень прост. Сервер инициализирует экземпляр нужного типа, воспользовавшись функцией MakeNotifier или прямым вызовом конструктора. Виртуальный метод TNotify.Execute реализует заданный способ нотификации, именно его сервер вызывает, когда нужно выполнить извещение клиента.
Эта библиотечка родилась вследствие практической необходимости как результат обобщения наработок на данную тему. Теперь, создавая серверный модуль, я снабжаю его этим набором нотификаторов, чтобы он мог предоставить клиенту свободный выбор способа извещения.
Практическим примером использования объектов-нотификаторов является таймерный менеджер, которому будет посвящена следующая статья под названием "".
Специально для
Расположение эксперта внутри DLL библиотеки
Если вы хотите расположить вашего эксперта не в пакете, а в DLL библиотеке, библиотека должна экспортировать функцию INITWIZARD0001 следующего формата: | type TWizardRegisterProc = function(const Wizard: IOTAWizard): Boolean; type TWizardTerminateProc = procedure; function INITWIZARD0001(const BorlandIDEServices: IBorlandIDEServices; RegisterProc: TWizardRegisterProc; var Terminate: TWizardTerminateProc): Boolean stdcall; | Для регистрации вашего эксперта вызовите внутри этой функции RegisterProc и передайте ей экземпляр заранее созданного класса вашего эксперта. BorlandIDEServices - указатель на основной интерфейс для работы со всей IDE. Отдельные части его мы рассмотрим далее. По окончании работы IDE или при принудительной выгрузке вашего эксперта будет вызвана функция Terminate, которую вы должны передать среде. Поместите полный путь к DLL в ключ реестра HKEY_CURRENT_USER\Software\Borland\Delphi\7.0\Experts или HKEY_LOCAL_MACHINE\SOFTWARE\Borland\Delphi\7.0\Experts Именем ключа может быть произвольная строка. Эксперт будет запущен только при перезапуске среды, если она выполнялась. Вуаля!
Распределенные системы на основе COM+
Для создания распределенных систем, т.е. систем, элементы которых работают на нескольких компьютерах, в Delphi используется технология Midas. Данная технология имеет как достоинства, так и недостатки, связанные с тем, что для ее функционирования необходимо устанавливать дополнительные компоненты, осуществляющие связь между компьютерами и проводить их настройку. Оказывается, что использование COM+ дает возможность проектировать такие системы средствами, встроенными в Windows. Единственным условием, которое обязательно должно быть выполнено - в сети должен обязательно присутствовать контроллер домена, осуществляющий идентификацию пользователей. Причем не обязательно под Windows 2000. Вы можете использовать сеть с контроллером домена под Windows NT 4.0 с установленными апдейтами. Внимание! Распределенные системы в одноранговой сети (WORKGROUP) на основе COM+ работать не будут. В этом разделе мы рассмотрим пример создания простого приложения и организацию его работы под управлением COM+.
Для создания компонента воспользуемся Мастером New Transaction Object в Delphi 6, Рисунок 10 (заметим, что в Delphi 5 вам придется сначала создать библиотеку ActiveX и лишь затем вызвать Мастер MTS Object).
Затем с помощью Редактора Библиотеки Типов создадим в его интерфейсе метод SayHello (Рисунок 11) и напишем его реализацию.
| unit uHello; {$WARN SYMBOL_PLATFORM OFF} interface uses ActiveX, Mtsobj, Mtx, ComObj, HelloMTS_TLB, StdVcl; type THelloTest = class(TMtsAutoObject, IHelloTest) protected procedure SayHello(const Mess: WideString); safecall; { Protected declarations } end; implementation uses ComServ, Dialogs; procedure THelloTest.SayHello(const Mess: WideString); begin ShowMessage(Mess); end; initialization TAutoObjectFactory.Create(ComServer, THelloTest, Class_HelloTest, ciMultiInstance, tmBoth); end. | Думаю, что приведенный код в комментариях не нуждается. Далее с помощью утилиты Component Services сначала создадим пустой пакет (Рисунок 12), а затем установим в него наш компонент (Рисунок 13).


Теперь напишем простое Windows-приложение, которое будет вызывать единственный метод данного компонента. Код, который для этого используется, ничем не отличается от обычного вызова COM компонента.
| unit uTest; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls; type TfrmMaim = class(TForm) btCallHello: TButton; procedure btCallHelloClick(Sender: TObject); private { Private declarations } public { Public declarations } end; var frmMaim: TfrmMaim; implementation {$R *.dfm} uses HelloMTS_TLB; procedure TfrmMaim.btCallHelloClick(Sender: TObject); var HelloTest : IHelloTest; begin HelloTest := CoHelloTest.CreateRemote('AlexAD'); HelloTest.SayHello('Hell Word!'); end; end. | В данном примере указано имя компьютера и нажать кнопку, то через некоторое время на экране появится сообщение (Рисунок 15).
В том, что компонент запущен под управлением MTS, легко убедиться с помощью той же утилиты Component Services - дело в том, что активный компонент начинает вращаться (Рисунок 15).
Если вы теперь возьмете и запустите созданное тестовое приложение на другом компьютере, и попытаетесь выполнить ту же операцию, то система выдаст сообщение о том, что компонент не зарегистрирован.
Теперь организуем связь между двумя компьютерами через COM+. Делается это очень просто. Сначала выберем в утилите Component Services мышкой папку, где установлен наш компонент и вызовем Application Export Wizard. Далее укажем, что мы хотим экспортировать не сам компонент, а только Application proxy (Рисунок 16).
После этого Мастер создаст нам MSI файл, где будет находиться вся информация для организации связи между компьютерами. Теперь останется только скопировать этот файл на нужный компьютер и щелкнуть по нему мышкой. При этом автоматически запустится Мастер, который проведет proxy в MTS. В этом легко убедиться с помощью утилиты Component Services.
Теперь, если вы запустите тестовое приложение, то на компьютере, где установлен MTS компонент, появится сообщение (Рисунок 15).
В прошлом к.т.н., доц. Рыбинской Государственной Авиационной Технологической Академии и нач. отделения программирования НПО КРИСТА, по программированию. В настоящее время - Senior Software Engeener, RegScan Inc., PA, USA.
Расширенная обработка исключительных ситуаций
Описанный ниже механизм иллюстрирует один из методов регистрации программных ошибок времени выполнения, что может быть полезно при тестировании приложения. Идея заключается в комбинированном использовании компоненты, которая регистрирует все возникающие во время выполнения исключительные ситуации (Exceptions, в дальнейшем – ИС) в файле журнала и формы, которая визуализирует полученный LOG-файл. Предлагаемый способ обработки ИС обладает следующими преимуществами по сравнению со стандартным: Вся информация записывается в файл для последующего просмотра сотрудниками, выполняющими поддержку кода Создаётся снимок системы в целом: информация об ИС, объекте, который её породил, приложении и ОС Имеется возможность регистрировать сообщения, которые не являются результатом ИС
Расширяемость
Приведенная реализация имеет ряд ограничений. Первое и основное - это отказ от использования элементов в атрибутах XML документов. Это ограничение может быть снято переработкой кода парсера и процедур сохранения XML. Для отличия элементов от атрибутов в интерфейсе объектов можно придти к следующему соглашению: Все классовые типы являются элементами Все простые типы являются атрибутами соответствующих объектов Пример. TPerson = class; TMyXMLMessage = class(TPersistent) published property LanguageOfText: WideString; property ToPerson: TPerson; end; TPerson = class(TPersistent) published property FirstName: WideString; property LastName: WideString; end; Таким образом, в первом случае объект приведенного выше класса TMyXMLMessage при сериализации даст следующий XML код: english Osama Unknoun При обработке простых типов как атрибутов получим следующий более компактный код: Второй вариант позволяет работать с любыми документами, однако надо решить, каким образом описывать данные #CDDATA. Возможно, для этого придется зарезервировать какой-либо тип.
Второе ограничение, которое следует упомянуть, это способ описания коллекций. В приведенной реализации коллекции сохраняются в виде тега свойства, в который вложены описания элементов коллекции. Довольно часто в XML документах повторяющаяся группа тегов не заключается специально в теги, отделяющие эту группу. Это является препятствием для написания классов для обработки уже существующих документов. Поэтому необходимо предусмотреть и такую возможность.
Приведенная реализация будет постоянно обновляться, в том числе и на основании Ваших, уважаемый читатель, предложений. Последняя версия компонента с исходными текстами входит в библиотеку Globus VCL Extention Library.
Чудин Андрей, октябрь 2001г. Специально для
Разбиение объектного пространства сцены путём построения octree-дерева
Have you ever stood and stared at it, Morpheus? Marveled at its beauty. Its genius. Billions of people just living out their lives... oblivious. Did you know that the first Matrix was designed to be a perfect human world? Where none suffered, where everyone would be happy. It was a disaster. No one would accept the program. Entire crops were lost. Some believed we lacked the programming language to describe your perfect world. But I believe that, as a species, human beings define their reality through suffering and misery. Agent Smith. The Matrix. Хотя в Королевстве статьи такого типа публикуются нечасто, я всё же попросил разрешения у Королевы сделать это, так как на тематику статей сайта никакие ограничения не накладываются, а сама программа написана на Object Pascal в среде Delphi. Для тех, кто читал мои ранние статьи по DirectX, хочу предупредить, что свои исследования в области прикладного API вроде DirectX и OpenGL я прекратил, и теперь больше занимаюсь алгоритмами, необходимыми для реализации трёхмерной графики. Ну да это так, к слову. В компьютерной графике уже давно используются различные методы разделения объектного пространства на части с целью эффективной обработки только тех элементов сцены, которые наблюдатель может непосредственно увидеть через виртуальную "камеру". Всевозрастающая сложность геометрических сцен даёт прекрасную почву для исследования и разработки таких алгоритмов, причём если в компьютерной графике высокого уровня эти алгоритмы позволяют просто сократить время рендеринга сцены с месяцев до дней, то для графики реального времени они являются просто жизненно необходимыми - иначе понятие "интерактивная графика" просто бы отсутствовало. Обычно тем, кто только начинает пробовать свои силы в 3D-пространстве, трудно понять, для чего же нужно разбиение пространства. Действительно, если опыты не выходят за рамки "солнышка и земли вокруг него", то заниматься этим нет смысла. Но допустим, мы взялись за рендеринг сложной сцены, вроде уровня Quake 3. Количество полигонов в сцене может достигать нескольких десятков тысяч (из года в год это значение неудержимо растёт), и если этот массив данных целиком отправлять на графический конвейер в каждом кадре, ни о какой интерактивности в игре и речи быть не может. Графический процессор будет тратить время на просчёт каждого полигона у каждого объекта, даже если в результате он жестоко не попадёт на экран. В то же время лего заметить, что из всей сцены рельно в кадре постоянно видна лишь её небольшая часть. Очевидно, что объекты за "спиной" не будут видны однозначно, то же самое можно сказать об объектах, лежащих за границей зрения (рамками экрана). Если реализовать алгоритм, который позволяет выявить такие объекты, в результате его работы в графический ускоритель на обработку мы будем посылать сравнительно малую часть всей геометрии. Здесь я собираюсь рассмотреть метод разделения объектного пространства, который называется octree (по-моему, от латинского octa - восемь, и английского tree - дерево). Восьмеричное дерево. Вообще подобные алгоритмы были разработаны ещё в 70-х годах, например, для точного описания ландшафта, но позже нашли своё применение в компьютерной графике. Данный алгоритм производит разделение объектного пространства на восемь подпространств. Общую схему работы можно представить следующими шагами: Помещаем всю сцену в выровненный по осям куб. Этот куб описывает все элементы сцены и является корневым (root) узлом дерева. Проверяем количество примитивов в узле, и если полученное значение меньше определённого порогового, то производим связывание (ассоциацию) данных примитивов с узлом. Узел, с которым ассоциированы примитивы, является листом (leaf). Если же количество примитивов, попадающих в узел, больше порогового значения, производим разбиение данного узла на восемь подузлов (подпространств) путём деления куба двумя плоскостями. Мы распределяем примитивы, входящие в родительский узел, по дочерним узлам. Далее процесс идёт рекурсивно, т. е. для всех дочерних узлов, содержащих примитивы, выполняем пункт 2. Данный процесс построения дерева может содержать несколько условий прекращения рекурсии: Если количество примитивов в узле меньше или равно пороговому значению. Если рекурсия (количество делений) достигла какой-то определённой глубины вложенности. Если количество созданных узлов достигло порогового значения.
Самый простое и наглядное условие - это проверка на количество попадающих в узел геометрических примитивов (в нашем случае таким примитивом является треугольник). Это значение можно свободно варьировать, однако если вы хотите, чтобы скорость врендеринга была действительно хорошей, необходимо учитывать особенности современных видеокарт. Т. е. для отдельного вызова на графический конвейер необходимо подавать 200-300+ вершин, поэтому количество треугольников в узле должно быть достаточно большим - 100 и более. С другой стороны, бессмысленно делать это значение слишком большим - вне зависимости от того, видны ли все примитивы листа или нет, на графический конвейер они будут отправлены все, а это приведёт в большинстве случаев к бессмысленной обработке зачастую невидимой геометрии.
А теперь о том, как же именно применение данного алгоритма может помочь быстро откинуть части невидимой геометрии. Те элементы, что отображаются при рендеринге на экране, попадают в так называемый viewing frustum - пространство видимости. Графически оно выглядит вот так:
Рисунок 1. Viewing frustum
Теперь, в процессе рендеринга мы рекурсивно выполняем следующую процедуру: начиная с базового (root) куба, мы проверяем, попадает ли данный куб в поле зрения (viewing frustum). Если НЕТ - на этом всё и заканчивается, если же ДА - перемещаемся вглубь рекурсии на один шаг, т. е. поочерёдно проверяем видимость каждого из восьми подузлов корневого узла и т. д. Преимущество заключается в том, что если определено, что данный узел не виден, то можно смело не выводить и всю геометрию этого узла - она тоже будет не видна. Таким образом, ценой всего лишь нескольких проверок, мы отбросим значительную часть сцены. А в случае, если не виден корневой узел, на экран не будет выведено ничего. Сплошная экономия!
Представленная программа как раз и демонстрирует работу данного алгоритма. Из двоичного файла загружается сцена (я взял готовую модель для 3D Studio MAX), представляющая собой интерьер простой кухни. Для этой сцены строится octree-дерево, в качестве порогового количества примитивов в узле установлено значение 300. В сцене я специально разместил два высокополигональных объекта, чтобы можно было "прочувствовать" преимущество алгоритма. Это бутылки из-под кетчупа на столе. Как только они попадают в область видимости, fps резко падает, но при выходе их за пределы видимости fps возрастает. Если же направить камеру в пустоту, fps возрастает до максимально возможного, так как в этом случае рендеринг сцены не производится. Если бы мы не использовали алгоритм разбиения пространства, fps был бы неизменно низким, словно бутылки с кетчупом видны постоянно.
К сожалению, от достоинств алгоритма а нужно перейти к его недостаткам, коих немало. Первый из них - это возможное деления примитива ребрами кубов дерева, например, вот так:
Рисунок 2. Проблемный случай
Обычно примитив относят к тому узлу, в пределах которого лежат вершины, образующие примитив. К какому из узлов отнести треугольник в данном случае? Казалось бы, взять да и отнести его к узлу I. Но так делать нельзя, и вот почему. Предположим, что мы связали треугольник с узлом I - тогда, если в процессе рендеринга мы определили, что узел I не виден, то и треугольник выведен не будет. Однако при этом возможна ситуация, когда узлы II и III будут в пространстве вида - полигон "выпадет" из сцены. Для избежания этого примитив относят к узлу, если хотя бы одна вершина примитива находится в пределах узла. В данном случае при таком подходе один и тот же треугольник будет ассоциирован с узлами I, II и III. В принципе, это не так уж страшно, так как данные можно индексировать, и этим избежать значительных расходов памяти. Однако в процессе рендеринга, если видны все три узла, полигон придётся вывести три раза. А это уже неприятно. Тем более, что такой полигон обычно не один, их множество.
Кое-где пишут, что это не так страшно, но я не согласен. Если такой примитив, например, покрыт большой текстурой, скорость вывода упадёт в несколько раз. Да и вообще, такие КрамольныЕ мысли в интерактивной графике недопустимы! Поэтому я поступил так: если примитив не полностью лежит в узле, его индекс заносится в отдельный массив. В процессе рендеринга ведётся учёт всех отрисованных "дробных" примитивов в специальном буфере, и если при выводе данного треугольника его индекс совпадёт с одним из индексов в буфере, мы не рисуем этот треугольник. Как показал опыт, помечать примитивы не так уж медленно, гораздо более медленно выводить геометрию три-четыре раза подряд.
Всё бы хорошо, да с делением примитива есть ещё одна проблема. Допустим, узлы I, II и III не видны. Однако узел IV может всё же попадать в поле зрения камеры, и видно, что частичка полигона всё же может выпасть, вот так:
Рисунок 3. Полигон выпал
Что делать? Очевидно, что нельзя относить примитив к узлу, ориентируясь только на факт принадлежности вершины примитива к этому узлу. Наверное, все же вместо этого надо проверять, проходит ли РЕБРО примитива через пространство узла. Это может потребовать гораздо больше времени при построении дерева, но зато мы избежим выпадения полигонов. Кстати, структуру один раз построенного дерева можно записать в файл, и уже больше не строить его каждый раз при старте программы, а загружать из файла. В приведённом случае полигон придётся отнести ко всем четырём узлам.
Второй недостаток octree-дерева - это вывод всех объектов, находящихся в поле viewing frustum, но на самом деле в конечном счёте невидимых. Например, стена кухни может закрывать бутылки с кетчупом, но они всё равно будут отсылаться на конвейер (так как находятся в области viewing frustum), и fps будет низким. По-видимому, чистым octree-based алгоритмом здесь не обойтись, необходимо дополнительно реализовать так называемый Z-buffer, например, на тайловой основе. Коротко его работу можно описать так:
Разбиваем проектную плоскость на некоторое число прямоугольников (тайлов), размером, например 32х32 пикселя. Если некий примитив полностью закрывает собой этот тайл, записываем в этот тайл среднее z-значение данного примитива При выводе очередного узла определяем, находятся ли его лицевые грани полностью в пределах тайла. Если это так и z-значение ближайшей вершины узла-куба больше z-значения тайла, то узел является скрытым, а значит, вся его геометрия тоже скрыта.
Вот примерно так можно будет определить, что бутылки с кетчупом находятся за стеной. К сожалению, всё это мною пока не реализовано :(
Представленная программа написана в IDE Delphi 5 без использования VCL, поэтому проблем с компиляцией в средах различных версий выше 3 я не предвижу. Используется API OpenGL (стандартный модудь Delphi opengl.pas), и дополнительный небольшой модуль myglext.pas, где я описал некоторые необходимые для работы программы расширения OpenGL (всё-таки удивительно, насколько стандартный модуль неполон в этом вопросе - ведь некоторые недокументированные в нём API-функции уже давно входят в ядро OpenGL и являются фундаментальными для 3D-графики). Для более эффективного рендеринга используется функция ядра glDrawElements() и не используются специфические расширения конкретных видеокарт, поэтому программа по идее должна работать на любом компьютере. Управление: мышью можно вращать камеру, а левой и правыми кнопками мыши двигать камеру вперёд-назад. Клавишей D включается/отключается отрисовка рёбер узлов дерева - можно как бы увидеть его структуру. Класиша W задаёт wireframe-режим.
Реализация алгоритма вполне работоспособна, но ещё недостаточно эффективна. Пораскинув мозгами, можно сделать несколько оптимизаций (чем автор и собирается заняться), а также попытаться избавиться от указанных мною ранее проблем.
Если вам есть что сказать — пишите
Скачать проект
Реализация языка шаблонов для Object Pascal на Perl
| Раздел Подземелье Магов | сев А.В. , дата публикации 24 января 2002г. |
Реализация передач команд серверу
Для этого в компоненте в разделе реализована функция Procedure ExecuteMacroPM(Str:String); // Выполнить макрос Которая в свою очередь вызывает метод TDDEClientConv.
К сожалению разработчики немного запутали передачу логических данных в процедуры и функции Script Language и в разные функциях булевые значения могут передаваться в виде '0' -логического "НЕТ" , так и в виде строкового ".F." , "OFF" или "False"
В общем для обработки преобразования реализовано несколько функций // вернуть строковый On Off Function ReturnOnOffStr(Value: Boolean) : String; // вернуть строковый .T..F. Function ReturnTrueFalseStr(Value: Boolean) : String; // вернуть строковый 0 , 1 Function Return0_1Str(Value: Boolean) : String; // более подробно смотрите в компоненте…
Реализация приема данных из DDE сервера
Запрос данных с сервера в компоненте к сожалению не реализован функцией т.к. компонент в свое время был написан за два часа в очень скоростном режиме, если вы обратите внимание на некоторые функции то запрос данных с DDE сервера в них реализован примерно так var PcharReply : Array[0..1023] of char; begin PcharReply := DDE.RequestData('GetPMState'); В основе вызывается стандартная функция RequestData TDDEClientConv.Для более подробной информацие обратитесь в справку по TDDEClientConv.
Обработка событий является одним из
Введение
Обработка событий является одним из ключевых моментов в COM. Существует масса программ, для нормального функционирования которых требуется поддержка событий.
GUI пользователя должен уметь обрабатывать различное количество событий, например, таких как: нажатие на кнопку мыши, перемещение мыши по экрану и т.д. Приблизительно так же может возникнуть потребность обрабатывать события внутри объектов COM. В данной статье мы рассмотрим принцип работы свободно связанных событий и создадим наглядное приложение для демонстрации использования такого типа событий в COM+. (Для более детальной информации о события в COM+ смотрите статью А.Новика «Система поддержки событий COM+» на сайте журнала «Клиент-Сервер»).
Редактирование реквизитов поля
Эта операция производится в обработчике TConfiguratorFr.SpeedButton5Click главной формы конфигуратора. Так же, как в случае с таблицей, сначала запоминаются текущие реквизиты поля, а затем производится обновление информации в памяти и в системной базе данных. Допускается изменение имени поля, группы данных, типа и размера поля.
Редактирование реквизитов таблицы
Эта операция производит в обработчике TConfiguratorFr.SpeedButton4Click главной формы конфигуратора. Действия протекают по стандартной схеме: сначала запоминаются текущие реквизиты структуры таблицы, а затем производит их корректировка с учетом информации, введенной пользователем. Допускается изменение имени таблицы, категории информации, к которой отнесена таблица (что эквивалентно изменению ее имени), а также пользовательских комментариев к ней.
Как прекрасна была бы жизнь,
Как прекрасна была бы жизнь, если б можно было все насущные нужды разработчика удовлетворить только средствами языка разработки! Увы, нет в мире совершенства, как говорил Лис, и поэтому фирмы-разработчики средств разработки генерируют всё новые, всё более мощные среды разработки (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) Нажимаем кнопку "Сгенерировать" - в каталогах, указанных на закладке "На-стройки" будут сформированы файлы Рисунок 1 Рисунок 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) Рисунок 3 В модуль главной формы и во все модули, в которых предполагается использовать импортируемые из DLL функции, включаем заголовочный файл Example_Extern.h Пишем пользовательский код и компилируем проект. Всё! Скриншот работы тес-тового приложения приведён на Рисунок 4 Рисунок 4
9 октября 2001 г Специально для Скачать архив: (92 K)
(1) — кстати, компиляция приложения и dll с пакетом vcl50 полностью снимает проблему дочерних окон и использование менеджера памяти BORLNDMM.DLL (подключение ShareMem) становится не только не нужным, но и опасным . (2) — как ни странно, но и от Microsoft бывает что-нибудь хорошее… (3) — когда одно приложение управляет другим или позволяет себе "уведомлять"другое приложение о своих событиях (4) — когда приложения выполняются на разных компьютерах в локальной сети (или internet) и взаимодействуют друг с другом. (5) — подробнее о VMT будет рассказано далее. (6) — если произойдет "сдвиг" VMT, последствия могут быть непредсказуемыми -но фактический вызов будет неверным. (7) — когда один пакет ссылается на другой, а тот, в свою очередь, на третий, а тот … (8) — только выполняют при загрузке дополнительную работу (9) — кстати, использование TActionList довольно хорошая практика (10) — New/Package (11) — объектно-ориентированное программирование (12) — так называемый GUID - глобальный уникальный идентификатор (13) — я всегда испытываю некоторый скепсис, когда слышу про то, что чего-то "нам надолго хватит". В памяти еще остались ощущения от программирования под 16 разрядные среды (DOS и Windows) с 64k барьером и 640k общей памяти (которой, по тогдашнему мнению Microsoft, должно было хватить надолго). Но, как мне кажется, возможностей GUID, по крайней мере, на наш век хватит. Тем более в пределах одного приложения. (14) — функция QueryInterface более подробно обсуждается далее (15) — кстати, возникновение такой ситуации свидетельствует о плохом проектировании программы и следует пересмотреть всю идеологию системы в целом. (16) — в виду отсутствия в Delphi механизма множественного наследования, агрегация (то есть включение одного объекта в другой с транспортацией его свойств и методов в объект-контейнер) довольно часто применяется в Delphi. (17) — в частности, при переводе компонента в элемент ActiveX (18) — вместе с _AddRef и _Release она реализует интерфейс IUnknown, являющийся базовым интерфейсом для всех остальных интерфейсов (как TObject является базовым классом для всех классов Delphi). Для любителей порассуждать о том, какой язык лучше вот информация к размышлению: для реализации механизма интерфейсов (да и, пожалуй, полностью всего COM) в C++ (причем в классической реализации по Страуструпу) не нужно ничего, кроме быстрых и умелых рук. Тогда как в Delphi Object Pascal потребовалось для его поддержки пришлось вносить изменения в сам язык. (19) — допустим, IUnknown (20) — включая получение интерфейса IUnknown. (21) — но следует более тщательней подходить к реализации пакета базовых классов. То есть нужно постараться предусмотреть все, что только можно, ибо после запуска проекта в самостоятельное плавание малейшее изменение в этом пакете может потребовать перекомпиляцию, причем как основного приложения, так и всех без исключения plugin's (22) — правда, особого смысла я в этом не вижу (23) — а вот это может оказаться полезным (одна интеграция с MS Office'ом стоит многого) (24) — Библиотека поддержки COM в Delphi это использует на полную катушку (25) — пока не будет проинициализировано внутренне поле FVCLComObject TComponent. А это случится только при создании на основе TComponent ActiveX объекта. (26) — если вы не хотите все испортить :)
С учетом критики и дополнений
| Раздел Подземелье Магов | Статья обновлена | Я не профи в Win API, просто у меня возникла именно такая проблема. Я нашел решение устраивающее меня. И к тому же решил, поделился с вами. Если кому-то требуется что-то другое - дерзайте, я с удовольствием прочту на "Королевстве" что и как у вас получилось. Handle = Хэндл = Рукоятка :)
Хочу предложить 2 способа: 1) Простой, с использованием command.com /c имя_консольной_проги > имя_файла_куда_переназначить_StdOut 2) С использованием Win API (2 штуки) Вы уж сами выберите, что вам подходит больше. Я использую способ № 2.2.
Рассмотрим их более подробно на примерах.
Сервер удаленного доступа. Часть I
| Раздел Подземелье Магов | Автор Александр Галилов дата публикации 05 ноября 1999г. |
Введение В этой статье рассматривается проектирование сервера удаленного доступа под Windows 95/98, позволяющего осуществлять подключение клиентов к командному интерпретатору COMMAND.COM. Прошу читателей отнестись с пониманием к возможным ошибкам и неточностям, т.к. я сравнительно недавно занялся данной темой. Приведенный в статье пример реализован на C++ Builder 1 и Delphi 3. Обратите внимание на то, что автор НЕ ТЕСТИРОВАЛ примеры Win NT. Имеются все основания предполагать некорректность их работы в этой операционной системе. Если хотите - проверьте. Под WindowsNT прилагаемый проект не работает, проверено. Что, впрочем, автор и не обещает. Лена Филиппова
Часть 1 Первая часть статьи посвящена вопросу построения внутрисистемного интерфейса сервера удаленного доступа. Здесь под термином "внутрисистемный интерфейс" подразумевается способ взаимодействия нитей (threads) сервера непосредственно с программой, производящей выполнение пользовательских запросов. В данном случае пользовательские запросы поступают во всем известный командный интерпретатор COMMAND.COM (кстати, в Windows95/98 этот файл имеет формат EXE). Для организации взаимодействия с командным интерпретатором я использовал механизм неименованных трубок (anonymous pipes). Данный механизм был выбран по причине отсутствия в Win95 таких средств, как именованные трубки (Named Pipes) в Win NT. Именованные трубки позволяют реализовать рассмотренный здесь пример со значительно меньшими усилиями. Практически отличия Win NT и Win95 таковы, что простой, в принципе, механизм приходится реализовывать весьма нетривиальным способом. Трубка - это по сути канал передачи данных. Трубка имеет два файловых идентификатора - один для записи данных, другой - для чтения имеющейся в трубке информации. Порядок продвижения байтов в трубке - FIFO (первый поступивший байт первым оказывается на выходе). С помощью API функции CreateProcess мы запускаем командный процессор, но при этом стандартные ввод и вывод перенаправляем на наши трубки. После проделывания всех этих операций мы получаем пару файловых идентификаторов, при помощи которых можем общаться с "Сеансом MS-DOS", однако, имейте ввиду, что этот механизм НЕ ПОЗВОЛЯЕТ получать/принимать данные с не стандартного ввода (STDIN) и вывода (STDOUT), т.е. Вы не сможете работать через трубки с Norton Commander или Far manager, хотя без проблем можете их запустить из командного интерпретатора COMMAND. А вот использовать все команды DOS (даже format d:) - это запросто :). Ничего не мешает работать и с другими программами, имеющими стандартный ввод-вывод, например Турбо ассемблер (TASM). Теперь немного уточню насчет использования трубок. Конечно, pipes - это не изобретение Микрософт. Когда-то их описание я обнаружил в руководстве системного программирования под Unix, но подозреваю, что и там они появились не впервые. Вот что написано про трубки в Win32 Developer's References:
A pipe is a communication conduit with two ends; a process with a handle to one end can communicate with a process having a handle to the other end.
Рассмотрим более подробно создание трубки. Функция CreatePipe создает трубку, предоставляемую программисту в виде "двух концов" - идентификаторов:
BOOL CreatePipe( PHANDLE hReadPipe, // address of variable for read handle PHANDLE hWritePipe, // address of variable for write handle LPSECURITY_ATTRIBUTES lpPipeAttributes, // pointer to security attributes DWORD nSize // number of bytes reserved for pipe );
hReadPipe и hWritePipe - указатели на идентификаторы. Идентификаторы получают значение при выполнении этой функции. lpPipeAttributes - если Вы в Win95/98 можете это опустить и указать просто NULL, если вы в Win NT - см. Win32 Developer's References. nSize- предположительный размер буфера. Система опирается на это значение для вычисления реального размера буфера. Этот параметр может быть равным нулю. В этом случае система выберет размер буфера "по умолчанию", но какой именно - я не знаю. Если трубка создана, функция возвратит ненулевое значение, в случае ошибки - вернет нуль.
Следует заметить, что для операций с трубками используются функции ReadFile и WriteFile. Причем операция чтения завершается только после того, как будет что-нибудь прочитано из трубки, а операция записи завершается после помещения данных в собственную очередь трубки. Если очередь пуста, ReadFile не завершиться, пока в трубку не поместит данные другой процесс или нить с помощью функции WriteFile. Если очередь заполнена то WriteFile не завершиться до тех пор, пока другой процесс или нить не прочитает данные из трубки с использованием ReadFile. Для общения с командным интерпретатором нам понадобится две трубки - одна для пересылки байтов "туда", другая - для получения информации из досовской сессии. Теперь обратим наше внимание на функцию CreateProcess - несомненно, очень важную и нужную (про функцию WinExec - не говорим).
BOOL CreateProcess( LPCTSTR lpApplicationName,
// pointer to name of executable module LPTSTR lpCommandLine, // pointer to command line string LPSECURITY_ATTRIBUTES lpProcessAttributes, // pointer to process security attributes LPSECURITY_ATTRIBUTES lpThreadAttributes, // pointer to thread security attributes BOOL bInheritHandles, // handle inheritance flag DWORD dwCreationFlags, // creation flags LPVOID lpEnvironment, // pointer to new environment block LPCTSTR lpCurrentDirectory, // pointer to current directory name LPSTARTUPINFO lpStartupInfo, // pointer to STARTUPINFO LPPROCESS_INFORMATION lpProcessInformation // pointer to PROCESS_INFORMATION );
lpApplicationName и lpCommandLine - указатели на "нуль-терминированные" (PChar) строки с именем запускаемого модуля (напр. "c:\command.com") и с командной строкой, которая будет передана запущенной программе в качестве аргумента. Если lpApplicationName равен нулю, то имя запускаемой программы должно быть первым в строке lpCommandLine и отделено пробелами от аргументов. Для Win NT - см. подробности в Win32 Developer's References. lpProcessAttributes - в Win95/98 игнорируется, установите его в нуль. Для Win NT - см. подробности в Win32 Developer's References. lpThreadAttributes - тоже, что и для lpProcessAttributes. bInheritHandles - показывает как наследуются идентификаторы из вызывающего процесса. Если равно TRUE, то все идентификаторы, которые могут наследоваться, наследуются новым процессом. Унаследованные идентификаторы имеют то же самое значение и привелегии, что и оригинальные. dwCreationFlags - определяет дополнительные флаги. Могут иметь следующие значения (список не полный, только для нашей задачи): CREATE_DEFAULT_ERROR_MODE - новый процесс не наследует режим ошибок (error mode) от вызывающего процесса, вместо этого CreateProcess назначает новому процессу режи по умолчанию. CREATE_NEW_CONSOLE - новый процесс создает новую консоль вместо родительской консоли. Этот флаг нельзя использовать вместе с флагом DETACHED_PROCESS. DETACHED_PROCESS - для консольных процессов - новый процесс не имеет доступа к консоли родительского процесса. HIGH_PRIORITY_CLASS - высокий приоритет. Используется в критичных ко времени выполнения процессах. IDLE_PRIORITY_CLASS - нити процесса выполняются только при простое системы (idle). NORMAL_PRIORITY_CLASS - приоритет для обыкновенных процессов без специальных задач. REALTIME_PRIORITY_CLASS - наивысший приоритет. Системные сообщения могут теряться при выполнении потока с этим приоритетом. lpEnvironment - указатель на среду окружения. Указывает на блок нуль-терминированных строк вида ИМЯ=ЗНАЧЕНИЕ. Сам блок завершается двумя нулевыми байтами для блока строк в формате ANSI и четырьмя нулевыми байтами для блока строк в формате UNICODE (см. подробности в Win32 Developer's References). lpCurrentDirectory - указатель на нуль-терминированную строку, содержащую текущий каталог. Если указатель равен NULL, то текущий каталог тот же, что и у родительского процесса. lpStartupInfo - указатель на структуру STARTUPINFO , которая определяет как должно появляться оконо для нового процесса. lpProcessInformation - указатель на структуру PROCESS_INFORMATION , заполняемую функцией CreateProcess. Эта структура содержит информацию о запущенном процессе.
Теперь более подробно рассмотрим структуры STARTUPINFO и PROCESS_INFORMATION . STARTUPINFO содержит следующие поля: DWORD cb - размер структуры в байтах LPTSTR lpReserved - не используется LPTSTR lpDesktop - только в Win NT, подробности см. Win32 Developer's References LPTSTR lpTitle - указатель на нуль-терминированную строку-заголовок консоли для консольных приложений DWORD dwX - игнорируется, если не установлен флаг STARTF_USEPOSITION в dwFlags. Определяет координаты левого верхнего угла создаваемого окна в пикселях по горизонтали. Подробности см. Win32 Developer's References DWORD dwY - игнорируется, если не установлен флаг STARTF_USEPOSITION в dwFlags. Определяет координаты левого верхнего угла создаваемого окна в пикселях по вертикали. Подробности см. Win32 Developer's References DWORD dwXSize- игнорируется, если не установлен флаг STARTF_USESIZE в dwFlags. Определяет размер создаваемого окна в пикселях по горизонтали. Подробности см. Win32 Developer's References DWORD dwYSize- игнорируется, если не установлен флаг STARTF_USESIZE в dwFlags. Определяет размер создаваемого окна в пикселях по вертикали. Подробности см. Win32 Developer's References DWORD dwXCountChars- Игнорируется, если не установлен флаг STARTF_USECOUNTCHARS. Для консольных приложений, создавших новую консоль, определяет размер экранного буфера по горизонтали. Для GUI приложений всегда игнорируется DWORD dwYCountChars- Игнорируется, если не установлен флаг STARTF_USECOUNTCHARS. Для консольных приложений, создавших новую консоль, определяет размер экранного буфера по вертикали. Для GUI приложений всегда игнорируется DWORD dwFillAttribute- Игнорируется, если не установлен флаг STARTF_USEFILLATTRIBUTE. Определяет начальные атрибуты (цвет текста и фона) для консольных приложений. Игнорируется для GUI-приложений. Может принимать значения: FOREGROUND_BLUE, FOREGROUND_GREEN, FOREGROUND_RED, FOREGROUND_INTENSITY, BACKGROUND_BLUE, BACKGROUND_GREEN, BACKGROUND_RED и BACKGROUND_INTENSITY. Например FOREGROUND_RED | BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE даст красный текст на белом фоне DWORD dwFlags- Битовое поле, показывающее какие поля структуры STARTUPINFO следует учитывать при создании окна. Могут использоваться любые комбинации значений (список не полный, подробности см. Win32 Developer's References): STARTF_USESHOWWINDOW- Если этот флаг не установлен, то wShowWindow игнорируется STARTF_USEPOSITION- Если этот флаг не установлен, то dwX и dwY игнорируются STARTF_USESIZE- Если этот флаг не установлен, то dwXSize и dwYSize игнорируются STARTF_USECOUNTCHARS- Если этот флаг не установлен, то dwXCountChars и dwYCountChars игнорируются STARTF_USEFILLATTRIBUTE- Если этот флаг не установлен, то dwFillAttribute игнорируется STARTF_USESTDHANDLES- Если установлен этот флаг, присвойте идентификаторы стандартного ввода, стандартного вывода и стандартной ошибки полям hStdInput, hStdOutput, и hStdError соответственно. Чтобы это работало, параметр fInheritHandles при вызове CreateProcess должен быть равен TRUE. WORD wShowWindow- Игнорируется, если не установлен флаг STARTF_USESHOWWINDOW. Если флаг STARTF_USESHOWWINDOW установлен, присвойте этому полю константу, определяющую способ отображения главного окна, например SW_MINIMIZE WORD cbReserved2- Зарезервировано, должно равняться нулю LPBYTE lpReserved2- Зарезервировано, должно равняться нулю HANDLE hStdInput- Игнорируется, если не установлен STARTF_USESTDHANDLES. Если флаг STARTF_USESTDHANDLES установлен, см. пункт про STARTF_USESTDHANDLES HANDLE hStdOutput- -""- HANDLE hStdError- -""- Структура PROCESS_INFORMATION содержит следующие поля: HANDLE hProcess- Дескриптор созданного процесса HANDLE hThread- Дескриптор первичной нити процесса (primary thread) DWORD dwProcessId- Идентификатор процесса DWORD dwThreadId- Идентификатор первичной нити процесса Заполнять поля структуры PROCESS_INFORMATION не нужно, они заполняются при вызове функции CreateProcess.
Рассмотрим пример использования трубок и функции CreateProcess для обмена данными с COMMAND.COM
var stinfo: TStartupInfo; prinfo: TProcessInformation; ReadPipe,WriteToCommand,ReadFromCommand,WritePipe: integer; // обнуляем поля структур для CreateProcess FillChar(stinfo,sizeof(TStartupInfo),0); FillChar(prinfo,sizeof(TProcessInformation),0); // пытаемся выполнить CreatePipe для первой и второй трубки if (not CreatePipe(ReadPipe,WriteToCommand,nil,PipeSize)) or (not CreatePipe(ReadFromCommand,WritePipe,nil,PipeSize)) then ErrorCode:=1 else begin stinfo.cb:= sizeof(stinfo); stinfo.lpReserved:= nil; stinfo.lpDesktop:= nil; stinfo.lpTitle:= nil; stinfo.dwFlags:= STARTF_USESTDHANDLES or STARTF_USESHOWWINDOW; stinfo.cbReserved2:= 0; stinfo.lpReserved2:= nil; stinfo.hStdInput:= ReadPipe; stinfo.hStdOutput:= WritePipe; stinfo.hStdError:= WritePipe; stinfo.wShowWindow:= SW_HIDE; // запускаем COMMAND.COM CreateProcess(CommandPath,nil,nil,nil,true, CREATE_DEFAULT_ERROR_MODE or NORMAL_PRIORITY_CLASS, nil,CurrentDirectory, stinfo,prinfo) После выполнения этого фрагмента мы имеем в итоге запущенный командный интерпретатор и пару файловых идентификаторов для приема/передачи символьных сообщений между COMMAND.COM и нашей программой. Сейчас самое время решить, каким образом реализовать обмен данными с трубками.
Казалось бы, что трубки вполне заменяют очереди, но это не совсем так. Обратившись к трубке для чтения символа, мы попадем в очень неприятное положение в случае, если трубка окажется пустой. Функция чтения не завершиться, пока не прочитает указанное количество символов. То же самое относится и к попытке записи символа в заполненную трубку. Выход заключается в создании двух нитей, одна из которых занимается считыванием символов и их постановкой в очередь на обработку, а другая - передачей символов из очереди в трубку.
Другой вариант - вместо отдельных очередей реализовать механизм Callback - процедур, например, по аналогии с оконными процедурами в Windows. В таком случае нити будут вызывать указанную им процедуру по мере надобности, т.е. если из досовской сессии поступает какая-либо информация, то вызывается процедура обработки входящих символов, а если есть возможность для передачи информации в сеанс MS-DOS - вызывается процедура передачи символов. Причем в последнем случае, возможно, не будет подлежащих передачи данных, тогда вызванная процедура должна сообщить об этом вызывающей нити специальным зарезервированным кодом. Таким образом, существует два основных способа обмена информацией с трубками - синхронный, когда используются Callback- процедуры и асинхронный - с использованием очередей ввода-вывода.
Приведенная в примере программа позволяет очень легко реализовать любой из этих способов. Ядро системы представлено в виде DLL, поэтому Вы можете использовать его не только в программах на Delphi.
Это дает нам возможность из основной нити программы обращаться к нашим очередям в совершенно произвольные моменты времени. Однако, следует учесть некоторые детали, касающиеся разделения нитями ресурсов. Необходимо гарантировать, что при обращении к очереди из основной нити программы мы не прервем операции с тои же очередью, производимые нитями, работающими с трубками. То же самое касается и Callback-процедур. Для избежания этого конфликта используется механизм критических секций. Критическая секция - это объект, поддерживаемый системой и разделяемый между двумя или более нитями. Суть работы критической секции состоит в том, что перед выполнением некоторого участка кода, которое нельзя прерывать, нить вызывает метод Enter, а перед завершением выполнения критического участка - метод Leave. Если во время выполнения критического участка другая нить вызовет метод Enter той же самой критической секции, то выполнение этой нити будет остановлено внутри метода Enter до тех пор, пока "предыдущая" нить, захватившая критический участок не вызовет метод Leave Этот механизм предотвращает доступ нитей к критическому участку, если он уже выполняется. Все нити для конкретного критического участка должны использовать один и тот же разделяемый объект критической секции.
Далее в примере используются функции API ReadFile и WriteFile. Сначала я опишу их, опираясь на Win32 Developer's References.
BOOL ReadFile( HANDLE hFile,// handle of file to read LPVOID lpBuffer,// address of buffer that receives data DWORD nNumberOfBytesToRead,// number of bytes to read LPDWORD lpNumberOfBytesRead,// address of number of bytes read LPOVERLAPPED lpOverlapped// address of structure for data ); hFile- Идентификатор файла для чтения. Должен быть создан с режимом доступа к файлу GENERIC_READ lpBuffer- Указатель на буфер, в который будут помещены загруженные прочитанные данные nNumberOfBytesToRead- Задает число байт, которые нужно прочитать lpNumberOfBytesRead- Указатель на переменную, которая получит значение количества прочитанных байт lpOverlapped- Указатель на структуру OVERLAPPED. В примере не используется. Для более подробной информации см. Win32 Developer's References
BOOL WriteFile( HANDLE hFile,// handle to file to write to LPVOID lpBuffer,// pointer to data to write to file DWORD nNumberOfBytesToRead,// number of bytes to write LPDWORD lpNumberOfBytesRead,// pointer to number of bytes written LPOVERLAPPED lpOverlapped// pointer to structure needed for overlapped I/O ); hFile- Идентификатор файла для записи. Должен быть создан с режимом доступа к файлу GENERIC_WRITE lpBuffer- Указатель на буфер, из которого будет записываться информация nNumberOfBytesToRead- Задает число байт, которые нужно записать lpNumberOfBytesRead- Указатель на переменную, которая получит значение количества записанных байт lpOverlapped- Указатель на структуру OVERLAPPED. В примере не используется. Для более подробной информации см. Win32 Developer's References
Обе описанные функции возвращают ненулевое значение в случае успешного завершения
//============================================================================= // Получение символа из очереди. Если символа в очереди нет - возвращает -1, // если символ есть - возвращает код символа (не char, а int !!!) function TFlowFromCommand.Get: integer; begin // входим в критическую секцию cs.Enter; Result:=GetQueue(end_chain,start_chain,chain_data,CHAIN_SIZE_FROM_COMMAND); // покидаем критическую секцию cs.Leave; end; //============================================================================= // устанавливает символ в очередь. Если символ в очередь установлен, // функция возвращает 1, если в очереди нет места - возвращает 0 function TFlowFromCommand.Put(c: char):integer; begin // входим в критическую секцию cs.Enter; Result:=PutQueue(c,end_chain,start_chain,chain_data,CHAIN_SIZE_FROM_COMMAND); // покидаем критическую секцию cs.Leave; end; //============================================================================= // вызов Callback-процедуры для передачи символа в основную нить procedure TFlowFromCommand.VCLExec; begin CallBackReceive(c,self); end; //============================================================================= procedure TFlowFromCommand.Execute; var read: integer; begin // входим в цикл repeat // если попытка чтения символа из трубки вызвала ошибку c:=0; if (not ReadFile(Pipe,c,1,read,nil)) then begin // отдаем оставшуюся часть кванта времени системе Sleep(0); continue; end // иначе делаем попытки поставить символ в очередь пока это наконец не // удасться успешно выполнить или вызываем обработчик, если он установлен else if @CallBackReceive=nil then while(Put(chr(c))=0) do Sleep(0) else begin gcs2.Enter; Synchronize(VCLExec); gcs2.Leave; end; until (Terminated or Suspended); end; //=============================================================================
Теперь у нас есть две очереди и методы Get и Put для доступа к ним. Еще мы имеем возможность "подцепить" Callback-процедуры и работать без использования очередей. Мы можем в любой момент воспользоваться этими методами для осуществления обмена информацией между запущенным командным интерпретатором и основной нитью нашей программы. Также мы можем использовать методы доступа к очередям совершенно произвольно в других нитях процесса.
Пример реализации описанного в статье механизма (C++Builder 1, Delphi 3) Вы можете скачать (16 K)
Александр Галилов
Сервер удаленного доступа. Часть II
| Раздел Подземелье Магов | Автор Александр Галилов дата публикации 11 ноября 1999г. |
Предисловие ко второй части
В первой части статьи был рассмотрен пример построения внутрисистемного интерфейса сервера удаленного доступа. К сожалению, неожиданно стали очевидными некоторые моменты, делающие использование DLL нежелательным. Во-первых, метод Synchronize далеко не всегда нормально работает в нитях (threads), созданных внутри DLL. Т.е. при вызове Synchronize(Something) метод Something не запускается. Этот эффект наблюдается только при использовании DLL и очевиднее всего именно в Win NT, что и послужило основной причиной неработоспособности примера в этой ОС. Во-вторых в Win 95/98 также есть ряд условий, отличных от таковых в Win NT, при которых Synchronize внутри DLL работает неправильно. В силу сложившихся обстоятельств в новом примере предоставляю исходник DLL, не использующей вышеупомянутый метод. Однако, при ее использовании остается проблема синхронизации с "main VCL thread". Мною были также исправлены "глюки" с обработкой очередей в предыдущей версии DLL и дополнен заголовочный файл. В-третьих, учитывая дополнительную сложность использования функций DLL для доступа к ее нитям, я решил подготовить новый пример полнофункционального (с "натягом", конечно) сервера без использования каких бы то ни было DLL. Все принципы, описанные в первой части, в примере реализованы полностью. В ходе этой работы замечены некоторые особенности работы компонента TServerSocket, которые Вы заметите, если начнете с ним активно "общаться". Но это уже так, к слову.
Введение Обеспечение обслуживания клиентов - неотъемлемая функция любого сервера удаленного доступа. Рассмотренный здесь пример не является исключением. Обслуживание клиента состоит из четырех основных стадий: Аутентификация при подключении клиента; Подготовка рабочей среды, т.е. выделение ресурсов на обслуживание; Собственно процесс обслуживания, состоящий в выполнении клиентских запросов и отсылке сообщений о результатах; Отключение клиента и освобождение выделенных ресурсов; В качестве главного "интернетовского компонента" используется TServerSocket, позволяющий осуществлять многоканальное обслуживание.
Аутентификация В данном случае аутентификация заключается в запросе пароля у пользователя и проверке правильности введенного слова. Процесс аутентификации реализован в виде отдельной нити, внутри которой происходит циклический опрос сокета пользователя на предмет наличия введенных символов. Время опроса и длина вводимой цепочки символов ограничена соответственно 30 секундами и 32 символами. Символы с кодом менее 32 (пробел) считаются признаком конца строки. После того, как пароль будет введен, выполняется проверка введенного слова на наличие в списке допустимых паролей. Если список не содержит такого слова, то пользователю отправляется сообщение о неудачной попытке и связь разрывается, после чего завершается и сама нить. В случае правильного ввода пароля происходит инициализация другой нити, производящей запуск ДОСовской сессии или подключение к уже запущенной сессии.
Выделение ресурсов Для выделения ресурсов используется нить внутри которой происходит запуск командного интерпретатора. Сразу же после запуска инициализируются нити ввода-вывода данных, а управляющая нить переходит в режим ожидания завершения работы командного интерпретатора. Каждая сессия имеет специальную структуру-описатель, в которой храниться состояние соединения, ссылка на сокет клиента и ссылка на нить, запустившую командный интерпретатор. Указатель на эту структуру храниться в списке пользователей и паролей который, в свою очередь, заполняется информацией при инициализации программы-сервера на основании файла PASSWORDS.LST (см. пример). Сама структура формируется непосредственно перед активизацией сессии.
Обслуживание Обслуживание клиентов происходит следующим образом: обработчик события, возникающего при поступлении информации от клиента, направляет поступающие данные (символы, строки) на входную очередь нити которая переносит данные через трубку (pipe) в командный интерпретатор или в запущенную из него программу на стандартное устройство ввода (STDIN). Для того, чтобы поступающие данные попадали в требуемый сеанс MS-DOS, сокет-источник данных сравнивается с сокетом в структуре-описателе сесии (см. предыдущий параграф). Когда командный интерпретатор или запущенная из него программа пытается осуществить вывод символов на стандартное устройство вывода (STDOUT) происходит передача символов через трубку в нить, контролирующую выходящий из сеанса MS-DOS поток. Эта нить вызывает Call-back функцию которая на основании данных из структуры-описателя осуществляет передачу информации через требуемый сокет.
Отключение клиента Отключение клиента осуществляется либо по инициативе клиента, либо по инициативе администратора, либо в результате сбоя в канале обмена данными. Если клиент завершил свою сессию командой EXIT, то ожидающая завершения командного интерпретатора нить сама инициализирует процесс завершения соединения. Во всех этих случаях механизм реализации отсоединения одинаков. Просто-напросто производится завершение соединения на требуемом сокете и изменение содержимого полей соответствующей структуры-описателя. Если же происходит завершение работы командного интерпретатора, то структура-описатель уничтожается, что свидетельствует о необходимости повторного создания сеанса MS-DOS при присоединении клиента. Если клиент отсоединился не завершив работу своего командного интерпретатора, то вся обработка, инициированная отключившимся клиентом на сервере, продолжается без участия клиента. В таком случае, при повторной установке соединения клиенту будет предоставлена та же сессия, которую он покинул, но, возможно, с продолжающимся или завершенным процессом выполнения ранее данного задания.
Пример (Delphi 3) Вы можете скачать (19 K)
Александр Галилов
Собственно сам PGPsdk
28 октября 1997 г. PGP, Inc. объявила о поставке PGPsdk сторонним производителям программного обеспечения. PGPsdk - это средство разработки для программистов на С, позволяющее разработчикам программного обеспечения встраивать в него стойкие криптографические функции. Можно сказать что в PGPsdk реализованы все функции пакета PGP, мало того - версия PGP начиная с 5.0 хранит криптографические функции в динамических библиотеках – dll (о том насколько это не безопасно – вопрос к Крису Касперски, я лишь скажу что насколько я силен в математике). PGPsdk - это динамическая библиотека, состоящая из трех файлов [табл. 1], поддерживающая базовые алгоритмы криптования (перечислены выше), гибкое управление ключами, сетевой интерфейс и др. (можно использовать одну библиотеку - PGP_sdk.dll, если Вы не будите использовать фирменный интерфейс пользователя от NAI и сетевую библиотеку).
Установка Скачайте архив с PGPsdk [9], на момент написания статьи доступна версия 1.7.2 (должен заметить что архив занимает 3 с лишним мегабайт), необходимо его разархивировать и из каталога \Libraries\DLL\Release взять следующие файлы - табл. 1
| Табл.1 | | PGP_SDK.dll | для криптования, управление ключами и т.д. | | PGPsdkUI.dll | (UI= user interface) интерфейсные штучки, если Вам нужно будет только шифровать/расшифровывать, то этот файл необязателен. Но очень полезен для ввода пароля, выбора получателей сообщений, генерации ключей и другое. | | PGPsdkNL.dll | (NL= network library) сетевая библиотека для работы с сервером ключей или для transport layer security. Ее мы рассматривать не будем, но в ближайшем будущем я попытаюсь ее описать. |
Собственно распространять Вам приложение придется с этими файлами, подложить их необходимо или в системный каталог WINDOWS или в каталог вместе с приложением - механизм стандартный как и для всех dll, главное чтоб библиотеку было видно Вашему приложению.
Переходим к делу. Для работы система предоставляет ряд низкоуровневых PGP API (Application Programmig Interface) функций. Заголовки (хеадеры, описания) этих функций поставляются вместе с пакетом на Ц и лежат в каталоге Headers. Если Вы как и я пишите на Delphi, можете сами сконвертировать их, а можете взять готовые тут [10]. Это проект по переводу Ц-ных хеадеров на любимый мною язык программирования. Занимается всем этим делом Стивен Хейлер (Steven R. Heller ).
Описатели переведены на Delphi по принципу как это сделано для Ц - разбросаны на кучи модулей (листинг 1). Все названия модулей аналогичны Ц-ным заголовкам, за исключением pgpEncode - переименовано в pgpEncodePas, из-за особенностей объявления в Delphi (нельзя чтоб имя процедуры совпадало с названием модуля).
| Листинг 1. Объявление используемых библиотек. uses // PGPsdk pgpEncodePas, pgpOptionList, pgpBase, pgpPubTypes, pgpUtilities, pgpKeys, pgpErrors, // always last pgpSdk; |
Единственная трудность, которая возникает на пути включения криптования в Ваше приложение - это использование слишком уж низкоуровневых PGP API функций. Для того что бы сделать какую-нибудь операцию - будь то подсчет публичных ключей в связке или просто зашифровать файл - необходимо создавать контекст, указать где находятся ключи, создать фильтр ключей, подготовить файловые дескрипторы, если с памятью - выделить ее (в случае шифрования-/-расшифрования), затем все это в обратном порядке освободить (если контекст неправильно освобождается - файлы с резервными ключиками не удалятся). И все это при том что в системном каталоге WINDOWS создается файл, в котором содержится информация где находятся файлы с публичными и секретными ключами (о нем будет подробно сказано ниже). Для сравнения работы через PGP API предоставлен листинг2.
| Листинг 2. Пример использования PGPsdk через PGP API Var context : pPGPContext; keyFileRef : pPGPKeySet; defaultKeyRing : pPGPKeySet; foundUserKeys : pPGPKeySet; filter : pPGPFilter; countKeys : PGPUInt32; keyFileName : PChar; userID : PChar; inFileRef, outFileRef : pPGPFileSpec; inFileName, outFileName : PChar; Begin // Init от C++ context:=NIL; keyFileName:='pubring.pgp'; userID:=''; inFileName:='myInFile.txt'; outFileName:='myOutFile.txt.asc'; // Begin PGPCheckResult('sdkInit', PGPsdkInit); PGPCheckResult('PGPNewContext', PGPNewContext( kPGPsdkAPIVersion, context )); PGPCheckResult('PGPNewFileSpecFromFullPath', PGPNewFileSpecFromFullPath( context, keyFileName, keyFileRef )); PGPCheckResult('PGPOpenKeyRing', PGPOpenKeyRing( context, kPGPKeyRingOpenFlags_None, keyFileRef, defaultKeyRing )); PGPCheckResult('PGPNewUserIDStringFilter', PGPNewUserIDStringFilter(context, userID, kPGPMatchSubString, filter)); PGPCheckResult('PGPFilterKeySet', PGPFilterKeySet(defaultKeyRing, filter, foundUserKeys)); // Открываем файловые манипуляторы PGPCheckResult('PGPNewFileSpecFromFullPath', PGPNewFileSpecFromFullPath(context, inFileName, inFileRef)); PGPCheckResult('PGPNewFileSpecFromFullPath', PGPNewFileSpecFromFullPath(context, outFileName, outFileRef)); // // А вот здесь уже идет кодирование. // PGPCheckResult('PGPEncode', PGPEncode( context, [ PGPOEncryptToKeySet(context, foundUserKeys), PGPOInputFile(context, inFileRef), PGPOOutputFile(context, outFileRef), PGPOArmorOutput(context, 1), PGPOCommentString(context, PChar('Comments')), PGPOVersionString(context, PChar('Version 5.0 assembly by Evgeny Dadgoff')), PGPOLastOption(context) ] )); // // Освобождаем занимаемые ресурсы и контекст PGP // if (inFileRef<>NIL) then PGPFreeFileSpec(inFileRef); if (outFileRef<>NIL) then PGPFreeFileSpec(outFileRef); if (filter<>NIL) then PGPFreeFilter(filter); if (foundUserKeys<>NIL) then PGPFreeKeySet(foundUserKeys); if (defaultKeyRing<>NIL) then PGPFreeKeySet(defaultKeyRing); if (keyFileRef<>NIL) then PGPFreeKeySet(keyFileRef); if (context<>NIL) then PGPFreeContext(context); PGPsdkCleanup; End; |
Здесь реализован пример из [9] со страницы 39. Функция PGPCheckResult позаимствована у Стивена из его примеров - принимает два параметра - строковую и код выполнения функции PGP API, если была ошибка - генерируется исключение и на экран выводится описание ошибки с именем функции (Очень помогает для ловли ошибок, а при вызове dll-библиотеки, тем более написанной на другом языке – помогает избавиться от Access violation).
| Листинг 3. Функция PGPCheckResult. procedure PGPCheckResult(const ErrorContext: shortstring; const TheError: PGPError); var s : Array [0..1024] of Char; begin if(TheError<>kPGPError_NoError)then begin PGPGetErrorString(TheError, 1024, s); if(PGPGetErrorString(TheError, 1024, s) = kPGPError_NoError)then raise exception.create(ErrorContext + ' [' + IntToStr(theError)+'] : '+StrPas(s)) else raise exception.create(ErrorContext + ': Error retrieving error description'); end; end; |
Там же у Стивена я нашел еще один проект - написанная на Delphi библиотека для VB, проект под названием SimplePGP (SPGP). Дело в том, что VB не может использовать библиотеку PGPsdk из-за ограничения импортирования библиотек dll [9, раздел FAQ]. Сам Стивен предложил мне добавить к проекту еще одну dll, тем самым забыть про PGP API, и использовать облегченную модель вызова функций криптований.
Сам интерфейс к доступу функциям выполнен не плохо, продуманно и вызов их не должен вызвать затруднений у Вас.
Открыв ее я подумал - а не убрать ли мне все эти "stdcall;export;" и просто присоединить библиотеку к ехе-файлу (ну не устраивает меня хитросплетение dll). Сказано сделано.
Сообщение окну
Сообщение окну удобно использовать, если клиентом является модуль с формой. Тогда достаточно в классе формы сделать обработчик этого сообщения: procedure WMMyMessage(var Msg : TMessage); message WM_MYMESSAGE; здесь код сообщения определен, например, так: const WM_MYMESSAGE = WM_USER+XXXX;
Сообщение посылается функцией PostMessage, а не SendMessage, чтобы сервер мог продолжить свою работу, не дожидаясь, пока клиент обработает сообщение. Таким свойством обладают все вышеописанные способы извещений. Кстати, метод Synchronize(Method: TThreadMethod) класса TThread использует для общения с главным потоком программы именно оконное сообщение, посылаемое через SendMessage. При этом заданный в параметрах вызова Synchronize метод класса выполняется в контексте главного потока (main VCL thread), и его код является потокобезопасным (может обращаться к любым объектам VCL). Но (другая сторона медали) пока наш клиент в главном потоке занят фактически выполнением этого метода или другими делами (сообщения ставятся в очередь), сервер не может продолжить работу - он замер на вызове SendMessage. Часто это весьма нежелательно.
Сообщение потоку
Сообщение потоку посылается функцией PostThreadMessage, и для его получения поток не обязан иметь окно, достаточно содержать вызовы функций GetMessage или PeekMessage.
Создание DTD для объекта
Содержание
За созданием кода для сериализации и десериализации объектов в Delphi логично перейти к рассмотрению вопроса о возможности генерации соответствующего DTD для сохраняемых в XML классов. DTD понадобится нам, если мы захотим провести проверку XML документа на корректность и допустимость с помощью одного из XML анализаторов. Работа с анализатором MSXML рассмотрена в статье на есть. Необходимо рекурсивно пройтись по всем свойствам объекта и сгенерировать модели содержания для каждого тега. При сериализации в XML мы не использовали атрибутов, а значит мы не сможем в DTD установить контроль над содержанием конкретных элементов. Остается только определить модель содержания для XML, т.е. вложенность тегов в друг друга. Хотя стандарт DTD устаревает и следует переходить к использованию схем, будет полезным обеспечить возможность создания DTD для наших объектов. Создадим процедуру GenerateDTD(), которая обеспечит запись формируемого DTD для заданного объекта Component в заданный поток Stream. Она создает список DTDList, в котором будут накапливаться атрибуты DTD, после чего передает всю черновую работу процедуре GenerateDTDInternal().
{ Процедура генерации DTD для заданного объекта в соответсвии с published интерфейсом его класса. Вход: Component - объект Выход: текст DTD в поток Stream } procedure GenerateDTD(Component: TObject; Stream: TStream); var DTDList: TStringList; begin DTDList := TStringList.Create; try GenerateDTDInternal(Component, DTDList, Stream, Component.ClassName); finally DTDList.Free; end; end;
|
|
Следующий код просматривает свойства объекта, составляет их список, а затем формирует из этого модель содержания для элемента. Для свойств классовых типов используется рекурсия. Поскольку при сериализации объекта мы не использовали атрибутов, то определений для них создавать нет необходимости.
Для всех неклассовых типов модель содержания это - (#PCDATA). К примеру, свойство объекта Tag: integer превращается в .
Отдельно подходим к коллекциям. Для них необходимо указать на множественность дочернего тега элемента коллекции. Например, для свойства TMyCollection модель содержания может выглядеть так: .
| { Внутренняя рекурсивная процедура генерации DTD для заданного объекта. Вход: Component - объект DTDList - список уже определенных элементов DTD для предотвращения повторений. Выход: текст DTD в поток Stream } procedure GenerateDTDInternal(Component: TObject; DTDList: TStrings; Stream: TStream; const ComponentTagName: string); var PropInfo: PPropInfo; TypeInf, PropTypeInf: PTypeInfo; EnumInfo: PTypeInfo; TypeData: PTypeData; i, j: integer; AName, PropName, sPropValue, s, TagContent: string; PropList: PPropList; NumProps: word; PropObject: TObject; const PCDATA = '#PCDATA'; procedure addElement(const ElementName: string; Data: string); var s: string; begin if DTDList.IndexOf(ElementName) <> -1 then exit; DTDList.Add(ElementName); s := 'if Data = '' then Data := PCDATA; s := s + '(' + Data + ')>'#13#10; Stream.Write(PChar(s)[0], length(s)); end; begin { Playing with RTTI } TypeInf := Component.ClassInfo; AName := TypeInf^.Name; TypeData := GetTypeData(TypeInf); NumProps := TypeData^.PropCount; GetMem(PropList, NumProps*sizeof(pointer)); try { Получаем список свойств } GetPropInfos(TypeInf, PropList); TagContent := ''; for i := 0 to NumProps-1 do begin PropName := PropList^[i]^.Name; PropTypeInf := PropList^[i]^.PropType^; PropInfo := PropList^[i]; { Пропустить не поддерживаемые типы } if not (PropTypeInf^.Kind in [tkDynArray, tkArray, tkRecord, tkInterface, tkMethod]) then begin if TagContent <> '' then TagContent := TagContent + '|'; TagContent := TagContent + PropName; end; case PropTypeInf^.Kind of tkInteger, tkChar, tkFloat, tkString, tkWChar, tkLString, tkWString, tkVariant, tkEnumeration, tkSet: begin { Перевод в DTD. Для данных типов модель содержания - #PCDATA } addElement(PropName, PCDATA); end; { код был бы полезен при использовании атрибутов tkEnumeration: begin TypeData:= GetTypeData(GetTypeData(PropTypeInf)^.BaseType^); s := ''; for j := TypeData^.MinValue to TypeData^.MaxValue do begin if s <> '' then s := s + '|'; s := s + GetEnumName(PropTypeInf, j); end; addElement(PropName, s); end; } tkClass: { Для классовых типов рекурсивная обработка } begin PropObject := GetObjectProp(Component, PropInfo); if Assigned(PropObject)then begin { Для дочерних свойств-классов - рекурсивный вызов } if (PropObject is TPersistent) then GenerateDTDInternal(PropObject, DTDList, Stream, PropName); end; end; end; end; { Индивидуальный подход к некоторым классам } { Для коллекций необходимо включить в модель содержания тип элемента } if (Component is TCollection) then begin if TagContent <> '' then TagContent := TagContent + '|'; TagContent := TagContent + (Component as TCollection).ItemClass.ClassName + '*'; end; { Добавляем модель содержания для элемента } addElement(ComponentTagName, TagContent); finally FreeMem(PropList, NumProps*sizeof(pointer)); end; end; |
Закоментированный код нам не нужен, но он не удален, т.к. он демонстрирует получение списка возможных значений для перечисления (Enumeration) и набора (Set). Это может понадобится, если появится необходимость генерировать свойства в виде атрибутов XML тегов и, соответственно, DTD для возможных значений этих атрибутов.
Продолжение
Создание и наследование элементов управления, редактирование особенностей
Редакторы-наследники TParticulEditor предполагают, что элемент управления TParticulEditor.Control будет "умещён в одну строку", т. е. данные, которые будут отображены в нём, можно отобразить в сравнительно "узком" элементе управления (20 пикселов). TParticulEditor имеет 4 стандартных наследника TEditEditor (редактор в виде TEdit), TButtonEditor (в виде TButton), TComboBoxEditor (в виде TComboBox), TCheckBoxEditor (в виде TCheckBox). В модуле автоматически регистрируются 11 основных типов данных, которые привязываются к редакторам следующим образом: к TEditEditor - строки, целые числа, действительные числа, к TButtonEditor - текст, выбор цвета и методы: без параметров, вычисление периметра элемента управления и масштабирование, к TComboBoxEditor - перечисление (в том числе и события), к TCheckBoxEditor - булевы величины.
Рассмотрим TEditEditor. Он редактирует данные трёх типов (в модуле PrtEdits). Всю "соль" обработки осуществляют процедуры TExecutor: StringExecutor, IntegerExecutor и RealExecutor. Так, StringExecutor просто переприсваивает данные из строки ввода TEdit редактируемой особенности, IntegerEdit и RealEdit перед присваиванием делают проверку формата вводимого числа из TEdit. Таким же образом, благодаря TExecutor можно обрабатывать любые данные, вводимые в строку. Немного остановлюсь на TButtonEditor. Он служит, как правило, для обработки сложных данных, редактирование которых производится в диалоговом окне. Таким образом, кнопка редактора TButton служит для вызова некоторого диалога (его инициализация и обработка производится внутри процедуры TExecutor). В него передаются кодированные данные (параметры Code и Info), редактируются, кодируются в строку и выдаются Result'ом. Следует обратить внимание, что если особенность только для чтения, то внутри TExecutor следует это учесть и изменить форму, запрещая редактирование данных. Также TButtonEditor служит для реализации методов. Если метод без параметров, то по клику на кнопке производится выполнение этого метода; если с параметрами - выводится диалоговое окно ввода параметров; если метод возвращает какой-либо результат, то он отображается в заголовке кнопки. В Delphi это реализуется маленькой кнопочкой с тремя точками.
Для создания нового элемента управления следует унаследовать его от TParticulControl и обязательно перекрыть методы GetTypeName, GetParticuls и SetParticul. Быстрое создание новых свойств, методов или событий выполняется с помощью процедур DoProperty, DoMethod и DoEvent. Например:
function TSample.GetTypeName: string; begin Result := 'Некоторый класс'; end; function TSample.GetParticuls: TParticulList; var P: TParticul; begin Result := TParticulList.Create; with Result do begin P := DoProperty('Просто строка', dtString, True, True, FString, '', False); Add(P); P := DoProperty('Ширина', dtInteger, Length(FString) <> 0, True, IntToStr(Width), '', False); Add(P); end; end; procedure TSample.SetParticul(Value: TParticul); begin if Value.Name = 'Просто строка' then FString := Value.Code; if Value.Name = 'Ширина' then Width := StrToInt(Value.Code); end; Теперь свойства отобразятся в Инспекторе на вкладке "Свойства" с заголовками "Просто строка" и "Ширина" (на русском!). При их редактировании выведутся соответствующие редакторы с соответствующими Executor'ами. Обращу внимание, что таким образом могут редактироваться как "реальные" свойства, так и просто поля, а, быть может, и выполнятся процедуры (что-то вроде Get и Set).
Теперь хочу коснуться наследования элементов управления. Как известно, Object Pascal не позволяет осуществлять наследование с ограничением видимости, что послужило причиной создания большого количества Custom'ов в VCL. В языке С++ эта возможность имеется (private-, protected- и public-наследование). Данный Инспектор позволяет производить имитацию private- и public-наследования. Это очень удобно, когда необходимо скрыть "лишние" особенности в потомках.
TPublicSample = class(TSample) ... TPrivateSample = class(TSample) ... implementation ... //наследуем все особенности предка и добавляем свои function TPublicSample.GetParticuls: TParticulList; begin Result := inherited GetParticuls; ... end; //добавляем только свои особенности function TPrivateSample.GetParticuls: TParticulList; begin Result := TParticulList.Create; ... end; Хочу ещё раз обратить внимание, что особенности элемента управления не имеют никакого отношения к реальным свойствам, событиям и методам. Можно обращаться к полям, методам, свойствам любой области видимости (а не только published). То есть, методы GetParticuls и SetParticul - это имитация области published.
В примере Example1 показаны реализация свойств и методов различных элементов управления (о событиях чуть попозже, там есть несколько тонкостей). TRectControl - пример элемента управления, TRoundRectControl - его public-наследник, TEllipticControl - его private-наследник. На форму выведены два TRectControl'а и по одному TRoundRectControl'у и TEllipticControl'у. Кнопка Button1 показывает/скрывает Инспектор.
Создание и отладка MTS объектов
MTS представляет собой оболочку, которая осуществляет поддержку транзакций, управление доступом и совместное использование ресурсов (resource pooling) в распределенных системах, построенных на основе COM. В Delphi имеются Мастера, которые позволяют создавать MTS объекты, поддерживающие все возможности MTS. Еще раз перечислим возможности, которые предоставляет пользователю MTS: Управление системными ресурсами, включая процессы, потоки (threads), и соединения с базами данных, что позволяет серверному приложению осуществлять работу с множеством пользователей одновременно. Автоматическую поддержку транзакций, что повышает надежность системы. Создание, выполнение и удаление серверных компонентов тогда, когда это необходимо системе. Поддержку доступа к системе на основе роли пользователя (role-based security).
С помощью этих возможностей разработчик может создавать распределенные приложения, состоящие из функциональных частей, каждая из которых реализует одно или несколько бизнес правил (business logic). Для этой цели можно использовать либо MTS объекты (MTS objects) или MTS модули данных (MTS remote data modules). Эти компоненты располагаются в динамических библиотеках (DLLs), которые затем устанавливаются в MTS. Созданные таким образом компоненты могут использоваться как обычными Windows приложениями, так и ActiveForm.
Создание MTS объектов
В Delphi 5 имеются два Мастера для создания MTS объектов. Первый, под названием MTS Object, создает компонент, похожий на обычный компонент Delphi. Второй, с именем MTS Data Module, создает модуль данных, похожий на одноименный компонент Delphi. Его можно использовать, как контейнер для размещения компонентов доступа к базам данных. Но, в общем-то, особой разницы между этими компонентами нет. Первый шаг, который необходимо сделать — создать новую ActiveX Library (Рисунок 1).
Второй - создать Data module с помощью Мастера создания MTS Data Module (Рисунок 2).
Далее необходимо выбрать потоковую модель (Threading model) и модель транзакций (Transaction model) для создаваемого компонента (Рисунок 3). Вы должны выбрать одну потоковую модель из трех - Single, Apartment, или Both.
В том случае, если выбрана Single, MTS гарантирует, что только один вызов клиента будет обрабатываться в каждый момент времени. В этом случае полностью исключается влияние одного клиентского приложения на другое. В том случае, если выбрана модель Apartment, то MTS гарантирует, что один экземпляр данного компонента в любой момент выполняет один запрос клиента, но не обязательно использует для этого один и тот же поток (thread). Поэтому нельзя использовать переменные потока (thread variables), поскольку нет гарантии, что последовательность клиентских вызовов будет обрабатываться тем же потоком данного компонента. Таким образом, для избежания конфликтов между потоками, нельзя использовать глобальные переменные или компоненты, которые находятся в модуле данных, если их одновременное использование может привести к таким конфликтам. Вместо этого следует использовать shared property manager. В том случае, если выбрана модель Both, то это означает, что модуль работает так же, как и в случае Apartment, но обратные вызовы (callbacks), которые передаются клиенту, будут выполняться последовательно. Таким образом, можно не заботиться о влиянии их друг на друга. Внимание! Модель Apartment в MTS отличается от одноименной модели в терминологии DCOM (Distributed COM). Вы так же должны выбрать один из вариантов поддержки транзакции. Доступны следующие опции: Requires a transaction. В этом случае, всякий раз, как только клиент будет обращаться к интерфейсу модуля данных, его обращение будет выполняться в контексте транзакции MTS. В том случае, если клиент работает в контексте транзакции, новая транзакция не будет создаваться. Requires a new transaction. При выборе этого варианта, каждый раз, как только к интерфейсу компонента будет происходить обращение, MTS автоматически будет создавать для него транзакцию. Supports transactions. В данном варианте модуль может работать в контексте транзакции MTS, но клиент должен поддерживать использовать контекст транзакции при вызове методов интерфейса. Does not support transactions. В этом случае модуль данных не может использоваться в контексте транзакции MTS.
Следует иметь в виду, что при установке компонента на MTS, модель транзакции можно будет изменить.
При этом можно указать максимально время, после которого транзакция будет автоматически прервана (transaction timeout). По умолчанию оно равно 60сек. Для того чтобы запретить автоматическое прерывание транзакции (например, при отладке приложения), следует установить это время равным нулю.
Для установки его следует с помощью утилиты Component Services выбрать компьютер, для которого следует изменить время транзакции и на странице Options провести соответствующие изменения (Рисунок 4).
В Delphi 6 процесс создания модуля данных практически совпадает с тем, что был описан выше, за исключением того, что новый проект будет создан автоматически, как только вы обратитесь к Мастеру Transaction Data Module Object (Рисунок 5).
При создании нового MTS data module, Delphi автоматически создает процедуру UpdateRegistry для поддержки технологии Midas, которая используется Borland.
| class procedure TTestD5.UpdateRegistry(Register: Boolean; const ClassID, ProgID: string); begin if Register then begin inherited UpdateRegistry(Register, ClassID, ProgID); EnableSocketTransport(ClassID); EnableWebTransport(ClassID); end else begin DisableSocketTransport(ClassID); DisableWebTransport(ClassID); inherited UpdateRegistry(Register, ClassID, ProgID); end; end; | Поскольку в COM+ весь обмен информацией между компьютерами осуществляется самим COM+, то данный код не требуется и его нужно удалить руками, как из интерфейсной части компонента, так и из его реализации.
Процесс создания интерфейса полностью совпадает с тем, что используется в обычном COM компоненте, и поэтому здесь не рассматривается.
Создание нестандартных редакторов особенностей
Бывают ситуации, когда редактируемые данные имеют формат, который необходимо обработать нестандартным образом. Выше были перечислены стандартные редакторы особенностей. Может оказаться, что среди них не окажется такого, который мог бы обработать данные специального вида. В этом случае можно пойти двумя путями. Первый - обработать эти данные с помощью специальной формы; второй - создать редактор с наиболее удобным элементов управления. Рассмотрим подробнее оба способа.
Первый способ наиболее простой. В начале необходимо определиться с формой. Здесь возможны два варианта: или создать форму в DesignTime или в RunTime. Обработка формы будет происходить внутри специальной процедуры типа TExecutor, которую затем необходимо будет зарегистрировать: const dtSample = 100; ... procedure function SampleExecutor(Code, Info: string; var Changed: Boolean; ReadMode: Boolean = False): string; ... implementation ... procedure function SampleExecutor(Code, Info: string; var Changed: Boolean; ReadMode: Boolean = False): string; var SampleForm: TSampleForm; {или TForm, если в RunTime} begin Changed := False; SampleForm := TSampleForm.Create(nil); with SampleForm do begin ... end; procedure TForm1.FormCreate(Sender: TObject); begin RegisterData(dtSample, TButtonEditor, SampleExecutor); ... end; ... end; Не следует регистрировать новый тип данных в конструкторе элемента управления!!! При создании второго экземпляра элемента возникнет ошибка регистрации ERegister (так как это будет попытка зарегистрировать ещё раз на один и тот же номер). Теперь можно этот новый тип использовать: function TSampleControl.GetParticuls: TParticulList; var P: TParticul; begin ... P := DoProperty('Новый тип', dtSample, ...); ... end; Но иногда хочется обработать новые данные более красивым способом. Тогда можно пойти вторым способом - создать собственный редактор свойств. Как я уже упоминал, не все элементы управления подходят для создания собственного редактора, так, например, трудно будет что-либо редактировать в TStringGrid'е стандартной высотой 20 пикселов!
Перед написанием собственного редактора необходимо выбрать элемент управления, который отобразится в рабочей области Инспектора. Это может быть либо один из элементов управления VCL, обязательно наследник TWinControl (так, например, TSpeedButton не подойдёт!), либо собственный созданный элемент управления, наследник TWinControl (TCustomControl). В конструкторе редактора необходимо инициализировать этот элемент процедурой Init, без инициализации возникнет EAccessViolation. Новый редактор, естественно, должен быть наследником TParticulEditor.
TMyControl = class(TCustomControl) ... TSampleEditor = class(TParticulEditor) ... constructor TSampleEditor.Create; begin inherited Create; Init(TMyControl); ... end; Далее в конструкторе должны быть сделаны необходимые установки: стиль, границы (если необходимо) и, главное, назначены процедуры-обработчики событий для редактирования новой особенности. Так, например, изменение особенности в TEditEditor'е и TComboBoxEditor'е осуществляет OnChange, в TButtonEditor'е и TCheckBoxEditor'е - OnClick. Обработчик будет выглядеть примерно таким образом: procedure TSampleEditor.SelfAction; var Changed: Boolean; begin Changed := True; if Assigned(Executor) then FParticul.Code := Executor((Control as TMyControl).TextProperty, FParticul.Info, Changed, FParticul.ReadMode); if Changed then Make; end; ... constructor TSampleEditor.Create; begin ... (Control as TMyControl).OnAction := SelfAction; end; Желательно, чтобы текстовые данные могли отображаться в Инспекторе при редактировании (например, в TEdit - свойство Text, в TButton, TCheckBox - Caption, в TComboBox реализовано неявно, но, тем не менее, информация о данных выдаётся в Инспектор через Items и ItemIndex), хотя и необязательно. Также необходимо перекрыть метод SetParticul, который влияет на внешний вид элемента управления в зависимости от значений полей TParticul. Далее необходимо написать специальную процедуру обработки данных. Если это простое присваивание, то можно процедуру не писать (указать при регистрации nil). Новый редактор регистрируется: procedure RegisterData(dtSample, TSampleEditor, SampleExecutor); или procedure RegisterData(dtSample, TSampleEditor, nil); В примере Example2 показаны оба подхода. Нестандартный тип данных THomo обрабатывается c помощью процедуры HomoExecutor, в которой данные редактируются с помощью формы THomoForm2. Для типа данных TDate создаётся новый редактор TDateTimePickerEditor на базе элемента управления TDateTimePicker из VCL.
Создание поля в таблице
Создание поля может выполняться двумя путями: по ходу создания новой таблицы из диалога формирования списка полей TTbDefFr по кнопке Новое поле или же непосредственно из главной формы нашей платформы по кнопке NewF. В обоих случаях создается диалог формирования новой структуры поля FldDlgFr с тем отличием, что в первом случае работа происходит с буферной структурой таблицы FDbInterface.N_pTTableInfo, а во втором – с текущей структурой таблицы FpTTableInfo главной формы конфигуратора. В обоих случаях работа начинается с создания буферной структуры поля FDbInterface.Init_NpTFieldInfo;
После выхода из диалога FldDlgFr ход действий несколько отличается, но суть их одна и та же. В первом случае сначала идет «набивка структуры таблицы» FDbInterface.N_pTTableInfo списком структур полей и обновление информации на сервере производится в один прием, т.е. одновременно создается как таблица, так и поля в ней. Во втором случае эта операция выполняется для одиночной структуры поля, добавляемой в структуру таблицы, что требует лишь обновления структуры таблицы на сервере.
Создание pop-up меню своего компонента и кое-что еще о классе TComponentExpert
Давайте рассмотрим создание простейшего одноуровневого контекстного меню на своем компоненте, которое будет открываться при щелчке правой кнопкой по нему в самом верху контекстного меню Delphi. Прежде всего вам следует разделить код вашего компонента на Design-time и Run-time. Для этого перенесите ваш компонент в модуль, с названием, например, MyComponent.pas, а процедуры регистрации его в палитре компонентов (procedure Register и т.д.) в модуль, с названием, например, MyComponentReg. На такие меры приходится идти из-за того, что Borland не включила в исходные коды исходник файла Proxies.pas. Итак, получим два файла: MyComponent.pas:
| unit MyComponent; interface uses SysUtils, Classes; type TMyComponent = class(TComponent) private { Private declarations } protected { Protected declarations } public { Public declarations } published { Published declarations } end; | MyComponentReg.pas
| unit MyComponentReg; interface uses DesignIntf, DesignEditors, MyComponent, Classes, Dialogs; type TMyComponentEditor = class(TComponentEditor) private procedure ExecuteVerb(Index: Integer); override; function GetVerbCount: Integer; override; function GetVerb(Index: Integer): string; override; procedure Edit; override; end; procedure Register; implementation procedure Register; begin RegisterComponents('Samples', [TMyComponent]); RegisterComponentEditor(TMyComponent, TMyComponentEditor); end; { TMyComponentEditor } procedure TMyComponentEditor.Edit; begin ShowMessage('TMyComponent component v1.0 by Rastrusny Vladislav'); end; procedure TMyComponentEditor.ExecuteVerb(Index: Integer); begin inherited; case Index of 0: //Действие при выборе первого определенного пункта меню end; end; function TMyComponentEditor.GetVerb(Index: Integer): string; begin case Index of 0: Result := 'Demo Menu Item 1'; //Название первого пункта меню end; end; function TMyComponentEditor.GetVerbCount: Integer; begin Result := 1; end; end. | Рассмотрим теперь, что же тут написано. В первом файле просто определен компонент MyComponent. В нем вы определяете все свойства и методы вашего компонента. Все как обычно. Теперь - второй файл MyComponentReg. Он содержит процедуры регистрации компонента и процедуру регистрации редактора компонента (TComponentEditor). Этот редактор и будет отображать меню и прочие безобразия. Итак: Определяем TMyComponentEditor как потомка TComponentEditor. Сам по себе этот класс является "воплотителем" интерфейса IComponentEditor, хотя нам все равно. Для того, чтобы все это заработало нам нужно будет переопределить стандартные методы класса TComponentEditor. Рассмотрим его: | type TComponentEditor = class(TBaseComponentEditor, IComponentEditor) private FComponent: TComponent; FDesigner: IDesigner; public constructor Create(AComponent: TComponent; ADesigner: IDesigner); override; procedure Edit; virtual; function GetVerbCount: Integer; virtual; function GetVerb(Index: Integer): string; virtual; procedure ExecuteVerb(Index: Integer); virtual; procedure Copy; virtual; procedure PrepareItem(Index: Integer; const AItem: IMenuItem); virtual; property Component: TComponent; property Designer: IDesigner; end; | Конструктор нам переопределять не нужно. Поэтому начнем с описания метода Edit. Метод Edit вызывается при двойном щелчке по компоненту. Вот так просто! При двойном щелчке на компоненте! Если метод не определен, то при двойном щелчке будет выполнен первый пункт меню, которое вы определили. Метод GetVerbCount: Integer должен возвращать количество определенных вами пунктов меню. Метод GetVerb(Index: Integer): string должен возвращать название пункта меню № Index. Метод ExecuteVerb(Index: Integer) вызывается при щелчке на пункте меню, определенном вами. Index - номер меню из метода GetVerb. В нем вы определяете действия, которые будут происходить при нажатии на ваш пункт меню. Метод Copy вызывается при копировании вашего компонента в буфер обмена Свойство Component как вы уже наверное догадались позволяет получить доступ к компоненту, на котором щелкнули мышью и т.п. Метод PrepareItem(Index: Integer; const AItem: IMenuItem) вызывается для каждого определенного вами пункта меню № Index и через параметр AItem передает сам пункт меню для настройки. Для работы нам нужно будет рассмотреть саму реализацию интерфейсас IMenuItem. Он определен в модуле DesignMenus.pas и является потомком интерфейса IMenuItems.
| IMenuItems = interface ['{C9CC6C38-C96A-4514-8D6F-1D121727BFAF}'] // public function SameAs(const AItem: IUnknown): Boolean; function Find(const ACaption: WideString): IMenuItem; function FindByName(const AName: string): IMenuItem; function Count: Integer; property Items[Index: Integer]: IMenuItem read GetItem; procedure Clear; function AddItem(const ACaption: WideString; AShortCut: TShortCut; AChecked, AEnabled: Boolean; AOnClick: TNotifyEvent = nil; hCtx: THelpContext = 0; const AName: string = ''): IMenuItem; overload; function AddItem(AAction: TBasicAction; const AName: string = ''): IMenuItem; overload; function InsertItem(const ACaption: WideString; AShortCut: TShortCut; AChecked, AEnabled: Boolean; AOnClick: TNotifyEvent = nil; hCtx: THelpContext = 0; const AName: string = ''): IMenuItem; overload; function InsertItem(Index: Integer; const ACaption: WideString; AShortCut: TShortCut; AChecked, AEnabled: Boolean; AOnClick: TNotifyEvent = nil; hCtx: THelpContext = 0; const AName: string = ''): IMenuItem; overload; function InsertItem(AAction: TBasicAction; const AName: string = ''): IMenuItem; overload; function InsertItem(Index: Integer; AAction: TBasicAction; const AName: string = ''): IMenuItem; overload; function AddLine(const AName: string = ''): IMenuItem; function InsertLine(const AName: string = ''): IMenuItem; overload; function InsertLine(Index: Integer; const AName: string = ''): IMenuItem; overload; end; | | IMenuItem = interface(IMenuItems) ['{DAF029E1-9592-4B07-A450-A10056A2B9B5}'] // public function Name: TComponentName; function MenuIndex: Integer; function Parent: IMenuItem; function HasParent: Boolean; function IsLine: Boolean; property Caption: WideString; property Checked: Boolean; property Enabled: Boolean; property GroupIndex: Byte; property HelpContext: THelpContext; property Hint: string; property RadioItem: Boolean; property ShortCut: TShortCut; property Tag: LongInt; property Visible: Boolean; end; | Начнем с конца. Т.е. с IMenuItem. Как видно, почти все члены интерфейса соответствуют членам класса TMenuItem. Т.е. обратившись в методе PrepareItem к AItem.Enabled:=false мы запретим выбор этого элемента меню. Что же касается класса TMenuItems, то они, видимо, предназначены для манипулирования элементом меню в качестве родительского для нескольких других. Думаю, в них опытным путем разобраться тоже не составит труда.
Что же касается процедуры RegisterComponentEditor, то она принимает два параметра: первый - класс компонента, для которого создается редактор свойств и второй - собственно сам класс редактора свойств.
Создание редакторов свойств
Для создания редактора свойств нужно написать класс, унаследованный от TBasePropertyEditor. Но мы рассмотрим более функционального его потомка TPropertyEditor
| TPropertyEditor = class(TBasePropertyEditor, IProperty, IProperty70) protected procedure SetPropEntry(Index: Integer; AInstance: TPersistent; APropInfo: PPropInfo); override; protected function GetFloatValue: Extended; function GetFloatValueAt(Index: Integer): Extended; function GetInt64Value: Int64; function GetInt64ValueAt(Index: Integer): Int64; function GetMethodValue: TMethod; function GetMethodValueAt(Index: Integer): TMethod; function GetOrdValue: Longint; function GetOrdValueAt(Index: Integer): Longint; function GetStrValue: string; function GetStrValueAt(Index: Integer): string; function GetVarValue: Variant; function GetVarValueAt(Index: Integer): Variant; function GetIntfValue: IInterface; function GetIntfValueAt(Index: Integer): IInterface; procedure Modified; procedure SetFloatValue(Value: Extended); procedure SetMethodValue(const Value: TMethod); procedure SetInt64Value(Value: Int64); procedure SetOrdValue(Value: Longint); procedure SetStrValue(const Value: string); procedure SetVarValue(const Value: Variant); procedure SetIntfValue(const Value: IInterface); protected { IProperty } function GetEditValue(out Value: string): Boolean; function HasInstance(Instance: TPersistent): Boolean; { IProperty70 } function GetIsDefault: Boolean; virtual; public constructor Create(const ADesigner: IDesigner; APropCount: Integer); override; destructor Destroy; override; procedure Activate; virtual; function AllEqual: Boolean; virtual; function AutoFill: Boolean; virtual; procedure Edit; virtual; function GetAttributes: TPropertyAttributes; virtual; function GetComponent(Index: Integer): TPersistent; function GetEditLimit: Integer; virtual; function GetName: string; virtual; procedure GetProperties(Proc: TGetPropProc); virtual; function GetPropInfo: PPropInfo; virtual; function GetPropType: PTypeInfo; function GetValue: string; virtual; function GetVisualValue: string; procedure GetValues(Proc: TGetStrProc); virtual; procedure Initialize; override; procedure Revert; procedure SetValue(const Value: string); virtual; function ValueAvailable: Boolean; property Designer: IDesigner read FDesigner; property PrivateDirectory: string read GetPrivateDirectory; property PropCount: Integer read FPropCount; property Value: string read GetValue write SetValue; end; | Предположим, нам нужно создать редактор для текстового свойства, при нажатии кнопки "…" в Object Inspector. Объявим специальный тип этого свойства TMyComponentStringProperty = string;
Далее, в компоненте укажем свойство данного типа property MyProperty: TMyComponentStringProperty, далее в Run-time части компонента (MyComponentReg.pas) объявим класс TMyCSPEditor (в переводе: TMyComponentStringPropertyEditor :)), унаследовав его от класса TStringProperty, который в свою очередь является потомком рассматриваемого класса TPropertyEditor: type TMyCSPEditor = class(TStringProperty) . Переопределим в нем несколько методов таким образом (фрагменты файла):
| type TVRSIDBListViewExcludeColumnsPropertyEditor = class(TStringProperty) function GetAttributes: TPropertyAttributes; override; procedure Edit;override; end; -------------------------------------------------------------- procedure TVRSIDBListViewExcludeColumnsPropertyEditor.Edit; var Text: string; begin if InputQuery('Введите строковое значение',Text)=False then Exit; Self.SetValue(Text); end; function TVRSIDBListViewExcludeColumnsPropertyEditor.GetAttributes: TPropertyAttributes; begin Result:=[paDialog]; end; | Итак, приступаем к рассмотрению методов класса TPropertyEditor. Начнем с тех, которые мы уже использовали.
Метод Edit. Просто вызывается при щелчке на кнопке "…" в Object Inspector. В TStringProperty не переопределен. Метод SetValue(Text: string). Должен устанавливать значение свойства в переданную строку. В TStringProperty переопределен. Этот метод вызывается самим Object Inspector, когда пользователь вводит значение поля. Вы можете переопределить этот метод для установки вашего свойства в зависимости от значения, введенного пользователем. Если вы обнаруживаете ошибку в переданном параметре - вызовите исключение. Метод GetAttributes: TPropertyAttributes. Задает параметры свойства. Рассмотрим их по порядку. paValueList - указывает, что редактор свойств возвращает список допустимых значений свойства через метод GetValues. В редакторе свойств рядом со свойством появляется раскрывающийся список paSortList - указывает, что список, возвращенный GetValues нужно сортировать paSubProperties - указывает, что у свойства имеются подсвойства (типа подсвойства Name у свойства Font класса TFont). Подсвойства, если этот флаг установлен, должны возвращаться методом GetProperties. paDialog - указывает, что рядом со свойством должна быть кнопка "…", по нажатию которой вызывается метод Edit для редактирования значения свойства. Что мы и указали в нашем примере. paMultiSelect - Разрешает отображать свойство в Object Inspector, даже если выделено более одного объекта paAutoUpdate - указывает, что метод SetValue нужно вызывать при каждом изменении значения свойства, а не после нажатия Enter или выхода из Object Inspector (Пример: свойство Caption у формы изменяется одновременно с набором на клавиатуре) paReadOnly - указывает, что значение через Object Inspector изменить нельзя. Оно устанавливается в классе TClassProperty, от которого унаследованы все классовые редакторы свойств типа TStrings, TFont и т.п. При установке рядом со значением свойства отображается строка, возвращенная методом GetValue и значение это изменить нельзя. paRevertable - указывает, изменение значения свойства можно отменить. Это не касается вложенных подсвойств. paFullWidthName - указывает Object Inspector, что прорисовка значения свойства не требуется и можно занять под имя свойства всю длину панели. paVolatileSubProperties - установка этого значения указывает, что при любом изменении свойства нужно повторить сборку подсвойств (GetProperties) paVCL - ??? paReference - указывает, что свойство является указателем на что-либо. Используется вместе с paSubProperties для указазания отображения объекта, на которое ссылается в качестве подсвойств (TFont). paNotNestable - указывает, что отображать значение свойства в момент, когда его подсвойства развернуты - небезопасно (этот пункт мне пока непонятен) Методы GetXXXValue и SetXXXValue. Используются для внутренней установки реального значения свойства. Как правило, используются методом GetValue и SetValue. В принципе, все эти методы уже определены в классе TPropertyEditor, и переопределять их не нужно. Метод Modified вызывается для указания того факта, что значение свойства изменено. Это метод уже определен в TPropertyEditor и переопределять его не требуется. Метод GetEditValue возвращает true, если значение можно редактировать Метод GetIsDefault возвращает true, если значение свойства в текущий момент является значением свойства по умолчанию. Т.е. метод должен возвращать true, если НЕ нужно сохранять значение свойства в .dfm файле. Метод Activate вызывается при выборе свойства в Object Inspector. При использовании переопределения этого метода для отображения значения свойства исключительно в момент активизации нужно быть осторожным, если указаны параметры свойства paSubProperties и paMultiSelect. Метод AllEqual вызывается всякий раз, когда выделяется более одного компонента. Если этот метод вернет true, будет вызван метод GetValue, в противоположном случае будет отображена пустая строка. Вызывается только, если указано свойство paMultiSelect. Очевидно, метод должен проверять совпадение свойств у все выбранных компонентов путем опроса методе GetComponent. Метод AutoFill вызывается для определения, могут ли элементы списка быть выбраны по возрастанию. Указывается, только если указан параметр paValueList. Метод GetComponent возвращает компонент с заданным индексом из выбранных компонентов. Метод GetEditLimit возвращает максимальное количество символов, которые можно ввести в текстовое значение свойства. По умолчанию 255. Метод GetName возвращает имя свойства, в котором знаки подчеркивания заменены на пробелы. Метод должен переопределяться только, если свойство не предназначено для отображения в Object Inspector Метод GetComponentValue возвращает значение свойства типа TComponent в том и только в том случае, если свойство унаследовано от TComponent. Этот метод переопределяется в классе TComponentEditor Метод GetProperties вызывается для каждого подсвойства, которое редактируется. В метод передается параметр типа TGetPropertyProc. Это указатель на процедуру для обработки каждого свойства. Например, TClassProperty вызывает процедуру TGetPropertyProc для каждого published элемента класса, а TSetProperty - для каждого элемента множества. Т.е. при использовании подсвойств вы должны определить процедуру TGetPropertyProc, чтобы она определяла каждое подсвойство. Метод GetPropType возвращает указатель на информацию о типе редактируемого свойства (TypeInfo (Type)) Метод GetValue возвращает значение свойства в виде текстовой строки. Например, в TClassProperty этот метод переопределен для возвращения в качестве результата имени типа класса (TStrings и т.п.). Метод ValueAvailable возвращает true, если можно получить доступ к значению свойства, не вызывая исключения.
Описания для остальных методов и свойств, к сожалению, найти не удалось, поэтому исследовать их можно только опытным путем.
По завершении создания редактора свойств не забудьте зарегистрировать его внутри метода register вызовом RegisterPropertyEditor(TypeInfo(), , , ); RegisterPropertyEditor(TypeInfo(TMyComponentsStringProperty), TMyComponent, '', TMCSPEditor);
Передав вместо имени свойства пустую строку, мы указали тем самым, что имя может быть любым. Так же пустую строку можно передать вместо имени компонента.
Вот, собственно, и все. Пишите свой редактор свойств, переопределяйте нужные методы и вперед!
Создание собственной среды разработки
До настоящего момента были показаны приёмы непосредственной работы с элементами-потомками TParticulControl. Эти приёмы, как уже упоминалось, годятся для проектирования таких систем, работающих только в режиме редактирования (яркий пример - САПРы). Но во многих системах редактируемые элементы используются как в "DesignTime", так и в "RunTime" (Word, HTML-редакторы и пр.). Ясно, что данный подход для них неприемлем.
Данная проблема решена путём использования приёмов агрегации и установления прозрачности. Как уже было сказано ранее, TParticulControl имеет наследника TExternalControl, специально предназначенного для этой цели. Свойство ExternalObject элемента TExternalControl позволяет редактировать любые объекты (а не только компоненты!). Если объект не является наследником TControl, то он отображается, как в Delphi, квадратиком. TExternalControl располагается под редактируемым объектом, если тот является наследником TWinControl, в рабочем режиме, и над ним - в режиме редактирования. Для того, чтобы обработать какой-либо объект, необходимо создать класс-потомок от TExternalControl для перекрытия методов GetTypeName, GetParticuls и SetParticul. Однако в отличие от TParticulControl в потомке TExternalControl методы служат для обработки свойств, полей, методов и событий не самого себя, а объекта ExternalObject. Перед использованием нового элемента управления необходимо присвоить его свойству ExternalObject редактируемый объект.
TExternalControl прозрачен, поэтому, в случае обработки потомка TControl будет отрисовываться именно потомок. В случае редактирования наследника от TWinControl необходимо "менять их местами" в режимах редактирования (TExternalControl.BringToFront) и рабочем (TExternal.SendToBack) для получения фокуса. В противном случае, невозможно будет добраться до нижнего. В примере Example3 показано, как создать собственную среду разработки. Показано, как можно редактировать потомка TWinControl (TEdit внедрён в TParticulEdit), TControl (TImage в TParticulImage) и TObject (TParticulSample).
Также в примере показана обработка событий. Процедуры-обработчики для последних могут находится как на "несущей" форме, так и "внутри" объектов. Обработка событий идентична обработке перечислений (Enum): создаётся список-"перевод" на русский всех возможных процедур-обработчиков и выдаётся в виде списка, из которого пользователь выбирает нужную. Поскольку процедуры нельзя сравнивать (if OnEnter = SelfEnter then ...), то текущие процедуры отслеживаются путём присвоения их русских имён специальным полям в объекте (например, поля StrEnter, StrExit, StrMouseMove в TParticulEdit). Также в примере показано, как обрабатывать данные типа TBitmap через свойство Handle.
Создание таблицы пользовательской базы данных
Для этого служит операция по кнопке NewT.
Работа платформы состоит в следующем, определяется текущая категория информации, которая занесена в свойство Tag в виде числового выражения ссылки на структуру категории информации TInfoCategory: wTabSheet := FPageControl.ActivePage; wpTInfoCategory := pTInfoCategory(Pointer(wTabSheet.Tag)); В глобальные переменные заносятся соответствующие сведения: apDbType := wpTInfoCategory.sTFbDbType; apDbTypeS := wpTInfoCategory.sEnumName; Создается диалог формирования структуры таблицы и в него передается информация о категории информации, для которой нужно создать таблицу: if TbDlgFr = nil then TbDlgFr := TTbDlgFr.Create(nil); try TbDlgFr.DbInterface := FDbInterface; TbDlgFr.ppTInfoCategory := wpTInfoCategory; TbDlgFr.ShowModal; finally FreeAndNil(TbDlgFr); end;
Если после выхода из этого диалога имеется ненулевая ссылка на буферную структуру FDbInterface.N_pTTableInfo, то производится как обновление отображения главной формы, так и обновление системной базы данных в процедуре Update_Server(). Разумеется, на SQL сервере создается соответствующая таблица пользователя. Суть обновления системной базы данных состоит в том, что в таблицу T_Tables заносятся сведения о вновь созданной таблице, а в таблицу T_Fields – информация о ее полях.
Создании серверной части обработки документа
Как было отмечено ранее, обработка HTTP запроса может осуществляться либо CGI-приложениями, либо Java-сервлетами. Возможен и вариант написания ASP-страниц. Но в этом случае передача данных возможна только методом "GET" через строку запроса. Хотя, обработка HTTP запроса ASP-страниц работает более эффективнее, чем CGI-приложением. Однако, на мой взгляд, без разницы, как обрабатывать, а важнее решить вопрос - как построить программму обработки, а не какими средствами.
Если из предыдущей главы мы рассмотрели варианты формирования XML-документ, то задача серверного приложения обратная - разбор XML-документов. Ниже представлена часть программы, осуществляющей разбор xml-документа:
procedure Tthread1.DataParser(Sender: Tobject); var r,FNode : IXMLDOMElement; // объявление объектов DOMElement Str,Filename : String; parm : String; CoDocXML, CoDocXSL, CoDocResult : CoDomDocument ; // объявление сокласса и XMLDoc, XSLDoc, ResultDoc : DomDocument ; // объекта XMLDomDocument // HttpStr : String; - глобальная переменная, содержащая строку HTTP запроса Begin XMLDoc:=coDocXML.Create; // создание документа XMLDoc XMLDoc.Set_async(false); // установка синхронного режима обрабработки XMLDoc.LoadXML(HttpStr); // загрузка DOM документа из строки HttpStr r:=Doc.Get_documentElement; // получение адреса корневого элемента FNode:= r.SelectSingleNode("//TypeDocument"); // получение значения элемента FileName:= FNode.GetAttibute("id"); // получение значения аттрибута id="Order" FileName:= FileName+".xsl"; // и формирование имени файла Order.xsl XSLDoc:=coDocXSL.Create; // создание документа XSLDoc XSLDoc.Set_async(false); // установка синхронного режима обрабработки XSLDoc.LoadXML(FileName); // загрузка DOM документа из файла Order.xsl ResultDoc:=coDocResult.Create; // создание документа XMLDoc ResultDoc.Set_async(false); // установка синхронного режима обрабработки ResultDoc.validateOnParse := true; // установка проверки разбора XMLDoc.transformNodeToObject(XSLDoc, ResultDoc); // разбор XMLDoc по XSL-шаблону Str:= ResultDoc.text; // переменной Str присваивается текстовое значение // результирующего документа. FNode:= r.SelectSingleNode("//InvoiceNumber"); // поиск элемента parm:= FNode.text; // и получение значения элемента Query.Close; // закрывает запрос для доступа Query.Text := Str; Query.Params[0].AsString := parm; // присваивание значения параметра Query.ExecSQL; // выполнение запроса end;
Вся изюминка разбора заключается в применении XSL-шаблона, который сформирован для каждого типа документа индивидуально. Результатом разбора является строка SQL-запроса. Впоследствие выполнение сформированной строки SQL-запроса осуществит необходимые изменения данных в СУБД.
Приимущество использования разбора через щаблон еще и в том, что получается некая гибкость данных, и получается полная независимость работы алгоритма от программного кода. Ниже приведен используемый для обработки документа типа ORDER текст XSL-шаблона:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/TR/WD-xsl"> <xsl:template match="/"> <xsl:for-each select="//header"> INSERT into TABREG ( FROM, TO, TYPEDOC,body) VALUES( ' <xsl:value-of select="from" />' ,' <xsl:value-of select="to" />','<xsl:value-of select="TypeDocument/@id" />' ) xsl:for-each> <xsl:for-each select="//item"> INSERT into GOODS ( invoiceNumber, name, price, quality) VALUES( ' :num', '<xsl:value-of select="name" />' , '<xsl:value-of select="price" />', '<xsl:value-of select="quality" /> ' ) xsl:for-each> xsl:template> xsl:stylesheet>
Поясняя вышеприведенный пример, надо отметить, что использование пары тагов и носит формальный характер, т.к. после разбора в результирующем XML-документе формально должен присутствовать хотябы один узел. Метод ResultDoc.text присваивает текстовае значение полученного в ходе разбора XML-документа ResultDoc. В этом случае значением является все то, что обрамлено пары тегов и , т.е. сформированный нами SQL-запрос.
Другой особенностью написания программы надо отметить возможность использования SQL-параметра :num. Использование параметра позволяет упростить текст xsl-шаблона. Определение значение соответствующих элементов узлов XML-документа определякется первоночально выбора по имени соответствующего узла, например:
FNode:= r.SelectSingleNode("//InvoiceNumber"); // поиск элемента >InvoiceNumber> и далее использование свойства text: parm:= FNode.text; // и получение значения элемента >InvoiceNumber>
Список использованной литературы.
Platform SDK Release: August 2002.
Алексей Павлов Специально для
Moscow Power Engineering Institute (Technical University) Faculty of Nuclear Power Plants 21.10.02
Скачать (886 K)
var StartupInfo: TStartupInfo; ProcessInformation: TProcessInformation;
//… var StartupInfo: TStartupInfo; ProcessInformation: TProcessInformation; begin GetStartupInfo(StartupInfo); with StartupInfo do begin wShowWindow := SW_HIDE; //не показывать окно dwFlags := STARTF_USESHOWWINDOW; end; // для примера будем запускать [c:\program files\Borland\Delphi5\Bin]grep.exe с ключом '?' Win32Check(CreateProcess(nil, 'command.com /c grep.exe ? > MyStdOut.txt', nil, nil, FALSE, CREATE_NEW_CONSOLE, nil, nil, StartupInfo, ProcessInformation)); // ждем пока наш процесс отработает WaitForSingleObject(ProcInfo.hProcess, INFINITE); Win32Check(CloseHandle(ProcInfo.hProcess); end;
//… var ProcInfo: TProcessInformation; StartupInfo: TStartupInfo; hOut, hOutDup: THandle; begin // Создаем файл в который и будем переназначать StdOut // Например, с такими настройками, вы можете их изменить под свои нужды hOut := CreateFile('MyStdOut.txt', GENERIC_WRITE, 0, nil, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0); if (hOut = INVALID_HANDLE_VALUE) then RaiseLastWin32Error; А вот в этом месте и происходит все самое важное!!! Необходимо сделать рукоятку нашего файла НАСЛЕДУЕМОЙ, что и делаем… Win32Check(DuplicateHandle(GetCurrentProcess, hOut, GetCurrentProcess, @hOutDup, 0, TRUE, DUPLICATE_SAME_ACCESS)); Небольшое замечание Следует отметить, что если вы пишите прогу ТОЛЬКО под NT/2000, то сделать рукоятку наследуемой можно проще: Win32Check(SetHandleInformation (hOut, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT); и не надо будет заводить дубликат рукоятки hOutDup
// эта рукоятка нам уже не нужна, хотя вы можете ее использовать для своих целей Win32Check(CloseHandle(hOut)); GetStartupInfo(StartupInfo); with StartupInfo do begin wShowWindow := SW_HIDE; // не показывать окно dwFlags := dwFlags or STARTF_USESHOWWINDOW or STARTF_USESTDHANDLES; hStdOutput := hOutDup; // присваиваем рукоятку на свой файл end; Для примера будем запускать [c:\program files\Borland\Delphi5\Bin]grep.exe с ключом '?' Вызов CreateProcess с флагом bInheritHandles = TRUE !!! Win32Check(CreateProcess(nil, 'grep.exe ?', nil, nil, TRUE, CREATE_NEW_CONSOLE, nil, nil, StartupInfo, ProcInfo)); // ждем пока наш процесс отработает WaitForSingleObject(ProcInfo.hProcess, INFINITE); Win32Check(CloseHandle(ProcInfo.hProcess)); //если вы больше ничего не хотите делать с файлом, в который перенаправили StdOut, то закроем его Win32Check(CloseHandle(hOutDup)); end;
Этот способ мне показал Юрий Зотов (поместив его в разделе "
Ссылки
подготовлен для Delphi5. где есть приличный компонент микшер.
Стандартные средства
Как известно, компонент TTimer основан на User таймерах, предоставляемых Win32API. Это просто VCL-оболочка, удобная для использования "на форме" системных таймеров данного типа. Для их работы требуется окно, поскольку программа получает извещение о срабатывании таймера посредством сообщения WM_TIMER. Без участия VCL для работы с ними необходим обработчик сообщения WM_TIMER и функции API: CreateTimer SetTimer KillTimer
Да чем же они плохи, эти User таймеры? А вот чем! Недостаток 1.
Как известно, сообщения в Win32 обрабатываются в следующем порядке очередности: синхронные сообщения (от SendMessage) асинхронные сообщения (от PostMessage) завершение приложения или WM_QUIT (от PostQuitMessage) асинхронный ввод (мышь, клавиатура) требование прорисовки или WM_PAINT извещения от User таймеров или WM_TIMER Таким образом, сообщение WM_TIMER имеет наименьший приоритет и принимается только когда в очереди потока нет других сообщений. Это значит, что своевременному получению "тика" от User таймера может помешать как длительная обработка синхронных и асинхронных сообщений, так и реакция на интенсивный ввод пользователя, и даже активная перерисовка окон. Недостаток 2. Кроме того, повторные сообщения WM_TIMER от того же таймера уничтожаются, если в очереди еще есть необработанное такое же. Все это приводит к потере тиков таймера, в результате программа получает меньше вызовов обработчика таймера на заданном интервале, чем рассчитывал программист. Недостаток 3. Нельзя сделать так, чтобы моменты срабатывания были привязаны к системному времени (были синхронны с ним), т.е. чтобы таймер тикал в определенные часы, минуты, секунды. Он обязательно будет "уплывать". Недостаток 4. О срабатывании таймера User уведомляется только один поток (тот, который вызвал SetTimer), поэтому невозможно пробудить по таймеру сразу несколько потоков. Недостаток 5. Дискретность временного интервала оставляет желать лучшего. В Win9x она составляет 55 мс. Но даже в NT интервал квантуется не менее чем по 10 мс.
Stateful и stateless объекты
Как и обычные COM объекты, MTS объекты могут поддерживать свое внутренне сoстояние во время обращения к ним со стороны множества клиентов. Такие компоненты называют stateful. Но с другой стороны, MTS objects могут быть неустойчивыми (stateless), что означает, что объект не сохраняет свое внутреннее состояние (определяемое глобальными перемененными или полями в самом объекте) между обращениями к нему со стороны клиента. Напомним, что только Single модель гарантирует то, что компонент будет сохранять свое состояние между вызовами клиента. Т.е. только в этом случае вы можете использовать для доступа к базам данных компоненты Delphi, просто помещая их на модуль данных. Следует иметь в виду, что такая модель работает значительно медленнее, и stateless объекты являются предпочтительнее. При использовании любой потоковой модели значения локальных переменных не будут изменяться другими потоками (thread) во время их взаимодействия компонента с клиентом. Эту возможность и следует использовать, чтобы реализовать stateful свойство компонента. Например, для при работе с базами данных вы должны динамически создавать компоненты для доступа к ним ( TQuery, TADOQuery и т.д.) как локальные переменные в ваших методах, например: | function TMastCon.GetLZString (const RecID: Integer; const ConnectionString: WideString): String; var dsGetBlob: TADODataset; begin Result := ''; // Initialize string result to blank dsGetBlob := TADODataset.Create(nil); {Create TADODataset dynamically} try { Assign the ConnectionString w/c points to the DB } dsGetBlob.ConnectionString := ConnectionString; dsGetBlob.CommandText := 'select DocImage, RecID from _userblobs '+ 'where (RecID = :RecID) and (DocType=''LZ'')'; dsGetBlob.Parameters[0].Value := RecID; . . . . . . finally dsGetBlob.Free; //Release memory allocated to TADODataset end; end; | В этом случае каждый поток (thread) будет использовать свой собственный локальный компонент ADODataset и они не будет мешать друг другу. А вот для уменьшения количества соединений к базе данных, можно использовать глобальный компонент TADOConnection (естественно, если все нити будут использовать одну и ту же БД с одними и теми же правами доступа). ет, что читатель легко сможет сделать соответствующие изменения в коде.
StdIn, StdOut и StdErr. Перенаправление, чтение и запись.
рбань С.В., дата публикации 23 декабря 2002г. |
Вообще - я программист молодой, стаж - всего 2 года. И я никак не ожидал, что в век GDI мне придется возится с консолью... Ан нет, пришлось.
Начал писать "движок" для собственного сайта. А именно - "Apache 1.x shared module" (dll - линкуется к Апачу и обрабатывает определенные адреса).
Написал. Всего три сотни строк. НО умеет кучу всяких полезностей, типа вставлять на страницы данные из файлов (файл в файл), строки и, главное, данные из БД. Все это прекрасно. НО не умеет вставлять результаты работы других файлов (типа как CGI). Ну, думаю, надо сделать. Ага, а как? Вот тут то все и началось...
Итак, ЗАДАЧА: запустить процесс (некий файл), передать ему команды и получить от него результаты работы. Вставить полученные результаты на страницу сайта. Причем в целях совместимости механизмы передачи данных ДОЛЖНЫ быть стандартными - StdIn, StdOut, StdErr. Поискал на КД. Нашел вот такую штуку:
Хорошая статья, но мне-то НЕ в ФАЙЛ, а в ПРОГРАММУ надо! ми :-)). Кому надо - тот сам вставит. (Вот так и рождается "кривой" код. Типа сейчас лень, потом добавлю... Ага... Через час уже забудешь!!!) В общем - перехожу таки к технике дела.
Для передачи данных используются "безымянные" (Anonymus) "каналы" (Pipes). Чтобы заставить программу писать в (читать из) канал (а) - просто подменяем соответствующие Std(In, Out, Err). Программа и знать не будет, что ее данные уходят в "трубу" а не на реальную консоль. При создании каналов есть одна ВАЖНАЯ особенность. Создаем-то мы их в своем процессе (Parent) а использовать будем и в дочернем. (Учтите! дочерний процесс НЕ будет знать, что использует КАНАЛ! НО будет его использовать...). Так, вот, чтобы дочерний процесс мог нормально работать - хэндлы канала должны быть НАСЛЕДУЕМЫМИ. Чтобы это обеспечить - надо правильно заполнить структуру SECURITY_ATTRIBUTES используемую при вызове CreatePipe: | New(FsaAttr); FsaAttr.nLength:=SizeOf(SECURITY_ATTRIBUTES); FsaAttr.bInheritHandle:=True; FsaAttr.lpSecurityDescriptor:=Nil; | Заполнили? Молодцы! Теперь создаем каналы (я делаю только два, StdErr мне не нужен):
| If not CreatePipe(FChildStdoutRd, FChildStdoutWr, FsaAttr, 0) Then //Создаем "читальный" Pipe raise ECreatePipeErr.CreateRes(@sCreatePipeMsg) Else If not CreatePipe(FChildStdinRd, FChildStdinWr, FsaAttr, 0) Then //Создаем "писальный" Pipe raise ECreatePipeErr.CreateRes(@sCreatePipeMsg) | Создали? Если нет - то дальше ловить нечего, поэтому генерим Exception'ы... Есть еще одна тонкость. У нас Все созданные хэндлы наследуемые! А дочернему процессу понадобятся только два... Поэтому:
| //Делаем НЕ наследуемые дубликаты //Это нужно, чтобы не тащить лишние хэндлы в дочерний процесс... If not DuplicateHandle(GetCurrentProcess(), FChildStdoutRd, GetCurrentProcess(), @Tmp1, 0, False, DUPLICATE_SAME_ACCESS) Then raise EDuplicateHandleErr.CreateRes(@sDuplicateHandleMsg) Else If not DuplicateHandle(GetCurrentProcess(), FChildStdinWr, GetCurrentProcess(), @Tmp2, 0, False, DUPLICATE_SAME_ACCESS) Then raise EDuplicateHandleErr.CreateRes(@sDuplicateHandleMsg) | Дубликаты у нас в Tmp1 и Tmp2, теперь:
| CloseHandle(FChildStdoutRd);//Закроем наследуемый вариант "Читального" хэндла CloseHandle(FChildStdinWr); //Закроем наследуемый вариант "Писального" хэндла FChildStdoutRd:=Tmp1; //И воткнем их места НЕ наследуемые дубликаты FChildStdinWr:=Tmp2; //И воткнем их места НЕ наследуемые дубликаты | Ура! Теперь можем создавать дочерний процесс!
| If not CreateChildProcess(ExeName, CommadLine, FChildStdinRd, FChildStdoutWr) Then //Наконец-то! Создаем дочерний процесс! raise ECreateChildProcessErr.CreateRes(@sCreateChildProcessMsg) | Причем CreateChildProcess - это не API - это моя функция! Вот она:
| //************************************************************************* function TChildProc.CreateChildProcess(ExeName, CommadLine: String; StdIn, StdOut: THandle): Boolean; Var piProcInfo: TProcessInformation; siStartInfo: TStartupInfo; begin // Set up members of STARTUPINFO structure. ZeroMemory(@siStartInfo, SizeOf(TStartupInfo)); siStartInfo.cb:=SizeOf(TStartupInfo); siStartInfo.hStdInput:=StdIn; siStartInfo.hStdOutput:=StdOut; siStartInfo.dwFlags:=STARTF_USESTDHANDLES; // Create the child process. Result:=CreateProcess(Nil, PChar(ExeName+' '+CommadLine), // command line Nil, // process security attributes Nil, // primary thread security attributes TRUE, // handles are inherited 0, // creation flags Nil, // use parent's environment Nil, // use parent's current directory siStartInfo, // STARTUPINFO pointer piProcInfo); // receives PROCESS_INFORMATION end; //************************************************************************* | Здесь важное значение имеют вот эти строчки: siStartInfo.hStdInput:=StdIn; siStartInfo.hStdOutput:=StdOut; siStartInfo.dwFlags:=STARTF_USESTDHANDLES; Первые две - понятно. А третья - читайте Хелп! Там все написано...
Самые умные (то есть те, кто ухитрился дочитать до этого места :-))) спросят:
- Ну, создали мы процесс и что дальше?
А дальше - мы можем с ентим процессом общаться! Например вот так:
| //************************************************************************* function TChildProc.WriteToChild(Data: String; Timeout: Integer=1000): Boolean; Var dwWritten, BufSize: DWORD; chBuf: PChar; begin //Обратите внимание на Chr($0D)+Chr($0A)!!! Без них - будет работать с ошибками //На досуге - подумайте почему... //Для тех, кому думать лень - подскажу - это пара символов конца строки. //(вообще-то можно обойтись одним, но так надежнее, программы-то бывают разные) chBuf:=PChar(Data+Chr($0D)+Chr($0A)); BufSize:=Length(chBuf); Result:=WriteFile(FChildStdinWr, chBuf^, BufSize, dwWritten, Nil); Result:=Result and (BufSize = dwWritten); end; //************************************************************************* | Это мы посылаем данные на StdIn процесса.
Читать - несколько сложнее. Нам же не надо вешать всю нашу программу только потому, что процесс не желает нам ничего сообщать??? А ReadFile - функция синхронная и висит - пока не прочитает! Если заранее известно, чего и сколько ДОЛЖЕН выдать процесс, то еще ничего... А если нет?
А если нет - делаем хитрый финт ушами :-) Есть у Мелко-Мягких такая ф-ия PeekNamedPipe. Не покупайтесь, на то, что она "Named" - фигня! Она прекрасно работает а анонимными пайпами! (кто не верит - можете почитать хелп)
Поэтому делаем так:
| //************************************************************************* function TChildProc.ReadStrFromChild(Timeout: Integer): String; Var i: Integer; dwRead, BufSize, DesBufSize: DWORD; chBuf: PChar; Res: Boolean; begin Try BufSize:=0; New(chBuf); Repeat For i:=0 to 9 do begin Res:=PeekNamedPipe(FChildStdoutRd, nil, 0, nil, @DesBufSize, nil); Res:=Res and (DesBufSize > 0); If Res Then Break; Sleep(Round(Timeout/10)); end; If Res Then begin If DesBufSize > BufSize Then begin FreeMem(chBuf); GetMem(chBuf, DesBufSize); BufSize:=DesBufSize; end; Res:=ReadFile(FChildStdoutRd, chBuf^, BufSize, dwRead, Nil); Result:=Result+LeftStr(chBuf, dwRead); end; Until not Res; Except Result:='Read Err'; End; end; //************************************************************************* | Ну, вот, как я и говорил - работает. Даже слишком хорошо. Как я и говорил - эта вся бодяга для Web сервера. Ну, беру я в качестве файла - format.exe.... Ндаааа....
Если честно - с format'ом я не прверял - а вот help c парметрами и "net use" прошли на ура! Так что пришлось резко думать, как ограничить список разрешенных для запуска программ....
В общем, кому лень разбираться - вот вам исходники модуля с готовым классом. А вот пример его использования:
| /************************************************************************* With TChildProc.Create(ReadIni(TagParams.Values['file'], FPage), TagParams.Values['cmd']) do Try WriteToChild(TagParams.Text); ReplaceText:=ReadStrFromChild; Finally Free; End; //************************************************************************* | Не правда ли просто?
Скачать : Исходный код проекта (6 K) Исполняемый код (пример) (207 K)
Свойства COM компонента
Получение свойств компонента производится аналогичным образом и в дополнительных комментариях не нуждается. | function GetComponentProperties(ApplicationName, ComponentName: String; Properties: Tstrings): boolean; var MainCatalog : ICOMAdminCatalog; Apps : ICatalogCollection; App : ICatalogObject; Comps : ICatalogCollection; comp : ICatalogObject; props : ICatalogCollection; Prop : ICatalogObject; Appscount : integer; i,j,k : integer; compsCount : integer; propsCount : integer; propValue : Olevariant; stringPropValue : string; begin try MainCatalog := CoCOMAdminCatalog.Create; Apps := MainCatalog.GetCollection('Applications') as ICatalogCollection; Apps.Populate; Appscount := Apps.Count; for i := 0 to AppsCount -1 do begin App := ICatalogObject(Apps.Item[i]); if App.Name = ApplicationName then begin comps := ICatalogCollection(Apps.GetCollection('Components', app.Key)); comps.Populate; compsCount := comps.Count; for j := 0 to compsCount - 1 do begin comp := ICatalogObject(Comps.Item[j]); if comp.Name = ComponentName then begin props :=ICatalogCollection(comps.GetCollection('PropertyInfo',comp.Key)); props.Populate; propsCount := Props.Count; Properties.Text :=''; for k := 0 to propsCount-1 do begin prop := ICatalogObject(Props.Item [k]); propValue := (Comp.Value[prop.Name]); case VarType (PropValue) of varBoolean : if propValue = true then stringPropvalue := 'Y' else stringPropvalue := 'N'; else stringPropValue := string(PropValue); end; if prop.name = 'Transaction' then case integer(PropValue) of COMAdminTransactionIgnored : stringPropValue := 'Ignored'; COMAdminTransactionNone : stringPropValue := 'None '; COMAdminTransactionSupported : stringPropValue := 'Supported'; COMAdminTransactionRequired : stringPropValue := 'Required '; COMAdminTransactionRequiresNew : stringPropValue :='Requires New'; end; if prop.name = 'ThreadingModel' then case integer(PropValue) of COMAdminThreadingModelApartment :stringPropValue := 'Apartment'; COMAdminThreadingModelFree :stringPropValue := 'Free'; COMAdminThreadingModelMain :stringPropValue := 'Main'; COMAdminThreadingModelBoth :stringPropValue := 'Both'; COMAdminThreadingModelNeutral :stringPropValue := 'Neutral'; COMAdminThreadingModelNotSpecified :stringPropValue := 'Not Specified'; end; if prop.name = 'Synchronization' then case integer(PropValue) of COMAdminSynchronizationIgnored :stringPropValue := 'Ignored'; COMAdminSynchronizationNone :stringPropValue := 'None'; COMAdminSynchronizationSupported :stringPropValue := 'Supported'; COMAdminSynchronizationRequired :stringPropValue := 'Required'; COMAdminSynchronizationRequiresNew :stringPropValue := 'Requires New'; end; Properties.Add(prop.name + ' = '+ stringPropValue); end; end end end end; result := true; except result := false end end; |
Свойства COM пакета
Получение свойств пакета также не представляет проблемы, поскольку хранятся они в виде коллекций. Для доступа к его свойствам можно воспользоваться свойством Value, которое возвращает вариант. Следует иметь в виду, что в том случае, если свойство доступно только для записи (например, Password), то обращение к нему приведет к возникновению исключительной ситуации. Для того чтобы избежать этого, можно воспользоваться свойством isPropertyWriteOnly, которое показывает, что это свойство только для записи. Поскольку часть свойств возвращается как перечислимый тип (1,2 ..) то для удобства пользователя их значения преобразуются в текстовой вид. Надеюсь, что дополнительные пояснения к данному фрагменту кода не требуются.
| function GetApplicationProperties(ApplicationName: String; Properties: TStrings): boolean; var MainCat : ICOMAdminCatalog; Apps : ICatalogCollection; App : ICatalogObject; props : ICatalogCollection; Prop : ICatalogObject; Appscount : integer; i,j : integer; propsCount : integer; propValue : Olevariant; stringPropValue : string; begin try MainCat := CoCOMAdminCatalog.Create; Apps := MainCat.GetCollection('Applications') as ICatalogCollection; Apps.Populate; Appscount := Apps.Count; for i := 0 to AppsCount -1 do begin App := ICatalogObject(Apps.Item[i]); if App.Name = ApplicationName then begin //show properties props := ICatalogCollection(Apps.GetCollection( 'PropertyInfo',App.Key)); props.Populate; propsCount := Props.Count; Properties.text :=''; for j := 0 to propsCount-1 do begin prop := ICatalogObject(Props.Item [j]); if not prop.IsPropertyWriteOnly then // you can't read it! begin propValue := (App.Value[prop.Name]); //Get property case VarType (PropValue) of varBoolean : // for Boolean properties if propValue = true then stringPropvalue := 'Y' else stringPropvalue := 'N'; else stringPropValue := string(PropValue); end; // Enumerated properties if prop.name = 'Authentication' then case integer(PropValue) of COMAdminAuthenticationDefault : stringPropvalue := 'Default'; COMAdminAuthenticationNone : stringPropvalue := 'None'; COMAdminAuthenticationConnect : stringPropvalue := 'Connect'; COMAdminAuthenticationCall : stringPropvalue := 'Call'; COMAdminAuthenticationPacket : stringPropvalue := 'Packet'; COMAdminAuthenticationIntegrity : stringPropvalue := 'Packet Integrity'; COMAdminAuthenticationPrivacy : stringPropvalue := 'Packet Privacy'; end; if prop.name = 'ImpersonationLevel' then case integer(PropValue) of COMAdminImpersonationAnonymous : stringPropvalue := 'Anonymous'; COMAdminImpersonationIdentify : stringPropvalue := 'Identify'; COMAdminImpersonationImpersonate : stringPropvalue := 'Impersonate'; COMAdminImpersonationDelegate : stringPropvalue := 'Delegate'; end; Properties.Add(prop.name + ' = '+ stringPropValue); // Add to list end end; end end; result := true; except result := false; end end; |
Таймер, который не подведет
| Раздел Подземелье Магов | н, дата публикации 18 июля 2001г. |
Мысль о хорошем таймере давно волнует умы программистов. Сразу оговорюсь, что речь не идет о прецизионном, "высокочастотном" иструменте отсчета интервалов времени, с дискретностью 1 мс и менее, как иногда хочется. Для этого существуют иные методы и/или иные операционные системы. Здесь же будет построен просто надежный таймер общего назначения, который "тикнет" вовремя, во что бы то ни стало. Реализация в пределах стандартных возможностей Win32API, т.е. ничего "военного". Плюс одна интересная идея, заимствованная из мира Unix.
Тактико-технические характеристики
Подсистема виртуальных таймеров (или Таймерный менеджер). Предоставляет любое количество программных объектов для отсчета времени, независимых от загрузки системы приложениями и работой пользователя, следующих типов: интервальный таймер (одновибратор/мультивибратор); точность - 10 миллисекунд; управление: пуск, останов, задание периода и режима. синхронизированный таймер (будильник), привязан к системному времени; набор моментов срабатывания конфигурируется строкой в формате , позволяющем простым способом описывать сложные периодические события; дискретность настройки - от секунды до месяца; управление: пуск, останов, задание маски времени и режима. Реализовано все это в виде DLL - для возможности использования не только в программах на Delphi. Впрочем, можно использовать Subj просто как библиотеку классов - модуль Timers.pas. При желании можно натянуть на это дело компонентную крышу, но у меня такой необходимости не возникало. В нынешнем виде его можно использовать в программах как с формами, так и вообще без "морды", т.к. он не использует VCL. Разработано и отлажено в среде Delphi 5, но будет компилироваться и в более ранних - может понадобиться замена типа dword на что-нибудь похожее (беззнаковость здесь роли не играет). Все исходные тексты и откомпилированная DLL собраны в архив . Тестовая программа (исходные тексты) отдельно в файле Для интересующихся - сорцы версии на С++ в файле .
Текст монитора приведен ниже
| Подпрограмма ловушки ошибок встраивается в каждую прикладную программу. Ниже приведен примерный шаблон такой ловушки и пример ее использования. Вы вправе настроить шаблон под особенности своей предметной области. 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. | | Даутов Ильдар, 06 декабря 1999 |
Текст с высоты птичьего полета или Регулярные выражения
"Look for a white shirt and a white apron," said the head which had been put together, speaking in a rather faint voice. "I'm the cook." L. Frank Baum, The Emerald City of Oz При решении прикладных задач, полезно рассматривать их с высоты "птичьего полета". Многие знают что это может существенно ускорить разработку, но не многие этим пользуются.
Разница в посимвольной обработке строк и обработке с помощью регулярных выражений в том, что в первом случае Вы думаете прежде всего как достичь цели, а во втором - а какая цель Вам собственно нужна ? %-) Кроме того, посимвольные алгоритмы трудно модифицировать, не говоря уж о том, что любая модификация сопровождается перекомпиляцией приложения.
В этой небольшой статье собрано несколько иллюстраций использования регулярных выражений в Delphi.
Прим. Если для Вас приведенные примеры выражений выглядят как древнеегипетские письмена, то ознакомьтесь с описанием их синтаксиса в любой книге о Perl или на . Они гораздо проще чем кажутся ! Для компиляции этих примеров достаточно добавить в список файлов проекта и вписать 'uses regexpr;' в юниты, где Вы используете регулярные выражения.
Предположим, Вам необходимо выманить ;) у пользователя адрес его электронной почты (моральную сторону и маркетинговую обоснованность подобной затеи мы здесь рассматривать не будем).
Идея в том, что если отвергать синтаксически некорректные адреса, то большинству пользователей надоест играть в эту орлянку и они либо откажутся от Вашей программы / уйдут с web-страницы, либо введут синтаксически корректный адрес. А как рядовому юзеру проще всего ввести такой адрес ?.. Правильно ! Проще всего ввести свой реальный e-mail !
Естественно, что вариант с p := Pos ('@', email); if (p > 1) and (p < length (email)) then ... проблемы не решает. Желательно как минимум просмотреть строку на предмет отсутствия некорретных символов а также наличия домена второго (или выше) уровня. Конечно, любой программист напишет такой анализатор... строк этак на *дцать и с перспективой перекомпилировать программу если что-то не впишется в эту проверку.
А теперь забудьте о посимвольной обработке и посмотрите на этот же анализатор, упрятанный в одну строку : if ExecRegExpr ('[\w\d\-\.]+@[\w\d\-]+(\.[\w\d\-]+)+', email) then ... gotcha! ... Регулярные выражения позволяют гибко реализовать достаточно изощренные проверки. Вот, скажем абсолютно корректная проверка на ... римские цифры любой величины (шаблон позаимствован из книги "Mastering Perl"): const Mask1 = '^(?i)M*(D?C{0,3}|C[DM])(L?X{0,3}|X[LC])(V?I{0,3}|I[VX])$'; ... if not ExecRegExpr (Mask1, DBEdit1.Text) then begin ... show error message ... DBEdit1.SetFocus; end;
В последнее время появилось неимоверное число программок, вылущивающих информацию из web-страниц. Так вот, на мой взгляд это гораздо разумнее делать с помощью регулярных выражений. Не изобретайте велосипед, используйте метро ! 8-)
Например вот таким нехитрым способом можно получить курс доллара и дату этого курса программно, не рассматривая рекламные баннеры (да простят меня CityCat и ФинМаркет ;) ).
Бросьте на форму TBitBtn, TLabel и TNMHTTP (TNMHTTP здесь использован исключительно для упрощения примера. Использовать эту гадость в реальной жизни не советую :-E~ ) и вставьте такой код обработки нажатия BitBtn1: procedure TForm1.BitBtn1Click(Sender: TObject); const Template = '(?i)Официальный курс ЦБ по доллару' + '.*Дата\s*Курс\s*Курс пок.\s*Курс прод. [^<\d]*' + '(\d?\d)/(\d?\d)/(\d\d)\s*[\d.]+\s*([\d.]+)'; begin NMHTTP1.Get ('http://win.www.citycat.ru/finance/finmarket/_CBR/'); with TRegExpr.Create do try Expression := Template; if Exec (NMHTTP1.Body) then begin Label1.Caption := Format ('Курс на %s.%s.%s: %s', [Match [2], Match [1], Match [3], Match [4]]); end; finally Free; end; end; В этом примере используется очень мощный механизм backtrack, отличающий NFA (non-deterministic finite state machine) реализацию регулярных выражений от DFA (deterministic finite state machine). В случае с NFA (на базе которого построен и TRegExpr) мы получаем возможность работать с подвыражениями, что и использовано в примере выше для выделения из шаблона элементов даты и собственно курса.
Кстати, здесь уже проявляются и ограничения регулярных выражений (см. ). Решая подбную задачу, я бы предварительно обработал текст: убрал бы незначимые тэги (ИМХО для надержного анализа достаточно оставить только табличные тэги), из оставшихся тэгов убрал бы все модификаторы (size, align и т.п.), убрал бы все переводы строк, а табуляции заменил на пробелы и убрал после этого повторяющиеся пробелы. После этого можно уже написать гораздо более надежное регулярное выражение.
А вот так можно достаточно надежно вынуть из неформализованного текста все Санкт-Петербургские номера телефонов (представленные как '(812)123-4567' или '+7 (812) 12-345-67' и т.д., причем извлечены будут внутригородские части номеров): procedure ExtractPhones (const AText : string; APhones : TStrings); begin with TRegExpr.Create do try Expression := '(\+\d *)?(\((\d+)\) *)?(\d+(-\d*)*)'; if Exec (AText) then REPEAT if Match [3] = '812' then APhones.Add (Match [4]) UNTIL not ExecNext; finally Free; end; end;
Необходимо некий текст отобразить в html-странице, но предварительно желательно выделить гиперссылками все встречающиеся в нем URL. Вот пример реализации (он не всегда сработает, но ведь 100% распознавание даже теоретически невозможно, да и в такого рода задачах не страшно если что-то не будет найдено. Страшно впустую тратить время на вспомогательные по сути вещи): type TDecorateURLsFlags = ( // Включаемые в видимую часть гипер-ссылки поля durlProto, // Протокол ('ftp://' или 'http://') durlAddr, // IP-адрес или символическое имя домена durlPort, // номер порта (например ':8080') durlPath, // путь (unix-формат) durlBMark, // объект внутри страницы (напрмер '#bookmark') durlParam // параметры запроса (например '?ID=13&User=Pupkin') ); TDecorateURLsFlagSet = set of TDecorateURLsFlags; function DecorateURLs (const AText : string; AFlags : TDecorateURLsFlagSet = [durlAddr, durlPath]) : string; const URLTemplate = '(?i)' // регистро-независимый режим + '(' + '(FTP|HTTP)://' // Протокол + '|www\.)' // Позволяет отловить ссылки указанные без 'http://' + '([\w\d\-]+(\.[\w\d\-]+)+)' // IP-адрес или символическое имя домена + '(:\d\d?\d?\d?\d?)?' // номер порта + '(((/[%+\w\d\-\\\.]*)+)*)' // путь (unix-формат) + '(\?[^\s=&]+=[^\s=&]+(&[^\s=&]+=[^\s=&]+)*)?' // параметры запроса + '(#[\w\d\-%+]+)?'; // объект внутри страницы var PrevPos : integer; s, Proto, Addr, HRef : string; begin Result := ''; PrevPos := 1; with TRegExpr.Create do try Expression := URLTemplate; if Exec (AText) then REPEAT s := ''; if CompareText (Match [1], 'www.') = 0 then begin Proto := 'http://'; Addr := Match [1] + Match [3]; HRef := Proto + Match [0]; end else begin Proto := Match [1]; Addr := Match [3]; HRef := Match [0]; end; if durlProto in AFlags then s := s + Proto; // Match [1] + '://'; if durlAddr in AFlags then s := s + Addr; // Match [2]; if durlPort in AFlags then s := s + Match [5]; if durlPath in AFlags then s := s + Match [6]; if durlParam in AFlags then s := s + Match [9]; if durlBMark in AFlags then s := s + Match [11]; Result := Result + System.Copy (AText, PrevPos, MatchPos [0] - PrevPos) + '' + s + ''; PrevPos := MatchPos [0] + MatchLen [0]; UNTIL not ExecNext; Result := Result + System.Copy (AText, PrevPos, MaxInt); // Tail finally Free; end; end; { of function DecorateURLs -------------------------------} Обратите внимание, что в приведенном выше примере Вы имеете возможность легко выделять из URL протокол, домен, путь и параметры запроса (см. параметр AFlags).
Возможно, в этом месте уже не лишним будет умерить пыл энтузиастов, в особенности тех, кому случалось использовать Перл.
Дело в том, что Перл - интерпретирующий язык. Основное следствие из этого - чем меньше операторов выполняется, тем быстрее (как правило) работает программа. В большистве случаев регулярное выражение отработает быстрее чем самый элементарный посимвольный анализ строки.
Поэтому, не кажется диким реализация функции Trim как выражения '^\s*(\S*)\s*$'.
Думаю, не надо объяснять насколько это глупо в истинно компилируемом Паскале. Так что, если анализируемая строка имеет простую структуру - напишите элементарный и очень быстрый цикл по ее разбору и не связывайтесь с регулярными выражениями.
Кроме того, не рекомендую использовать регулярные выражения там, где нужен полноценный парсер. Если, например, Вам нужно разобрать на теги HTML - поищите для этого более подходящий инструмент !
Если же искомая или проверяемая строка имеет сложную структуру, если эта структура может меняться, тогда это наш клиент ;) Если же описание должно меняться без перекомпиляции программы, то серьезной альтернативы регулярным выражениям практически нет.
Успехов !
Да, чуть не забыл, библиотека которая устраняет досадную забывчивость разработчиков Delphi и позволяет использовать в Delphi регулярные выражения без необходимости таскать за собой какие-либо DLL, лежит на
или .
Андрей Сорокин Специально для
Теоретические основы изображения кривых Безье
Теорию кривых Безье разработал П. де Кастело в 1959 году и, независимо от него, П. Безье в 1962 году. Для построения кривой Безье N-ого порядка необходимо N+1 точек, две из которых определяют концы кривой, а остальные N-1 называются опорными. В компьютерной графике наибольшее распространение получили квадратичные кривые Безье, строящиеся по трём точкам, и кубические кривые Безье, строящиеся по четырём точкам. Квадратичные кривые Безье используются, например, в шрифтах TrueType при определении контуров символов. API Windows позволяет строить только кубические кривые Безье. Кубические кривые Безье задаются следующей формулой: P(t)=A*(1-t)3+3*B*t*(1-t)2+3*C*(1-t)*t2+3*D*t3, (1) где A - начало кривой, D - её конец, а B и C - первая и вторая опорные точки. Прямая AB является касательной к кривой в точке A, прямая CD - в точке D. Параметр t изменяется от 0 до 1. При t=0 P(t)=A, при t=1 P(t)=D Одним из важнейших свойств кривой Безье является её делимость. Если кривую разделить на две кривых в точке t=0.5, каждая из полученных кривых также будет являться кривой Безье. На этом свойстве основывается алгоритм рисования кривых Безье: если кривая может быть достаточно точно аппроксимирована прямой, рисуется отрезок прямой, если нет - она разбивается на две кривых Безье, к каждой из которых вновь применяется этот алгоритм. В Windows поддерживается два типа кривых: кубические кривые Безье и эллиптические дуги. В Windows 9x/Me дуги рисуются независимо от кривых Безье. В Windows NT/2000/XP дуги аппроксимируются кривыми Безье. Для рисования кривых Безье используются функции PolyBezier, PolyBezierTo и PolyDraw.
В некоторых случаях удобно строить кривую Безье не по опорным точкам, а по точкам, через которые она должна пройти. Пусть кривая начинается в точке A, при t=1/3 проходит через точку B`, при t=2/3 - через точку C`, и заканчивается в точке D. Подставляя эти точки в уравнение (1), получаем систему, связывающую B` и C` с B и C. Решая систему, получаем: B=(-5*A+18*B`-9*C`+2*D)/6 (2) C=(2*A-9*B`+18*C`-5*D)/6
Из этих уравнений, в частности, следует, что для любых четырёх точек плоскости существует, и притом единственная, кривая Безье, которая начинается в первой точке, проходит при t=1/3 через вторую точку, при t=2/3 - через третью и завершается в четвёртой точке. Аналогичным образом можно вычислить опорные точки для кривой, которая должна проходить через заданные точки при других значениях t.
Тестирование
Разумеется, в мире нет ничего абсолютного. А тем более когда дело касается столь нереалтаймовой системы как Уиндоус. Впрочем, в случае real-time OS вопрос о таймерах вообще бы не стоял. А в наших условиях вполне может найтись в системе какой-нибудь хулиганский поток с высоким приоритетом (выше или равным нашему), который наглым образом будет отбирать управление на длительное время (больше длительности внутреннего цикла таймерного менеджера - 10 мс). И тогда наш таймер будет пропускать "тики" на коротких заданных интервалах и увеличивать погрешность на длинных. Это происходит в том случае, если кто-то работает не по правилам, либо производительности процессора не хватает для работы системы. В целях проверки и демонстрации функционирования таймерного менеджера, а также сравнительного анализа со стандартным таймером была разработана демо-программа (). Сравнивались: компонент TTimer, два интервальных таймера с разными способами уведомления (сообщение окну и асинхронный вызов), один синхронизированный таймер. Подсчитывалось количество срабатываний. Каждый факт срабатывания таймера записывался в журнал (TMemo), что также играло роль полезной нагрузки в работе приложения (попросту отъедание процессорного времени). Дополнительная нагрузка по инициативе пользователя эмулировалась задержкой (sleep) в обработчике событи OnClick кнопки. Одновременно контролировалась загрузка процессора по показаниям Windows NT Task Manager. Проведенные исследования показали (см. таблицу), что интервальный таймер ведет себя почти идеально от 100 мс и достаточно хорошо на более мелких интервалах, тогда как стандартный таймер на коротких интервалах, а особенно под нагрузкой совсем сдает позиции. На интервале 10 мс интенсивная обработка извещений от таймеров (обновление контролов, особенно TMemo) приводит к 100% загрузке процессора. Синхронизированный таймер (FixedTimer), заряженный на минимальный интервал 1 с, всегда давал точное число тиков, причем срабатывал в начале секунды с небольшим разбросом. От способа уведомления количество полученных тиков не зависело. При большой нагрузке и высокой частоте приложение могло получать уведомления PostMessage неравномерно (пачками накопленных в очереди сообщений), но общее число выдерживалось, насколько это возможно. Результаты приведены для следующей конфигурации: Cyrix 6x86PR233/64M/WinNT4. Измерения проводились также на платформе Win98SE, где IntervalTimer показал примерно те же результаты, а TTimer еще более худшие.
Интервал таймера, мс Интервал измерения, мс Количество срабатываний Total CPU usage, % Идеальный таймер TTimer IntervalTimer | Нормальные условия | | 100 | 10000 | 100 | 99 | 100 | 3-12 | | 100 | 100000 | 1000 | 998 | 1000 | 3-12 | | 10 | 10000 | 1000 | 659 | 999 | 100 | | 10 | 100000 | 10000 | 6361 | 9991 | 100 | | 15 | 10000 | 667 | 482 | 667 | 44-60 | | Нагрузка приложения (задержка на 2000 мс) | | 100 | 10000 | 100 | 79 | 100 | 2-17 | | 15 | 10000 | 667 | 333 | 667 | 2-100 | | 10 | 10000 | 1000 | 156 | 999 | 2-100 | | Внешняя нагрузка (играющий Winamp) | | 100 | 10000 | 100 | 97 | 100 | 28-51 | | 15 | 10000 | 667 | 175 | 632 | 100 | | 10 | 10000 | 1000 | 22 | 894 | 100 |
Специально для Исходные тексты программ, приложенные к данной статье, распространяются на правах freeware. Все исходные тексты и откомпилированная DLL собраны в архив . Тестовая программа (исходные тексты) отдельно в файле Для интересующихся - сорцы версии на С++ в файле .
Типы данных
Некоторые задачи обеспечения целостности базы данных и адаптации ее к предметной области оказались легко решаемыми посредством ввода специальных структур для хранения в памяти информации о типах данных, причем в них содержатся не только традиционные сведения, такие, как тип поля, ее размер и т.д., но и дополнительная информация, вводимая для повышения удобства работы с ними настройщику и пользователю. К такой информации относится, прежде всего, смысловое наименование типа, понятное для пользователя, не обладающего специальными знаниями в области программирования. Так, например, программисту все ясно, когда он видит обозначение типа как ftInteger, а для пользователя лучше, когда он увидит в качестве обозначения типа фразу «Целое число». Для ряда задач оказалось удобным создать специальный набор структур, предназначенных для хранения информации о типах полей, используемых для связей между таблицами, а также типов полей для хранения данных, выбираемых пользователем из собственных списков, по существу являющихся справочниками небольшого объема. В таких структурах дополнительной информацией являются смысловые названия полей и списков. В связи с этим были введены так называемые группы данных, каждая из которых представляет собой определенный набор типов данных, имеющих общий признак, т.е. описываемых структурой одного и того же типа. Обратим внимание, что фактически все поля, с которыми манипулирует наша платформа, имеют типы, предусмотренные в BDE, и здесь ничего нового нет. Новое понятие групп данных связано с характером той дополнительной информации, которая сопровождает параметры типа поля в указанных структурах. Если эта информация сводится просто к названию типа и нет других особенностей, связанных с полем, имеющим этот тип (например, просто поле имеет тип «Целое число»), то мы будем такие поля относить к базовой группе данных, а во всех остальных случаях – к той или иной группе данных, в зависимости от характера дополнительной информации, которая важна с точки зрения назначения системы. В этих случаях уже неважно, как называется тип по канонам BDE, - эта информация в структуру даже не вводится, так как дополнительная информация, вводимая в структуру, косвенно содержит описание типа. Так, если поле предназначено для хранения информации из списков, то косвенно это означает, что тип поля ftString. Важно еще подчеркнуть, что групп данных априори может быть сколько угодно. В описываемой платформе пока таких групп реализовано четыре. Базовая группа данных Прежде всего, разработчику следует решить, какие типы данных, реализуемые BDE, он собирается использовать в своей платформе. Вряд ли целесообразно организовывать поддержку почти сорока типов, перечисленных в BDE. Следует поддерживать часто используемые типы, такие как ftString, ftInteger, ftFloat, ftDate, ftTime, ftDateTime. Для автоинкрементных полей понадобится тип ftAutoInc. Для работы с текстами, графикой и вообще с бинарными данными нужны ftBlob, ftMemo, и, возможно, ftGraphic. Дальнейшее расширение базовой группы скорее носит узкоспециализированный характер. Чтобы настройщик и пользователь могли свободно манипулировать выбранным подмножеством типов данных, целесообразно ввести структуру:
| // Структура базового типа данных TFbBaseType = record sType: TFieldType; // идентификация типа BDE sBytes, // количество байтов под данный тип sSize, // размер типа, аналог из BDE sInc: Integer; // признак включения типа в платформу sDescr: ShortString; // краткое описание типа end; | Она служит хранилищем сведений о типе поля. Поля sBytes и sSize приведенной структуры содержат соответственно число байтов, занимаемых данным типом в памяти и стандартное значение размера типа по соглашениям BDE. Ввод в структуру поля sBytes обусловлен желанием явно указать размер отводимой памяти в тех случаях, когда в BDE параметр Size равен 0. Для строковых и аналогичных ему типов этот параметр конкретизируется в момент формирования поля базы данных. Создав массив базовых типов TFbFieldArray = Array[TFieldType] of TFbBaseType, а затем для тех ее членов, в которых поле sInc=1, заполнив поля sDescr, можно создать необходимые списки для работы с типами данных в конфигураторе. Если есть желание расширить или, наоборот, сузить реализованный список используемых типов базовой группы данных, достаточно отредактировать поля sInc и sDescr. Если sInc=0 (по умолчанию), то данный тип исключается из системы, причем при этом можно не очищать поле sDescr. И наоборот, если нужно включить данный тип в систему, то нужно установить sInc=1, и, если есть необходимость, отредактировать поле sDescr.
Ссылочная группа данных Ссылочная группа данных связана с требованием обязательности автоинкрементного поля в каждой таблице базы данных. Под ссылкой на таблицу можно иметь в виду поле целого типа, в котором хранится содержимое автоинкрементного поля той таблицы, на которую создается ссылка (ведущей таблицы). Для работы с ссылочными типами данных введена структура:
| // Структура ссылочного типа данных TFbReferenceType = record sType: TFieldType; sBytes, sSize, sInc: Integer; sDescr: ShortString; spTableInfo: pTTableInfo; end; | Она отличается от ранее введенной структуры для базовой группы наличием поля spTableInfo, куда заносится ссылка на уже существующую структуру таблицы. Остальные поля этой структуры копируют информацию из структур для автоинкрементного поля, кроме sDescr и sType. В поле sDescr вписывается наименование ведущей таблицы, например, Диагнозы. Для ссылочной группы данных sType = ftInteger. Идентификаторы полей для ссылок унифицируем, назвав их T1_id, T2_id и T3_id и т.д., в какой бы новой таблице они не заводились, запретив тем самым их редактирование настройщикам и пользователям.
Следящая группа данных Следящая группа данных особых пояснений не требует, т.к. принцип работы с ней в целом аналогичен принципу работы со ссылочной группой. Особенность состоит в том, что в структуру управления, помимо ранее рассмотренного указателя на структуру таблицы, вводится указатель на структуру того поля, на которое создается ссылка (ведущее поле), а тип sType устанавливается равным типу ведущего поля:
| // Структура следящего типа данных TFbLookupType = record sType: TFieldType; sBytes, sSize, sInc: Integer; sDescr: ShortString; spFieldInfo: pTFieldInfo; spTableInfo: pTTableInfo; end; | Приведенные три группы данных исчерпывают основные варианты. Но оказалось, что есть возможности по использованию механизма управления типами для создания полей, значения которых выбираются из определенных пользователем списков. Так появляется еще одна группа данных: списочная.
Списочная группа данных Простейший список может состоять из значений Да, Нет. Списков аналогичного свойства, содержащих от двух до десятка и более членов, из которых пользователь может выбирать значение, любая предметная область содержит во множестве. Так, очень удобно реализовывать такими списками справочники небольшого объема. Примером может быть справочник частей света из 6 членов: Европа, Азия, Америка, Африка, Австралия и Антарктида. Другой пример - набор специальностей медицинского персонала, и т.д. Структура для работы со списочными типами, по аналогии с остальными, имеет вид:
| // Структура списочного типа данных TFbPickType = record sType: TFieldType; sBytes, sSize, sInc: Integer; sDescr: ShortString; sPickList: TStrings; end; | Как видно, ключевым полем в этой структуре является список значений sPickList, из которого пользователь выбирает нужные ему значения во время работы приложения. Работа с базой данных таких "справочников", фактически начинающаяся со стадии конфигурирования, позволяет повысить гибкость настройки системы под конкретные требования функциональных задач. Разумеется, для создания списка и управления им придется создать менеджер списочных типов. Он должен обеспечивать создание списка, добавление в список нового члена, редактирование и удаление членов списка. Созданный список сохраняется в системной базе данных.
Для централизованного управления программным кодом в процессе использования групп данных целесообразно ввести обобщенную структуру. Предвидя это, мы намеренно вводили для всех групп данных схожие структуры для работы с ними в памяти. Сначала введем комбинированный тип для групп данных: TFbTypeGroup = (FldGroup, RefGroup, PicGroup, LUpGroup, NoGroup) соответственно идентифицирующий базовую, ссылочную, списочную и следящую группы. Для полноты в него введен тип NoGroup, не содержащий никакой группы.
Получаем структуру обобщенного (комбинированного) типа данных в виде вариантной записи:
| // Структура комбинированного типа данных TFbCommonType = packed record FbTypeGroup: TFbTypeGroup; case TFbTypeGroup of FldGroup: (FbFld: pTFbBaseType); RefGroup: (FbRef: pTFbReferenceType); PicGroup: (FbPic: pTFbPickType); LUpGroup: (FbLUp: pTFbLookupType); NoGroup: (); end; | В ней pTFbBaseType, pTFbReferenceType, pTFbPickType и pTFbLookupType - суть ссылки на структуры TFbBaseType, TFbReferenceType, TFbPickType и TFbLookupType соответственно. Итак, мы получили открытый механизм управления группами данных в конструкторе, который позволяет вводить в систему новые группы данных. Разумеется ничто не дается даром, - каждая новая группа данных потребует собственного менеджера, который бы обеспечивал интерфейс для работы с ним в конфигураторе системы. Кроме того, необходим способ связи конкретных структур полей и только что введенных структур групп данных для использования в конструкторе, а также во всех библиотечных функциях и процедурах, обрабатывающих структуры полей в пользовательском режиме.
Модернизируем структуру поля, приведенную выше (см. раздел ):
| // Структура поля TFieldInfo = record sFieldAttr: TStrings; // атрибуты поля: { sFieldName - Имя поля } { sMTableName - Имя ведущей таблицы } { sMFieldName - Имя ведущего поля } { sPicDescr - Имя списочного типа } { sFieldCaption - Наименование } { sFieldDescr - Описание } sFieldType: TFieldType; sFieldSize: Integer; sFieldMBytes: Integer; sMTTableInfo : pTTableInfo; // Ссылка на структуру главной таблицы sMTFieldInfo : pTFieldInfo; // Ссылка на структуру главного поля sPickList : TStrings; // Список списочного типа end; | Поясним смысл новых полей в структуре: sMTableName - имя ведущей таблицы, на которую задается ссылка; sMFieldName - имя ведущего поля, на которое задается ссылка; sPicDescr - строковое наименование списка значений. sMTTableInfo, sMTFieldInfo, sPickList - реальные ссылки на вудущую таблицу, ведущее поле и список значений поля соответственно, которые вводятся после загрузки приложения. Правила использования данных полей таковы. Если поле базы данных принадлежит к ссылочной группе, то sMTableName содержит наименование ведущей таблицы (sMTTableInfo <> nil). В этом случае структура поля не должна содержать атрибутов sMFieldName и sPicDescr (sMTFieldInfo = nil, sPickList = nil), а ее имя должно быть sMTableName_id. Если поле базы данных принадлежит к следящей группе, то должны быть заданы оба атрибута, как sMTableName, так и sMFieldName (sMTTableInfo <> nil, sMTFieldInfo <> nil), а имя поля должно быть sMTableName_sMFieldName. Структура такого поля не должна содержать атрибута sPicDescr (sPickList = nil). Если поле принадлежит к списочной группе, то должны отсутствовать атрибуты sMTableName и sMFieldName (sMTTableInfo = nil, sMTFieldInfo = nil), но должно быть задано имя списка sPicDescr (sPickList <> nil). Соответственно, все списки в системе должны быть с уникальными именами. Наконец, в структуре поля базовой группы не должно быть ни одного из атрибутов sMTableName, sMFieldName и sPicDescr (sMTTableInfo = nil, sMTFieldInfo = nil, sPickList = nil).
Приведенные правила позволяют хранить необходимую информацию о типах полей в системной базе данных, а при загрузке приложения создавать в памяти необходимые списки структур TFbCommonType. Это - ключевая задача, которая обеспечивает возможность работать с типами полей в платформе в режиме конфигуратора, а в пользовательском режиме - реализовать ссылки и списки значений.
После такого утомительного экскурса в дебри построения платформы, вернемся к листингу L2, чтобы пояснить как работает диалог с полями-переменными FpTFbCommonType : pTFbCommonType; FTFbTypeGroup : TfbTypeGroup.
В момент передачи диалогу ссылки на интерфейс к базам данных в обработчике Set_FDbInterface производится заполнение списка групп данных TypeGroupCmBox.
При выборе какого-либо конкретного элемента из этого списка по событию TypeGroupCmBoxChange будет заполнен соответствующий список типов данных TypesComboBox, из которого пользователь может выбрать конкретный тип поля, формируемого диалогом.
После открытия диалога формирования поля необходимо выбрать группу данных, а затем из списка типов данных этой группы выбрать конкретное значение типа. Подчеркнем еще раз, что в ссылочной и следящей группах данных информация появляется только при наличии в базе данных таблиц.
При выходе из диалога по кнопке ОК выполняется метод Execute, в котором задаются атрибуты буферной структуры поля FDbInterface.N_pTFieldInfo, созданной до входа в данный дилог.
В заключение раздела, касающегося создания таблицы, приведем листинг диалога, в котором производится формирование списка полей для таблицы. Особенность данного диалога состоит в том, что при входе в него автоматически создается начальная структура поля для автоинкрементного поля.
Листинг L3. , приведенного в листинге L3.
Траектории
API Windows реализует поддержку специфических объектов, называемых траекториями (path). Траектория представляет собой запись движения пера и состоит из одного или нескольких замкнутых контуров. Каждый контур состоит из отрезков прямых и кривых Безье. Для построения траектории в Windows NT/2000/XP могут быть использованы все графические функции рисования прямых, кривых и замкнутых контуров, а также функции вывода текста (в этом случае замкнутые контуры будут совпадать с контурами символов). В Windows 9x/Me могут быть использованы только функции рисования прямых, ломаных, многоугольников (за исключением PolyDraw и Rectangle), кривых Безье и функций вывода текста. Для создания траектории используются функции BeginPath и EndPath. Все вызовы графических функций, расположенные между BeginPath и EndPath, вместо вывода в контекст устройства будут создавать в нём траекторию. После того как траектория построена, её можно отобразить или преобразовать. Мы не будем здесь перечислять все возможные операции с траекториями, остановимся только на преобразовании траектории в ломаную. Как уже отмечалось выше, все контуры траектории представляют собой набор отрезков прямых и кривых Безье. С другой стороны, при построении кривой Безье она аппроксимируется ломаной. Следовательно, вся траектория может быть аппроксимирована набором отрезков прямой. Функция FlattenPath преобразует кривые Безье, входящие в состав траектории, в ломаные линии. Таким образом, после вызова этой функции траектория будет состоять из отрезков прямой. Отметим также некоторые другие преобразования траектории, полезные для создания графических редакторов и подобных им программ. Функция PathToRegion позволяет преобразовать траекторию в регион. Это может понадобиться, в частности, при определении, попадает ли курсор мыши в область объекта, представляемого сложной фигурой. Функция WidenPath превращает каждый контур траектории в два контура - внутренний и внешний. Расстояние между ними определяется толщиной текущего пера. Таким образом, траектория как бы утолщается. После преобразования утолщённой траектории в регион можно определять, попадает ли курсор мыши на кривую с учётом погрешности, определяемой толщиной пера. Поучить информацию о точках текущей траектории можно с помощью функции GetPath. Для каждой точки траектории эта функция возвращает координаты и тип точки (начальная линии, замыкающая точка отрезка, точка кривой Безье, конец контура). Таким образом, создав траекторию из кривой Безье (BeginPath/PolyBezier/EndPath), мы можем преобразовать эту траекторию в ломаную (FlattenPath), а затем получить координаты узлов этой ломаной (GetPath).
Требования к MTS объектам
В дополнение к обычным требованиям, предъявляемым COM к компонентам, MTS требует, чтобы компоненты находились внутри DLL.
Кроме того, существуют следующие требования, которые Мастера Delphi выполняют автоматически: При создании компонента он должен использовать стандартную фабрику классов (class factory), создаваемую. Компонент должен предоставлять доступ к входящим в него класс объектам (class object) с помощью стандартного метода DllGetClassObject. Все интерфейсы и классы (coclasses) должны быть описаны в библиотеке типов (type library), которая создается мастером и все методы и свойства должны создаваться с помощью Редактора библиотеки типов (Type Library editor). Компоненты должны поддерживать стандартный маршалинг (COM marshaling), который используется мастеров создания компонентов. Все интерфейсы должны быть дуальными (dual interface), что позволяет COM осуществлять автоматическую поддержку маршалинга. Компоненты должны поддерживать автоматическую регистрацию с помощью функции DllRegisterServer. Компоненты, выполняемые под управлением MTS не должны агрегатировать (aggregate) другие компоненты, которые выполняются вне MTS
Удаление поля в пользовательской таблице
Реализуется по кнопке DeleteF главной формы конфигуратора. В данном случае поступают по следующей схеме. Сначала из системной таблицы удаляется информация о выбранном поле в процедуре RemoveFrom_T_Fields(FDbInterface, FpTFieldInfo);
Затем удаляется информация из списка FieldsLBox на главной форме и, наконец, пользуясь методом FDbInterface.DeleteField(FpTTableInfo.sTableAttr.Values['sTableName'], FpTFieldInfo.sFieldAttr.Values['sFieldName']) удаляют структуру поля в памяти и обновляют структуру таблицы на сервере базы данных. В заключение производится обновление списков типов данных FDbInterface.Update_FbCommonTypeList.
(Продолжение следует) Скачать пример: Исходные коды (51K) Backup базы (1.2M)
Николай Озниев
Удаление пользовательской таблицы
Реализуется по кнопке DeleteT главной формы конфигуратора. Сначала определяется ссылка на выбранную структуру таблицы через свойство Tag активной страницы объекта FPageControl, а затем вся работа выполняется в процедуре Delete_Table, в которую передается ссылка на структуру удаляемой таблицы. В этой процедуре сначала удаляется информация о таблице из системных таблиц T_Tables и T_Fields с помощью соответствующих SQL-запросов. Затем удаляется из памяти структура поля вызовом специально для этого предназначенного метода интерфейса к базам данных: FDbInterface.Dispose_pTTableInfo(ApTTableInfo, True, True);
Здесь второй параметр указывает на необходимость удаления списка структур полей, а третий – на необходимость обновления информации о типах. Последнее действие необходимо в связи с тем, что при удалении любой таблицы происходит удаление одной записи в списке ссылочной группы данных и удаление из списка следящей группы данных стольких записей, сколько было полей в удаляемой таблице, не считая автоинкрементного поля.
Установка компонента в пакет
Выполнить эту операцию можно с помощью метода InstallComponent интерфейса ICOMAdminCatalog ComAdminCatalog.InstallComponent('COMTest', 'D:\users\Tranning\COM+\Capital.dll', '',''); где в первый параметр - имя пакета, в который будет устанавливаться компонент, второй - имя компонента (файла) который будет устанавливаться, следующий параметр - имя файла с библиотекой типов (в том случае, если она встроена в главный файл, то его можно опустить) и имя proxy-stub .dll (если не используется, то его так же опускают).
Установка MapX в Delphi
После установки дистрибутива MapX на компьютер нужно установить MapX в Delphi. Следующие шаги устанавливают МарХ в Delphi package. Это необходимо сделать только один раз. Откройте Delphi с новым, пустым проектом. Выберите Import ActiveX Control из меню Components Выберите MapInfo MapX V5 из списка, и нажмите Install.
В диалоге Install, установите его в по умолчанию в пакете программ Borland User's Components. Нажмите Yes чтобы перекомпилировать пакет программ (package), затем закройте и сохраните окно Package. Пиктограмма МарХ должна появиться в Controls palette, в разделе ActiveX.
Визуальный компонент инспектора
В этом разделе мы рассмотрим визуальный компонент инспектора, его основные методы и события, а также некоторые пользовательские аспекты, какие, как хинты. Причем, для простоты опустим аспекты реализации и, кроме того, будем использовать понятия "инспектор" и "визуальный компонент инспектора" как синонимы. Как это принято в Delphi, визуальный компонент представлен в двух формах - как TGsvCustomObjectInspectorGrid и, соответственно, TGsvObjectInspectorGrid. Опуская детали реализации и не очень важные свойства, класс инспектора определяется так:
| type TGsvCustomObjectInspectorGrid = class(TCustomControl) protected property LongTextHintTime: Cardinal; property LongEditHintTime: Cardinal; property AutoSelect: Boolean; property HideReadOnly: Boolean; property OnEnumProperties: TGsvObjectInspectorEnumPropertiesEvent; property OnGetStringValue: TGsvObjectInspectorGetStringValueEvent; property OnSetStringValue: TGsvObjectInspectorSetStringValueEvent; property OnGetIntegerValue: TGsvObjectInspectorGetIntegerValueEvent; property OnSetIntegerValue: TGsvObjectInspectorSetIntegerValueEvent; property OnFillList: TGsvObjectInspectorFillListEvent; property OnShowDialog: TGsvObjectInspectorShowDialogEvent; property OnHelp: TGsvObjectInspectorInfoEvent; property OnHint: TGsvObjectInspectorInfoEvent; public procedure NewObject; procedure Clear; procedure ExpandAll; procedure CollapseAll; end; | Вначале отметим самые простые свойства и методы: AutoSelect - если AutoSelect установить в True, то при выборе свойства, доступного для редактирования весь его текст будет выделяться, HideReadOnly - если установить в True, то инспектор будет скрывать все свойства, доступные только по чтению, Clear - вызов этого метода очистит инспектор, что означает отсутствие инспектируемого объекта, ExpandAll - раскрыть все вложенные веточки дерева свойств, CollapseAll - свернуть все вложенные веточки. Цикл событий инспектора при инспектировании начинается с вызова метода NewObject. Это приведет к тому, что инспектор начнет циклически вызывать событие OnEnumProperties. Сигнатура обработчика этого события следующая:
| TGsvObjectInspectorEnumPropertiesEvent = procedure(Sender: TObject; Index: Integer; out Info: PGsvObjectInspectorPropertyInfo) of object; | Обработчику передается монотонно увеличивающееся значение Index и, при каждом обращении, обработчик должен вернуть в out-аргументе указатель на метаданные очередного свойства или nil, если все свойства перечислены. Обработчик может выглядеть так:
| procedure TForm1.OnEnumProperties(Sender: TObject; Index: Integer; out Info: PGsvObjectInspectorPropertyInfo); begin Info := ObjectManager.PropertyInfo(Index); end; | То есть, запрос на очередное свойство просто передается менеджеру. После того, как все свойства перечислены, инспектор начинает отображение имен свойств и их значений. При этом, для доступа к значениям свойств он вызывает один из обработчиков OnGetStringValue или OnGetIntegerValue в зависимости от того, имеет ли значение свойства текстовое представление или графическое (например, значения boolean-свойств отображаются как CheckBox и не имеют текста). Обработчики этих событий также выглядят очень просто, например:
| procedure TForm1.OnGetStringValue(Sender: TObject; Info: PGsvObjectInspectorPropertyInfo; out Value: String); begin try Value := ObjectManager.GetStringValue(Info); except on E: Exception do StatusMessage('Error: ' + E.Message); end; end; | Это общий принцип - обработчик просто перенаправляет запрос менеджеру, который обрабатывает его сам, или, в свою очередь, перенаправляет метаклассам. Если пользователь изменяет значение свойства, то формируется событие OnSetStringValue (или OnSetIntegerValue). Если пользователь нажимает кнопку выпадающего списка, то формируется событие OnFillList, и после заполнения списка, инспектор отображает его. Если нажимается кнопка диалога (обозначаемого, как и в Delphi, тремя точками), формируется событие OnShowDialog. При выборе нового свойства формируется событие OnHint, которое можно обработать, например, так:
| procedure TForm1.OnHint(Sender: TObject; Info: PGsvObjectInspectorPropertyInfo); begin if Assigned(Info) then StatusBar.SimpleText := Info^.Hint; end; | то есть, просто вывести строку хинта из метаданных в статусную строку или в специальное окно подсказок. Хинт может быть весьма длинным, чтобы ясно изложить подсказку по свойству. Это облегчает работу пользователя при большом числе объектов и их свойств. Если пользователь нажимает клавишу F1, то формируется событие OnHelp, по которому программа вызывает справочную подсистему. Всплывающие подсказки (tooltips) используются в инспекторе для других целей, а именно, для отображения длинных имен и значений, которые не вмещаются в поля инспектора, например: Контролирует такие подсказки свойство LongTextHintTime - его значение определяет время, в течении которого "длинная" всплывающая подсказка будет отображаться. Если этому свойству присвоить 0, то подсказка отображаться не будет. Другой тип всплывающей подсказки связан с редактированием значений, текст которых не помещается в поле редактирования, например:
При отображении всплывающей подсказки редактирования курсор мыши приобретает вид стрелки вверх и перемещается на область подсказки, чтобы не мешать редактированию. Контролируются подсказки редактирования свойством LongEditHintTime аналогично LongTextHintTime.
Вместо вступления
Поводом для написания данной статьи является большое количество вопросов на Круглом столе по поводу реализации Инспектора, его русификации и т. п. В свою очередь я разрабатывал расчётную программу (что-то вроде мини-САПРа), в которой наиболее красиво выглядела бы реализация Инспектора (примерно как в SolidWorks. Именно из этой САПР я подчерпнул эту идею).
Сразу оговорюсь, что рассматриваемый далее Инспектор объектов правильнее было бы назвать "Псевдоинспектором", поскольку никакого отношения к реальным published-свойствам объектов он ни имеет. Он реализован без использования RTTI, в дебрях которого я, если честно, не очень разбираюсь. Инспекторами объектов в том или ином виде обладают многие системы (Delphi, C++Builder, SolidWorks, Visual Basic и т. д.). В своём Инспекторе я хотел бы иметь некоторые усовершенствования, отличающие его от вышеперечисленных.
Принцип работы моего Инспектора - обмен данными с объектом в виде кодированных строк. Строки как вид представления данных выбраны по двум причинам: во-первых, строки удобно отображать в Инспекторе (поначалу я хотел организовать обмен указателями, но возник ряд проблем с их преобразованием), во-вторых, в модулях Delphi есть много процедур кодирования данных в строки и наоборот (IntToStr-StrToInt, BoolToStr-StrToBool, ColorToString-StringToColor и др.). Плюс к этому реализованы ещё некоторые возможности, которых нет в других инспекторах. Так, например, в Инспекторе в добавление к свойствам и событиям реализованы ещё и методы (это было необходимо в моей задаче); появилось разрешение/запрещение свойств, событий и методов; появилась возможность добавлять свои редакторы не только в виде окон. Также ясно, что Инспектор будет обрабатывать только элементы управления особого вида. Более того, это должны быть потомки TWinControl, поскольку в процессе их редактирования необходимо будет как-то выделять редактируемый элемент, и лучше будет, чтобы при этом он получал фокус. Подозреваю, что в Delphi так оно и сделано, ведь даже невидимый компонент в DesignTime фактически отображается на редактируемой форме в виде элемента управления (квадратик такой небольшой). Эти элементы управления для моего Инспектора будут передавать в него кодированные свойства, события или параметры методов и обрабатывать их при изменении.
Каждое свойство, метод или событие должно помимо кодированных данных обладать другими дополнительными данными, как то имя, отображаемое в Инспекторе, булева переменная, показывающая, разрешено оно или запрещено и т. д. (далее я подробно на этом остановлюсь).
Вопросы реализации.
Вот собственно то, что я хотел сказать о технологии "многозвенного программирования". Теперь практическая часть. Как можно реализовать такую технологию? Один из наиболее используемых вариантов - это встраивание в программу интерпретатора проблемно-ориентированного языка. Встраивание компилятора я встречал значительно реже. В своей программистской практике я использовал оба варианта. Достоинства интерпретатора очевидны, но также очевидны и недостатки. Общий принцип состоит в том, что интерпретатор либо каждый раз интерпретирует исходное представление программы на проблемно-ориентированном языке, либо порождает промежуточный код, исполняемый некоторой виртуальной машиной. Примеры - пакет Microsoft Office с языком VB for Applications, реализация языка Java. Сюда же относятся все скриптовые языки. Если требуется межплатформенная совместимость, то интерпретаторы, вероятно, будут лучшим вариантом. Если же более важны соображения эффективности, то компиляция оказывается более предпочтительным выбором. Общий принцип - берем описание задачи на проблемно-ориентированном языке, генерируем соответствующий код на некотором универсальном языке, компилируем его и выполняем.
В этом разделе статьи я расскажу о втором варианте - использование компилятора, а конкретно, dcc32.exe из поставки Delphi (хотя можно использовать любой другой быстрый и качественный компилятор для любого другого подходящего языка).
Далее я рассмотрю этапы, которые нужно сделать, чтобы воплотить замыслы технолога. Конкретности реализации демонстрируется на примере небольшой библиотеки, которую прилагаю к статье (называется она DccUsing) и крошечного проекта под незатейливым именем DccExamples.
"Вшивание" информации в растровые рисунки.
В данной статье демонстрируется программа, позволяющая сохранять текстовую информацию в растровые рисунки. Суть программы состоит в следующем: берется текстовый файл и набор файлов рисунков. Далее выбирается подходящий рисунок и на его основе создается второй рисунок, несколько измененный. Причем степень изменения зависит от количества "вшиваемых" данных. При небольшом количестве информации отличить рисунки практически невозможно. А получить обратно текстовую информацию можно только лишь совместив эти два рисунка, а также имея библиотеку, реализующую алгоритм "склеивания" этих рисунков.
 Так выглядит программа в "рабочем" состоянии. А текст я взял из какого-то файла, валявшегося у меня под рукой.
Вступление
Crystal Reports (далее как CR) на сегодняшний день является лидирующим пакетом для разработки отчетности в крупных компаниях. Для доступа к отчетам компания Seagate предоставляет несколько вариантов: Элемент управления Crystal ActiveX Компонент Report Designer Component Компоненты VCL сторонних разработчиков. Automation Server Вызовы API реализуются через Report Engine API (далее RE). По моему мнению, лучшим является доступ посредством API функций, т.к.: вы полностью контролируете все, что происходит. узнаете, как все это работает. не зависите от фирмы разработчика компонент и их версий. не платите денег (хотя этот момент расплывчат J). В 90% случаев необходимо только вывести отчет и передать входящие параметры, т.е. вы получаете "тонкое" приложения на основе своих же наработок, что согласитесь, греет душу программиста. Предполагается, что читатель знаком с работой в Crystal Reports и понимает концепцию разработки отчетов в данной среде.
В настоящее время интегрированные среды программирования Borland Delphi и Borland C++ Builder являются весьма удобными средствами для разработки расширений оболочки Windows (далее Shell extensions).
В отличие от средств разработки компании Microsoft, где весь код Shell extension пишется внутри шаблона, генерируемого с помощью «Shell Extension Wizard», в Delphi вы можете использовать как подобные генераторы шаблонов, так и более быстрый и простой визуальный подход для разработки Shell extensions, например . В любом случае важным вопросом является отладка ваших Shell extensions. Эта статья была написана в помощь программистам, которые используют Borland Delphi (C++ Builder) для разработки своих Shell extensions. Она будет одинаково полезна как тем, кто использует визуальный подход, так и тем, кто пишет Shell extensions "от руки".
Введение и библиография
В журнале (№9 за 2002 г.) была опубликована статья Вячеслава Ермолаева "Использование template-классов при импортировании функций из DLL".
Уважаемый ет для решения этой задачи использовать механизм шаблонов (templates) языка С++. Это огромный шаг вперёд по сравнению с рутинным кодированием, но на мой взгляд, не идеальное решение, которому присущи некоторые недостатки: Всё разнообразие возможных функций ограничивается богатством заранее описан-ных шаблонов; и хотя в статье ли Вы припомните функций, не укладывающихся в эти рамки ?), всё же сам факт несвободы как-то стесняет Если Вы всё же где-то откопаете функцию с большим количеством параметров, Вам придётся расширить набор шаблонов, т.е. изменить библиотеку классов, что является крайне нетехнологичным решением - Вы же не изменяете исходники VCL! При кодировании пользовательского приложения приходится использовать до-вольно-таки громоздкое описание импортируемых функций в обёртках template-классов, что не способствует мнемонической лёгкости чтения и понимания текста Механизм шаблонов языка C++ позволяет уменьшить объём исходного, но никак не объектного, полученного после компиляции кода, который раздувается тем сильнее, чем более разнообразны параметры в импортируемых из DLL функциях. Кроме того, в рамках предложенного метода остаётся нерешённой проблема автоматизации довольно трудоёмкой рутинной операции - определения полных идентифика-торов функций в DLL (строковых параметров для функции GetProcAddress): Особых проблем не наблюдается, если все функции в DLL скомпилированы как "extern "C" - в этом случае линкер просто добавляет символ подчёрки-вания перед именем функции Если же DLL собрана с функциями в стиле C++, всё совсем не так одно-значно: идентификаторы могут получиться до полуметра длиной , и выковыривание их из DLL - лишняя ручная работа; можно, конечно, зная прави-ла работы линкера, синтезировать их - но и это явно предмет для автоматизации, к тому же подозреваю, что у разных средств разработки (BCB, Visual C++) раз-ные правила, по которым работает линкер
Технология.
Хотя в названии статьи тема выглядит довольно узко, я хотел бы рассказать не только об использовании dcc32, но и о технологии, которой я дал условное название "многозвенное программирование", хотя вынести это название в заголовок статьи мне показалось неправильным. Какой смысл я вкладываю в термин "многозвенное программирование"? Начну издалека. Работу над более или менее большими программами можно разделить на два крупных этапа. Первый этап - это собственно разработка, которая включает формулировку технического задания, увязку требований заказчика, проектную фазу, итеративное уточнение структуры проекта, программирование, отладку и тестирование. Первый этап заканчивается выпуском первой версии и началом эксплуатации программы у заказчика (или у массы пользователей, если программа была разработана по собственной инициативе для распространения или продажи). Затем наступает этап сопровождения, который включает устранение обнаруживаемых ошибок, адаптацию к постоянно изменяющимся требованиям заказчика, ввод дополнительных возможностей, которые не были оговорены в исходном задании. Часто бывает так - за время сопровождения программа претерпевает настолько существенные изменения, что сопровождение ее становится делом гораздо более трудоемким и хлопотным, чем разработка.
Если первый этап достаточно хорошо поддерживается разнообразным программистским инструментарием, то второй этап в этом смысле поддержан значительно хуже. Главной целью "многозвенного программирования" как раз и является поддержка этапа сопровождения. В чем основная идея этого подхода? Рассмотрим простую схему: разработчик <---> заказчик
В этой схеме заказчик использует только те функциональные возможности программы, которые предоставлены разработчиком. Для изменения этих возможностей заказчик выставляет требования разработчику, разработчик изменяет программу и возвращает заказчику. Таким образом, при интенсивном изменении требований к программе, у разработчика всегда большая загрузка, а у заказчика постоянно тормозится работа. Рассмотрим другую схему: разработчик <---> технолог <---> пользователь
В этой схеме заказчик образно разделяется на две составляющие - технолог и пользователь. Под технологом здесь понимается человек (или группа), который является посредником между разработчиком и пользователем. Технолог профессионально владеет той предметной областью, для которой разработана программа, но не является программистом - это может быть энергетик, астроном, режиссер. Причем, такое разделение заказчика может быть чисто условным - один и тот же человек может выполнять функции, как технолога, так и конечного пользователя. Технолог - это ключевое звено в цепочке. Технолог знает предметную область значительно лучше разработчика и, весьма часто, хотел бы изменить функционирование программы так, как не было предусмотрено программистом. Частые обращения к разработчику могут быть весьма затруднительными - как во времени, так и в пространстве.
Для улучшения этой ситуации можно сделать следующее - передать часть работы программиста технологу. Поскольку технолог по определению не является программистом, нужна дополнительная связующая часть. Такой связующей частью может быть проблемно-ориентированный язык, который разработчик включает в свой проект и которым технолог может воспользоваться для изменения функциональности программы (в разумных пределах). Естественно, что этот язык должен оперировать терминами той предметной области, в которой работает технолог. То есть, между формулировкой задачи и языком ее решения нужен минимальный семантический разрыв. Универсальные языки программирования на эту роль явно не подойдут. Внешней синтаксической формой проблемно-ориентированного языка может быть текст, граф, схема, короче то, на чем технолог наиболее адекватно формулирует свои конкретные задачи. Таким образом, интенсивность взаимодействия между разработчиком и технологом может быть уменьшена, так как значительную часть изменений технолог может делать самостоятельно.
Эта идея используется многими разработчиками, но в литературе я не встречал ее обсуждения как инструмента для сопровождения. В этом смысле вместо термина "многозвенное программирование" обычно используется термин "проблемно-ориентированный язык". В многозвенной структуре, которую я нарисовал выше, содержится только 2 программирующих звена, но в реальности этих звеньев может быть больше. Если предметная область достаточно разнородна, то "технолог" может быть целой цепочкой технологов - конкретный пример я приведу в самом конце статьи.
Конечно, здесь есть и другая сторона медали - маркетинговые соображения. Если заказчик получит развиваемый инструмент, то разработчик может остаться в проигрыше. И чем лучше инструмент, тем менее вероятно, что заказчик будет оплачивать сопровождение программы. Здесь уже решать разработчику - или заниматься только сопровождением старых программ или высвобождать время для новых разработок.
Введение в анимацию
Анимация заключается в последовательной смене картинок. При этом главная проблема - устранение мерцания изображения. В программе Canvas2 мерцание "резиновой" линии в целом незаметно, так как эта линия постоянно изменяет своё положение, а вот мерцание подложки, на которой нарисованы уже "впечатанные" в неё линии, было бы заметно, поэтому пришлось принимать специальные меры по её устранению. Чтобы не было мерцания при обновлении изображения, необходимо выполнение двух условий: Быстрая смена одного изображения другим Отсутствие между старым и новым изображением промежуточных полустёртых изображений
Максимальную скорость вывода при использовании средств GDI даёт вывод изображения на поверхность растра (bitmap) с последующим перенесением на экран. Этим обеспечивается то, что все элементы рисунка выводятся на экран одновременно, а не по очереди, что позволяет избежать промежуточных изображений. Поэтому программа Canvas2 хранит растр, на котором отображаются уже завершённые кривые, и при обработке события OnPaint рисует растр, а сверху - редактируемую в данный момент кривую с дополнительными элементами, облегчающими редактирование. Тем не менее, это не устраняет мерцание полностью, если для обновления окна использовать метод TWinControl.Refresh или TWinControl.Invalidate. Это связано с особенностями рисования окон в Windows и с тем, как VCL использует эти особенности. Для перерисовки сначала с помощью функций InvalidateRect или InvalidateRgn отмечается область окна, нуждающаяся в обновлении. Можно последовательно отметить несколько областей - система будет добавлять новую область к уже существующей. Затем с помощью функции UpdateWindow в очередь сообщений помещается WM_Paint. Рисование окна происходит при обработке этого сообщения. Для начала рисования вызывается функция BeginPaint. Эта функция анализирует область, нуждающуюся в обновлении, и, если при вызове функций InvalidateRect/Rgn был установлен флаг обновления фона, посылает окну сообщение WM_EraseBknd. В ответ на это сообщение окно закрашивает свою клиентскую часть заданной кистью. В частности, для форм Delphi это будет сплошная кисть с цветом, определяемым свойством Color формы. Поэтому сначала будет стёрто старое изображение, и лишь затем будет нарисовано новое. Это приводит к появлению мерцания. Существует три способа избавиться от мерцания: Не указывать флаг обновления фона при вызове InvalidateRect(Rgn) Перекрыть обработчик WM_EraseBkgnd и ничего не делать при получении этого сообщения Обновлять окно напрямую, без сообщения WM_Paint. В Delphi самым простым является третий способ: достаточно вызвать процедуру обработки OnPaint напрямую. Для реализации первого способа придётся вручную вызывать функцию API InvalidateRect, потому что TWinControl.Invalidate не позволяет сбрасывать флаг обновления фона. Второй способ не очень удобен, если анимированная картинка занимает не всё окно. Поэтому в программе Canvas2 выбран третий способ.
к адресному пространству чужого процесса.
Компонент предназначен для доступа к адресному пространству чужого процесса. Позволяет читать память процесса, записывать данные любой длины в память процесса и замораживать данные любой длины в памяти процесса. Можно работать одновременно с любым количеством запущенных на компьютере процессов. | Принцип работы компонента |
Чтобы получить доступ к процессу, компонент использует динамическую библиотеку. Устанавливается ловушка типа WH_CALLWNDPROC, которая реагирует на сообщения, посылаемые функцией SendMessage. После установки ловушки, компонент посылает сообщение WM_NULL целевому окну и, библиотека отображается на адресное пространство процесса (если не была отображена раньше), которому принадлежит целевое окно.
Эта технология подробно описывается в книге Джеффри Рихтера (как и ряд других технологий для тех же целей), поэтому особо подробно останавливаться не буду. Для того чтобы активировать компонент, предназначен метод: function Activate: Boolean; virtual; который устанавливает ловушку, создает информационный файл, отображаемый в память, в общем, делает некоторую подготовительную работу. Для того чтобы деактивировать компонент, предназначен метод: function Deactivate: Boolean; virtual; который убирает ловушку и закрывает все отображаемые в память файлы. Впрочем, активировать и деактивировать компонент, а также проследить его состояние можно, используя свойство: property Active: Boolean; Явно вызывать метод Activate не обязательно, так как при вызове методов компонента проверяется статус компонента и, если надо, производится его активация. Метод: function UpdateWndData: Boolean; virtual; получает информацию обо всех доступных в системе процессах и их окнах. После вызова этой функции становятся доступны следующие свойства: property ProcessId_: THandles; содержит дескрипторы процессов; property ProcessSize: TIntArray; содержит размеры процессов; property WndHandle: THandles; содержит дескрипторы окон процессов; property WndClassName: TStrings; содержит имена классов окон; property WndText: TStrings; содержит заголовки окон; property ModuleFileName: TStrings; содержит имена исполняемых файлов окон; Вышеперечисленные свойства представляет собой массивы данных, где любой элемент массива соответствует элементу любого другого массива с тем же индексом. Таким образом, все вышеперечисленные свойства имеют одинаковую длину, а элементы свойств с одинаковыми индексами относятся к одному и тому же процессу. Свойство компонента: property Selected: Integer; обозначает индекс выбранного элемента вышеперечисленных массивов. Это свойство можно установить исходя из, например, заголовка окна: … var Index: Integer; begin Index := 10; with MemoryInspector do if WndText[Index] = ‘Microsoft Internet Explorer’ then Selected := Index; end; … или это свойство можно установить исходя из имени класса окна: … var Index: Integer; begin Index := 10; with MemoryInspector do if WndClassName[Index] = ‘IEFrame’ then Selected := Index; end; … Зная дескриптор окна, можно получить индекс, соответствующий свойству Selected: function GetWindowIndex(Window: THandle): Integer; virtual;
Чтение памяти процесса осуществляется функцией: function PeekData: Boolean; virtual; Перед тем, как считывать данные процесса, необходимо установить некоторые свойства компонента. Первым делом, необходимо обновить информацию обо всех доступных в системе процессах и их окнах. После этого выбрать какое-нибудь окно целевого процесса и установить свойство Selected. В результате вызова функции PeekData данные записываются в поток памяти. Этот поток вы должны создать сами и установить ссылку на объект потока в свойстве компонента: property StreamRef: TMemoryStream; После того, как заданы свойства Selected и StreamRef, можно вызывать функцию PeekData. Работа функции PeekData зависит от свойства: property UpdateMemory: Boolean; Это свойство обозначает, будет ли перед считыванием памяти обновляться информация о регионах памяти и их блоках в выбранном процессе. Если это свойство истинно, то размер считываемой памяти будет, вероятно, изменяться. Когда память считывается первый раз, это свойство значение не имеет, библиотека обновляет информацию о памяти процесса в любом случае. В последующих вызовах функции PeekData можно либо заново обновить информацию (UpdateMemory = True), либо использовать ту информацию о памяти, которая была получена в первый раз (UpdateMemory = False). Работа функции PeekData также зависит от свойства компонента, которое определяет правила чтения памяти или записи в память: property ReadOptions: TReadOptions; где TProtect = (apPageReadOnly, apPageReadWrite, apPageWriteCopy, apPageExecute, apPageExecuteRead, apPageExecuteReadWrite, apPageExecuteWriteCopy, apPageNoAccess); TProtectSet = set of TProtect; определяет набор атрибутов защиты страниц памяти; TSpecial = (spPageGuard, spPageNoCache); TSpecialProtect = set of TSpecial; определяет набор специальных атрибутов защиты страниц памяти; TPageType = (ptMemImage, ptMemMapped, ptMemPrivate); TPageTypeSet = set of TPageType; определяет тип физической памяти страниц TReadOptions = record ChangeProtect: Boolean; ProhibitedProtect, PermittedProtect: TProtectSet; ProhibitedSpecialProtect: TSpecialProtect; ProhibitedPageType: TPageTypeSet; end; Описание атрибутов защиты страниц памяти:
apPageReadOnly: Разрешено только чтение страницы apPageReadWrite: Разрешены только чтение страницы и запись на странице apPageWriteCopy: Разрешена только запись на странице, которая приводит к предоставлению копии страницы, после чего этот флаг убирается apPageExecute: Разрешено только исполнение содержимого страницы apPageExecuteRead: Разрешены только чтение страницы и исполнение содержимого страницы apPageExecuteReadWrite: Нет ограничений apPageExecuteWriteCopy: Нет ограничений, любые операции приводят к предоставлению копии страницы, после чего этот флаг убирается apPageNoAccess: Нет доступа
Описание специальные атрибутов защиты страниц памяти:
spPageGuard: Попытка доступа к содержимому страницы вызывает исключение, после чего этот флаг убирается spPageNoCache: Отключает кэширование группы страниц памяти
Тип страниц регионов памяти:
ptMemImage: Указывает что страницы региона памяти отображены на EXE или DLL файл, спроецированный в память ptMemMapped: Указывает что страницы региона памяти отображены на файл данных, спроецированный в память ptMemPrivate: Указывает что страницы региона памяти отображены на страничный файл памяти
Структура TReadOptions:
Поле ChangeProtect обозначает будут ли производиться попытки получить доступ к защищенным блокам памяти. Защищенными считаются те блоки памяти, атрибуты которых не определены полями ProhibitedProtect, PermittedProtect, ProhibitedSpecialProtect и ProhibitedPageType Поле ProhibitedProtect определяет запрещенный набор атрибутов страниц памяти. Любой блок памяти, имеющий страницы с один из таких атрибутов, будет проигнорирован Поле PermittedProtect определяет разрешенный набор атрибутов страниц памяти Поле ProhibitedSpecialProtect определяет запрещенный набор специальных атрибутов страниц памяти. Любой блок памяти, имеющий страницы с один из таких атрибутов, будет проигнорирован Поле ProhibitedPageType определяет запрещенные типы страниц памяти. Любой блок памяти, имеющий страницы таких типов, будет проигнорирован
Значение свойства ReadOptions по умолчанию настроено оптимальным образом.
Как уже было сказано, вся память редактируемого процесса разбита на регионы и блоки. Когда библиотека читает память, она берет ее по кусочкам из каждого блока, а потом склеивает воедино и передает в компонент. Таким образом, каждый байт полученной памяти принадлежит какому-то блоку в том процессе, где он был взят. Так вот, если у вас есть, например, память какого-то процесса размером, скажем, в 10 мегабайт, и вы обнаружили в этой памяти нужное вам число, адрес которого 100 байт, то вы, естественно, хотите изменить его. Можно поступить несколькими способами:
Первый способ – это передать соответствующим методам записи адрес этого числа – 100 байт, то есть локальный адрес, а также новое число, на которое вы хотите изменить старое. Этот способ рассмотрим несколько позже. Второй способ – это получить блок памяти в редактируемом процессе, которому соответствует число по локальному адресу 100, и использовать соответствующие методы записи. Этот способ имеет преимущество, так как он выполняется быстрее.
Для получения блока памяти используется функция: function TMemoryInspector.GetMemoryRegion(LocalAddress: Longword): Boolean; Ее параметр LocalAddress это и есть адрес числа в нашем примере, по которому мы хотим получить соответствующий блок в редактируемом процессе. В результате вызова этой функции изменяются некоторые свойства компонента: property Beginning: Integer; Используется библиотекой и обозначает сумму размеров разрешенных блоков памяти, предшествующих полученному блоку. Значение этого свойства необходимо для некоторых методов записи. property MemoryRegion: TRegion; TRegion = record AllocationBase, BaseAddress: Pointer; AllocationProtect, Protect: TProtect; SpecialProtect: TSpecialProtect; PageState: TPageState; RegionSize: Longword; PageType: TPageType; end; Это и есть нужный нам блок памяти. Отдельные значения полей записи TRegion вряд ли вам понадобятся, так как свойство MemoryRegion нужно только для методов записи. Тем не менее, я кратко опишу эти поля:
AllocationBase: Начальный адрес региона памяти BaseAddress: Начальный адрес блока памяти AllocationProtect: Атрибуты защиты региона памяти, присвоенные ему по время резервирования Protect: Атрибуты защиты блока памяти SpecialProtect: Специальные атрибуты защиты блока памяти PageState: Состояние страниц блока памяти RegionSize: Размер блока памяти PageType: Тип физической памяти страниц блока
Все подготовительные работы для записи числа по адресу 100 завершены. Теперь можно приступать к записи: function Write(MemoryRegion: TRegion; Start, Beginning: Longword; Buffer: TShareBuffer; Length: Longword = 0): Boolean; overload; virtual; function WriteByte(MemoryRegion: TRegion; Start, Beginning: Longword; Value: Byte): Boolean; overload; virtual; function WriteWord(MemoryRegion: TRegion; Start, Beginning: Longword; Value: Word): Boolean; overload; virtual; function WriteLongword(MemoryRegion: TRegion; Start, Beginning: Longword; Value: Longword): Boolean; overload; virtual; function WriteInt64(MemoryRegion: TRegion; Start, Beginning: Longword; Value: Int64): Boolean; overload; virtual; function WriteSingle(MemoryRegion: TRegion; Start, Beginning: Longword; Value: Single): Boolean; overload; virtual; function WriteDouble(MemoryRegion: TRegion; Start, Beginning: Longword; Value: Double): Boolean; overload; virtual; function WriteExtended(MemoryRegion: TRegion; Start, Beginning: Longword; Value: Extended): Boolean; overload; virtual; function WriteString(MemoryRegion: TRegion; Start, Beginning: Longword; Value: ShortString): Boolean; overload; virtual; function WriteBuffer(MemoryRegion: TRegion; Start, Beginning: Longword; Value: Pointer; Length: Longword): Boolean; overload; virtual; function WriteBuffer(MemoryRegion: TRegion; Start, Beginning: Longword; Value: TByteArray): Boolean; overload; virtual; function WriteBuffer(MemoryRegion: TRegion; Start, Beginning: Longword; Value: string): Boolean; overload; virtual; Осталось выбрать наиболее подходящую функцию для записи. Несколько слов об общих параметрах функций. Параметры MemoryRegion и Beginning это то, о чем мы только что говорили. Параметр Start это адрес начала записи в масштабе блока памяти: Start = LocalAddress – Beginning. Базовый метод записи Write требует параметр Buffer, который имеет тип: TShareBuffer = record case Byte of 0: (ByteArray: TSmallByteArray); 1: (CharArray: TSmallCharArray); 2: (ValueRecord: TValueRecord); 3: (Float80: Extended); end; где TSmallByteArray = array[Byte] of Byte; TSmallCharArray = array[Byte] of Char; TValueRecord = record case Byte of 0: (ByteArray: array[0..7] of Byte); 1: (Signed8: Shortint); 2: (Unsigned8: Byte); 3: (Signed16: Smallint); 4: (Unsigned16: Word); 5: (Signed32: Longint); 6: (Unsigned32: Longword); 7: (Signed64: Int64); 8: (Float32: Single); 9: (Float64: Double); end; Как видно, параметром Buffer может быть представлено практически любое значение, имеющее наиболее распространенный тип и небольшой размер. Если требуется записать значение, длина которого превышает размер структуры TShareBuffer, следует использовать методы типа WriteBuffer. Такие методы могут записывать значения неограниченной длины. Параметры Value методов WriteBuffer имеют тип:
Pointer; ссылка на величину записи TByteArray = array of Byte; массив байт неограниченной длины String; длинная строка
В итоге я приведу полный код примера, в котором требуется записать в память процесса число по локальному адресу 100: … const Value: Int64 = 1000; var LocalAddress, Start: Integer; MemoryInspector: TMemoryInspector; Stream: TMemoryStream; begin MemoryInspector := TMemoryInspector.Create(Self); MemoryInspector.Parent := Self; with MemoryInspector do begin // Получаем информацию обо всех процессах и их окнах: UpdateWndData; // Выбираем самый первый процесс: Selected := 0; // Устанавливаем адрес: LocalAddress := 100; // Получаем блок памяти: GetMemoryRegion(LocalAddress); // Устанавливаем начало записи: Start := LocalAddress - Beginning; // Запись: WriteInt64(MemoryRegion, Start, Beginning, Value); // Память процесса можно загрузить в поток и сохранить в файл: Stream := TMemoryStream.Create; try StreamRef := Stream; PeekData; Stream.SaveToFile('stream.dat'); finally Stream.Free; end; end; …
Второй метод записи был только что подробно рассмотрен. Теперь пришла очередь описать первый метод, наиболее простой. Он работает немного медленнее предыдущего, но все же обладает некоторым преимуществом. Представьте себе ситуацию, вы собираетесь записать число размером, скажем 10 байт. Тот блок, в котором будет производиться запись, имеет размер, например 4096 байт, запись начинается с 4092 байта. Получается, что в регион может быть записано только 4 байта, а нужно записать 10 байт. Функции второго типа, которые были рассмотрены в предыдущей главе, в такой ситуации запишут только 4 байта из 10. Функции первого типа ведут себя иначе и в рассматриваемой ситуации сначала найдут следующий блок памяти, запишут в него неуместившиеся 6 байт, после чего запишут первые 4 байта в исходный блок памяти. Ниже приведен список функций первого типа:
function Write(LocalAddress: Longword; Buffer: TShareBuffer; Length: Longword = 0): Boolean; overload; virtual; function WriteByte(LocalAddress: Longword; Value: Byte): Boolean; overload; virtual; function WriteWord(LocalAddress: Longword; Value: Word): Boolean; overload; virtual; function WriteLongword(LocalAddress: Longword; Value: Longword): Boolean; overload; virtual; function WriteInt64(LocalAddress: Longword; Value: Int64): Boolean; overload; virtual; function WriteSingle(LocalAddress: Longword; Value: Single): Boolean; overload; virtual; function WriteDouble(LocalAddress: Longword; Value: Double): Boolean; overload; virtual; function WriteExtended(LocalAddress: Longword; Value: Extended): Boolean; overload; virtual; function WriteString(LocalAddress: Longword; Value: ShortString): Boolean; overload; virtual; function WriteBuffer(LocalAddress: Longword; Value: Pointer; Length: Longword): Boolean; overload; virtual; function WriteBuffer(LocalAddress: Longword; Value: TByteArray): Boolean; overload; virtual; function WriteBuffer(LocalAddress: Longword; Value: string): Boolean; overload; virtual;
Список этих функций соответствует уже рассмотренному списку функций, есть лишь некоторая разница лишь в параметрах. Параметр LocalAddress обозначает адрес начала записи в масштабе полученной памяти редактируемого процесса, т.е. в масштабе объекта потока памяти, на который ссылается свойство StreamRef. Я приведу код примера, который обсуждался в предыдущей главе, но применительно к рассматриваемым методам записи: … const Value: Int64 = 1000; var LocalAddress: Integer; MemoryInspector: TMemoryInspector; Stream: TMemoryStream; begin MemoryInspector := TMemoryInspector.Create(Self); MemoryInspector.Parent := Self; with MemoryInspector do begin // Получаем информацию обо всех процессах и их окнах: UpdateWndData; // Выбираем самый первый процесс: Selected := 0; // Устанавливаем адрес: LocalAddress := 100; // Запись: WriteInt64(LocalAddress, Value); // Память процесса можно загрузить в поток и сохранить в файл: Stream := TMemoryStream.Create; try StreamRef := Stream; PeekData; Stream.SaveToFile('stream.dat'); finally Stream.Free; end; end; …
Технология заморозки значений практически ничем не отличается от технологии записи на уровне блока памяти:
function Freeze(Elapse: Longword; MemoryRegion: TRegion; Start, Beginning: Longword; Buffer: TShareBuffer; Length: Longword = 0): Boolean; overload; virtual; function FreezeByte(Elapse: Longword; MemoryRegion: TRegion; Start, Beginning: Longword; Value: Byte): Boolean; virtual; function FreezeWord(Elapse: Longword; MemoryRegion: TRegion; Start, Beginning: Longword; Value: Word): Boolean; virtual; function FreezeLongword(Elapse: Longword; MemoryRegion: TRegion; Start, Beginning: Longword; Value: Longword): Boolean; virtual; function FreezeInt64(Elapse: Longword; MemoryRegion: TRegion; Start, Beginning: Longword; Value: Int64): Boolean; virtual; function FreezeSingle(Elapse: Longword; MemoryRegion: TRegion; Start, Beginning: Longword; Value: Single): Boolean; virtual; function FreezeDouble(Elapse: Longword; MemoryRegion: TRegion; Start, Beginning: Longword; Value: Double): Boolean; virtual; function FreezeExtended(Elapse: Longword; MemoryRegion: TRegion; Start, Beginning: Longword; Value: Extended): Boolean; virtual; function FreezeString(Elapse: Longword; MemoryRegion: TRegion; Start, Beginning: Longword; Value: ShortString): Boolean; virtual; function FreezeBuffer(Elapse: Longword; MemoryRegion: TRegion; Start, Beginning: Longword; Value: Pointer; Length: Longword): Boolean; overload; virtual; function FreezeBuffer(Elapse: Longword; MemoryRegion: TRegion; Start, Beginning: Longword; Value: TByteArray): Boolean; virtual; function FreezeBuffer(Elapse: Longword; MemoryRegion: TRegion; Start, Beginning: Longword; Value: string): Boolean; virtual;
Как видно, список этих функций отличается от списка функций записи только одним дополнительным параметром. Параметр Elapse обозначает частоту обновления в миллисекундах. После вызова любой из этих функций, изменяется свойство компонента, которое обозначает состояние заморозки: property Frozen: Boolean; В любой момент времени может быть заморожено только одно значение одного процесса. Для разморозки предназначена функция: function Unfreeze: Boolean; virtual; Ниже приведен код пример, который рассматривался в предыдущих главах, где вместо записи мы замораживаем значение: … const Value: Int64 = 1000; var LocalAddress, Start: Integer; MemoryInspector: TMemoryInspector; Stream: TMemoryStream; begin MemoryInspector := TMemoryInspector.Create(Self); MemoryInspector.Parent := Self; with MemoryInspector do begin // Получаем информацию обо всех процессах и их окнах: UpdateWndData; // Выбираем самый первый процесс: Selected := 0; // Устанавливаем адрес: LocalAddress := 100; // Получаем блок памяти: GetMemoryRegion(LocalAddress); // Устанавливаем начало заморозки: Start := LocalAddress - Beginning; // Заморозка с интервалом обновления 500 мс: FreezeInt64(500, MemoryRegion, Start, Beginning, Value); … // Разморозка: Unfreeze; // Память процесса можно загрузить в поток и сохранить в файл: Stream := TMemoryStream.Create; try StreamRef := Stream; PeekData; Stream.SaveToFile('stream.dat'); finally Stream.Free; end; end; …
Компонент состоит из нескольких частей. Первая часть это, собственно, сам компонент и набор необходимых вторичных компонентов. Вторая часть это используемая компонентом библиотека. Несколько слов о вторичных компонентах и файлах:
Компонент TMemoryManager предназначен для получения информации о доступных процессах и их окнах, а также для чтения памяти и записи в память Компонент TFileManager предназначен для создания файла, отображаемого в память и дальнейшей работы с таким файлом Файл MemUtils содержит некоторые общие типы и данные
Порядок установки компонента:
Установить компоненты TMemoryManager, TFileManager, файл MemUtils Получить файл Mi.dll и переместить его в директорию, где находится пакет с установленными компонентами Установить компонент TMemoryInspector
Необходимо, чтобы еще одна копия файла Mi.dll находилась в одной директории с исходными файлами программы, использующей компонент.
Скачать: (16 K) Архив содержит:
компонент TMemoryInspector компонент TMemoryManager компонент TFileManager файл MemUtils библиотеку Mi
Дополнительную информацию, а также пример по использованию этого компонента вы сможете найти на сайте .
Выравнивание текста по ширине с автоматическим переносом русских слов.
| Раздел Подземелье Магов | Антон Григорьев , дата публикации |
Программа ByWidth, написанная специально для , предназначена для демонстрации того, как средствами Delphi можно организовать просмотр текста с выравниванием по ширине и автоматическим переносом русских слов.
Краткое описание : Для переноса слов программа не использует словарь, а анализирует каждое слово с точки зрения правил русского языка. Правила эти достаточно сложны, поэтому иногда программа выбирает не самое изящное, хотя и не неправильное решение. Прежде всего это касается тех слов, в которых три или более согласных идут подряд. Программа может вставить перенос между любыми из них, хотя есть дополнительные правила, которые при этом игнорируются. Например, то, что приставка должна оставаться целой. Слишком муторно было бы заставлять компьютер учить все приставки. Да ещё и объяснять, что, например, в слове "президент" "пре" - это не приставка, а в слове "предлог" приставка не "пре", а "пред". Тогда уж лучше и в самом деле словарь. Впрочем, демонстрационная программа и есть демонстрационная программа, кто захочет, посмотрит, поймёт основные принципы и сделает лучше.
Из улучшений, которые так и напрашиваются, стоит назвать красную строку и разбивку текста на несколько абзацев. Но это делается настолько элементарно, что мне даже не хочется на этом останавливаться. Можно ввести перенос английских слов (сейчас программа их переносить не умеет, потому что я не слишком хорошо знаком с английскими правилами переноса), можно ввести запрет на перенос слов из прописных букв - возможностей для творчества хватает, было бы желание. Я же реализовал простейший вариант - при запуске программа ищет файл ByWidth.txt, мешает все слова в одну кучу, удаляет все лишние пробелы и разбивает на строки нужной длины, перенося при необходимости слова. Предполагается, что в исходном тексте нет переносов, в том числе и тех слов, которые пишутся через дефис. Таким образом, исходный текст может быть написан хоть в одну строку, хоть по одному слову на строку - разницы не будет. И можно вставлять пустые строки - они всё равно игнорируются.
Примечание: Защита от дурака в программе отсутствует, поэтому если файл ByWidth.txt не будет содержать никакого текста, никаких вежливых предупреждений не будет. Тот, кто захочет приспособить этот пример для чего-то полезного, без проблем добавит эту защиту, а загромождать демонстрацинную программу вряд ли стоит.
Вызов процедуры клиента
Асинхронный вызов процедуры (функции) клиента - один из древнейших способов взаимодействия сервера с клиентом. Это и обработчики прерываний (аппаратных и программных), это и callback-функции, используемые в Win32API, и т.д. Суть в следующем: клиент сообщает серверу адрес своей процедуры (и, возможно, некоторые параметры для ее вызова), а затем сервер в нужный момент ее вызывает. Главный недостаток - процедура выполняется в контексте вызывающего потока (сервера), и программист должен сам принять необходимые меры для синхронизации с потоком клиента, если имеются обращения к разделяемым данным. Кроме того, процедура должна выполниться достаточно быстро, чтобы не задерживать работу сервера (всяческие ожидания и SendMessage, явные и неявные, тут недопустимы). Вследствие некоторой противоречивости данных требований можно рекомендовать следующий метод работы: выполнить в асинхронной процедуре некие оперативные вычисления, а затем известить поток клиента способом, не требующим задержки (например - PostMessage), и быстренько отдать управление серверу. Асинхронный вызов метода класса (procedure of object) является разновидностью данного метода.
Взгляд в будущее
Пока эта статья создавалась, меня стали обуревать новые идеи. Потихоньку начал возникать код нового Инспектора. А идеи таковы: Возможность отключения из поля видимости тех особенностей, которые обрабатывать не нужно и которые "мешают", загромождая рабочую область Инспектора. Создание и регистрация не только своих объектов, а и своих классов. Для каждого реального обрабатываемого объекта в "DesignTime" нашей программы автоматически создавать объект-оболочку. Методы GetParticuls и SetParticul сделать свойствами процедурного типа. Это позволит не создавать специальный класс для каждого редактируемого класса. Реализовать множественное наследование, как в C++. Возможность создавать "DesignTime" не для всей программы, а для отдельного контейнера объектов. Возможность быстрой смены класса редактируемого объекта в "DesignTime" программы. Добавление собственных вкладок, помимо "Свойства", "Методы" и "События", например "Операции", "Состав", "Ссылки" и пр.
Waveform Audio Win32 API. Часть I
| Раздел | лилов дата публикации 29 апреля 2000. |
Введение
Одной из наиболее важных частей Multimedia-API Windows 95/98/NT по праву может считаться Waveform Audio. Предоставляя наиболее широкие возможности по работе с оцифрованным звуком, эта группа функций таит в себе немало "подводных камней". сил приложил к исследованию вопроса оптимального применения этих функций и хотел бы поделиться своими "открытиями" с читателями. Приводимые здесь примеры могут использоваться совершенно свободно, за исключением особо оговоренных случаев. Первая часть открывает небольшую серию статей, посвященных обработке звука в режиме реального времени (это когда время реакции системы на событие строго ограничено и не превышает заранее заданной величины - т.н. "жесткие" системы реального времени). Если Вы рассчитываете увидеть здесь разбор каких-то особенно сложных алгоритмов кодирования или сжатия звука - разочарую Вас. Далее применения быстрого преобразования Фурье пока ничего шибко математического не планируется. Обратите внимание, что вся информация почерпнута из Microsoft Multimedia Programmer's Reference, поэтому всячески рекомендую обращаться туда, тем более, что эти файлы включены в поставку Delphi 3, 4, 5. В первой части рассматривается использование функций Waveform Audio Win32 API. мнению, функций и рассматривает пример реализации программы, записывающей звук в WAV-файл в течение "неограниченного" времени. Приведенный в статье пример реализован на Delphi 4. Часть 1 Оцифрованный звук может быть представлен самыми различными способами. В числе наиболее широко применяемого способа цифрового представления звука можно отметить формат PCM - pulse code modulation - импульсно-кодовая модуляция. В контексте нашей тематики под этим термином подразумевается такой способ кодирования данных, при котором каждая выборка (отсчет), произведенная аналого-цифровым преобразователем (здесь - в смысле звуковой карты), представляется в памяти в виде числа, пропорционального по своему значению мгновенной величине сигнала в момент выборки. Скорость выборок или, другими словами, частота выполнения отсчетов (частота дискретизации), прямо связана с максимальной частотой поступающего аналогового сигнала. Если сигнал имеет гармоническую природу и ограничен в некотором диапазоне частот, т.е. может быть представлен в виде конечного числа членов ряда Фурье, то для его корректной оцифровки, согласно теореме отсчетов, достаточно иметь частоту дискретизации вдвое превосходящей частоту максимальной гармоники сигнала. Таким образом, если мы хотим без потери качества производить цифровую запись скажем, телефонного разговора, частота сигнала которого находится в диапазоне 300..3400 Гц, нам вполне достаточно установить частоту дискретизации 8000 отсчетов/сек. Величина 8000 выбрана из соображений совместимости с различными звуковыми картами и драйверами, поскольку для некоторых из них это является наименьшим возможным значением частоты дискретизации сигнала. Если же Вы хотите записывать радиопередачи в диапазоне FM (88 - 108 MHz), то необходимо выбрать частоту дискретизации 12500*2=25000 отсчетов/сек, т.к. звуковой диапазон FM-станции 12.5 килогерц. Как Вы уже наверное догадались, запись с компакт-диска для сохранения качества нужно производить с частотой дискретизации 44100 выборок/секунду. Замечу, что это вовсе не гарантирует качество звучание записи "как на CD-ROM". Звуковая карта вносит некоторые искажения в любом случае. Как правило, это напрямую связано со стоимостью карты. Более дорогие обычно обеспечивают лучшее качество. Теперь более подробно рассмотрим некоторые из функций, позволяющие работать со звуком. Прежде всего, рассмотрим функцию waveInGetNumDevs: function waveInGetNumDevs: UINT; stdcall; - функция возвращает количество устройств ввода, поддерживающих оцифровку звукового сигнала. Если функция вернула 0, то таких устройств в системе нет. Функция waveInGetDevCaps позволяет получить характеристики указанных устройств.
function waveInGetDevCaps( hwi: HWAVEIN; lpCaps: PWaveInCaps; uSize: UINT ): MMRESULT; stdcall; Здесь hwi - идентификатор открытого функцией waveInOpen (см. ниже) устройства или порядковый номер неоткрытого устройства в диапазоне от 0 до значения, возвращаемого функцией waveInGetNumDevs, уменьшенного на 1; lpCaps - адрес структуры (записи) TWAVEINCAPS; uSize - размер в байтах структуры TWAVEINCAPS.
type TWaveInCaps = record wMid: Word; wPid: Word; vDriverVersion: MMVERSION; szPname: array[0..MAXPNAMELEN-1] of AnsiChar; dwFormats: DWORD; wChannels: Word; wReserved1: Word; end;
Структура TWAVEINCAPS описывает параметры заданного устройства. Т.е. после вызова функции waveInGetDevCaps поля структуры содержат следующие значения: wMid: Word - идентификатор производителя; wPid: Word - идентификатор продукции производителя; vDriverVersion: MMVERSION - версия драйвера; szPname: array[0..MAXPNAMELEN-1] of AnsiChar - наименование продукта (строка заканчивается символом с кодом 0); dwFormats: DWORD - стандартные форматы данных, поддерживаемые устройством:
WAVE_FORMAT_1M08
|
11.025 kHz, mono, 8-bit
|
WAVE_FORMAT_1M16
|
11.025 kHz, mono, 16-bit |
WAVE_FORMAT_1S08
|
11.025 kHz, stereo, 8-bit |
WAVE_FORMAT_1S16
|
11.025 kHz, stereo, 16-bit |
WAVE_FORMAT_2M08
|
22.05 kHz, mono, 8-bit |
WAVE_FORMAT_2M16
|
22.05 kHz, mono, 16-bit |
WAVE_FORMAT_2S08
|
22.05 kHz, stereo, 8-bit |
WAVE_FORMAT_2S16
|
22.05 kHz, stereo, 16-bit |
WAVE_FORMAT_4M08
|
44.1 kHz, mono, 8-bit |
WAVE_FORMAT_4M16
|
44.1 kHz, mono, 16-bit |
WAVE_FORMAT_4S08
|
44.1 kHz, stereo, 8-bit |
WAVE_FORMAT_4S16
|
44.1 kHz, stereo, 16-bit |
Обратите внимание на то, что подавляющее большинство (если не все) звуковые карты поддерживают промежуточные режимы записи-воспроизведения. Т.е. вполне возможно на карте с максимальной частотой дискретизации 44100 выборок/сек производить запись со скоростью 16000 выборок/сек, хотя это и не сообщается по запросу waveInGetDevCaps. wChannels: Word - количество входных каналов (1-моно, 2-стерео) wReserved1: Word - зарезервировано
Функция waveInGetErrorText возвращает текстовое описание возникших в ходе выполнения ошибок.
function waveInGetErrorText( mmrError: MMRESULT; lpText: PChar; uSize: UINT ): MMRESULT; stdcall;
mmrError - код ошибки; lpText - адрес с которого будет размещена нуль-терминированная строка-описание; uSize - размер участка памяти, на который ссылается lpText;
Ниже приведен пример процедуры, выводящей сведения об устройствах аудиоввода. | uses Windows, MMSystem; type TModeDescr=record mode: DWORD; // код режима работы descr: string[32]; // словесное описание end; const // массив содержит сопоставления режима работы и словесного описания modes: array [1..12] of TModeDescr=((mode: WAVE_FORMAT_1M08; descr:'11.025 kHz, mono, 8-bit'), (mode: WAVE_FORMAT_1M16; descr:'11.025 kHz, mono, 16-bit'), (mode: WAVE_FORMAT_1S08; descr:'11.025 kHz, stereo, 8-bit'), (mode: WAVE_FORMAT_1S16; descr:'11.025 kHz, stereo, 16-bit'), (mode: WAVE_FORMAT_2M08; descr:'22.05 kHz, mono, 8-bit'), (mode: WAVE_FORMAT_2M16; descr:'22.05 kHz, mono, 16-bit'), (mode: WAVE_FORMAT_2S08; descr:'22.05 kHz, stereo, 8-bit'), (mode: WAVE_FORMAT_2S16; descr:'22.05 kHz, stereo, 16-bit'), (mode: WAVE_FORMAT_4M08; descr:'44.1 kHz, mono, 8-bit'), (mode: WAVE_FORMAT_4M16; descr:'44.1 kHz, mono, 16-bit'), (mode: WAVE_FORMAT_4S08; descr:'44.1 kHz, stereo, 8-bit'), (mode: WAVE_FORMAT_4S16; descr:'44.1 kHz, stereo, 16-bit')); procedure ShowInfo; var WaveNums, i, j: integer; WaveInCaps: TWaveInCaps; // структура в которую помещается информация об устройстве begin WaveNums:=waveInGetNumDevs; if WaveNums>0 then // если в системе есть устройства аудиоввода,то begin for i:=0 to WaveNums-1 do // получаем характеристики всех имеющихся устройств begin waveInGetDevCaps(i,@WaveInCaps,sizeof(TWaveInCaps)); // добавляем наименование устройства MainForm.Memo.Lines.Add(PChar(@WaveInCaps.szPname)); for j:=1 to High(modes) do begin // выводим поддерживаемые устройством режимы работы if (modes[j].mode and WaveInCaps.dwFormats)=modes[j].mode then Memo.Lines.Add(modes[j].descr); end; end; end; end; |  Рис 1. Сведения, выводимые процедурой ShowInfo.
Теперь Вы можете определить количество устройств аудиоввода Waveform audio и поддерживаемые ими режимы. Далее рассмотрим еще несколько функций, непосредственно обеспечивающих работу с звуковыми устройствами. Функция waveInOpen открывает имеющееся устройство ввода Waveform audio для оцифровки сигнала.
function waveInOpen( lphWaveIn: PHWAVEIN; uDeviceID: UINT; lpFormatEx: PWaveFormatEx; dwCallback, dwInstance, dwFlags: DWORD ): MMRESULT; stdcall;
Здесь lphWaveIn - указатель на идентификатор открытого Waveform audio устройства. Идентификатор используется после того, как устройство открыто, в других функциях Waveform audio; uDeviceID - номер открываемого устройства (см. waveInGetNumDevs). Это может быть также идентификатор уже открытого ранее устройства. Вы можете использовать значение WAVE_MAPPER для того, чтобы функция автоматически выбрала совместимое с требуемым форматом данных устройство; lpFormatEx - указатель на структуру типа TWaveFormatEx
type TWaveFormatEx = packed record wFormatTag: Word; { format type } nChannels: Word; { number of channels (i.e. mono, stereo, etc.) } nSamplesPerSec: DWORD; { sample rate } nAvgBytesPerSec: DWORD; { for buffer estimation } nBlockAlign: Word; { block size of data } wBitsPerSample: Word; { number of bits per sample of mono data } cbSize: Word; { the count in bytes of the size of } end;
В этой структуре значения полей следующее: wFormatTag - формат Waveform audio. Мы будем использовать значение WAVE_FORMAT_PCM (это означает импульсно-кодовая модуляция) другие возможные значения смотрите в заголовочном файле MMREG.H; nChannels - количество каналов. Обычно 1 (моно) или 2(стерео); nSamplesPerSec - частота дискретизации. Для формата PCM - в классическом смысле, т.е. количество выборок в секунду. Согласно теореме отсчетов должна вдвое превышать частоту оцифровываемого сигнала. Обычно находится в диапазоне от 8000 до 44100 выборок в секунду; nAvgBytesPerSec - средняя скорость передачи данных. Для PCM равна nSamplesPerSec*nBlockAlign; nBlockAlign - для PCM равен (nChannels*wBitsPerSample)/8; wBitsPerSample - количество бит в одной выборке. Для PCM равно 8 или 16; cbSize - равно 0. Подробности в Microsoft Multimedia Programmer's Reference; dwCallback - адрес callback-функции, идентификатор окна или потока, вызываемого при наступлении события; dwInstance - пользовательский параметр в callback-механизме. Сам по себе не используется dwFlags - флаги для открываемого устройства: | CALLBACK_EVENT | dwCallback-параметр - код сообщения (an event handle); | | CALLBACK_FUNCTION | dwCallback-параметр - адрес процедуры-обработчика; | | CALLBACK_NULL | dwCallback-параметр не используется; | | CALLBACK_THREAD | dwCallback-параметр - идентификатор потока команд; | | | CALLBACK_WINDOW | dwCallback-параметр - идентификатор окна; | | WAVE_FORMAT_DIRECT | если указан этот флаг, ACM-драйвер не выполняет преобразование данных; | | WAVE_FORMAT_QUERY | функция запрашивает устройство для определения, поддерживает ли оно указанный формат, но не открывает его; | В случае использование Callback процедуры она имеет следующий вид: procedure waveInProc(hwi: HWAVEIN; uMsg,dwInstance, dwParam1,dwParam2: DWORD);stdcall; begin // что-то делаем end; Параметры процедуры имеют следующее значение: hwi - идентификатор связанного с функцией открытого устройства; uMsg - Waveform audio сообщение. Может принимать значения: | WIM_CLOSE | посылается, когда устройство закрывается функцией waveInClose; | | WIM_DATA | устройство завершило передачу данных в блок памяти, установленный процедурой waveInAddBuffer; | | WIM_OPEN | сообщение посылается если устройство открыто функцией waveInOpen; | dwInstance - данные, определенные пользователем при вызове waveInOpen; dwParam1, dwParam2 - параметры сообщения.
Необходимо заметить, что в Microsoft Multimedia Programmer's Reference написано, что из callback-процедуры нельзя вызывать никаких системных функций кроме: EnterCriticalSection, LeaveCriticalSection, midiOutLongMsg, midiOutShortMsg, OutputDebugString, PostMessage, PostThreadMessage, SetEvent, timeGetSystemTime, timeGetTime, timeKillEvent, и timeSetEvent , поскольку это вызывает deadlock. Я столкнулся с весьма серьезным препятствием из-за этого ограничения, и решил все-таки рискнуть. В ходе небольших экспериментов я выяснил, что данное ограничение не распространяется на группу waveInAddBuffer, waveInReset и waveInClose, и возможно, некоторые другие. Не было проблем и с использованием функций Reset, BlockWrite, BlockRead, CloseFile. Говоря более точно, я так и не обнаружил возникновения deadlock, какие бы функции не вызывал изнутри waveInProc. Самое главное - не инициировать бесконечный рекурсивный вызов waveInProc. Для этого необходимо хорошо продумать обработчики поступающих в waveInProc сообщений. Вообще, рекомендую использовать механизм оконных сообщений вместо callback. Это позволяет избежать ненужных экспериментов и возможной неработоспособности программы в других версиях ОС. Более подробно реализация этого механизма приведена в примере. Функция waveInPrepareHeader выполняет подготовку буфера для операции загрузки данных: function waveInPrepareHeader( hWaveIn: HWAVEIN; lpWaveInHdr: PWaveHdr; uSize: UINT ): MMRESULT; stdcall; Здесь: hWaveIn - идентификатор открытого устройства; lpWaveInHdr - адрес структуры WaveHdr: type TWaveHdr = record lpData: PChar; { pointer to locked data buffer } dwBufferLength: DWORD; { length of data buffer } dwBytesRecorded: DWORD; { used for input only } dwUser: DWORD; { for client's use } dwFlags: DWORD; { assorted flags} dwLoops: DWORD; { loop control counter } lpNext: PWaveHdr; { reserved for driver } reserved: DWORD; { reserved for driver } end;
lpData - адрес буфера для загрузки данных; dwBufferLength - длина буфера в байтах; dwBytesRecorded - для режима загрузки данных определяет количество загруженных в буфер байт; dwUser - пользовательские данные dwFlags - флаги. Могут иметь следующие значения: | WHDR_DONE | устанавливается драйвером при завершении загрузки буфера данными; | | WHDR_PREPARED | устанавливается системой. Показывает готовность буфера к загрузке данных; | | WHDR_INQUEUE | устанавливается системой когда буфер установлен в очередь; | dwLoops - используется только при воспроизведении. При записи звука всегда 0; lpNext - зарезервировано; reserved - зарезервировано; uSize - размер структуры WaveHdr в байтах;
Функция waveInPrepareHeader вызывается только один раз для каждого устанавливаемого в очередь загрузки буфера. Существует функция waveInUnprepareHeader, с такими же параметрами, которая освобождает ресурсы системы по сопровождению выделенного под загрузку блока. waveInUnprepareHeader должна быть вызвана до удаления выделенного под буфер загрузки блока памяти. Функция waveInAddBuffer ставит в очередь на загрузку данными буфер памяти. Когда буфер заполнен, система уведомляет об этом приложение (см. выше waveInOpen). function waveInAddBuffer( hWaveIn: HWAVEIN; lpWaveInHdr: PWaveHdr; uSize: UINT ): MMRESULT; stdcall; Здесь: hWaveIn - идентификатор открытого Waveform audio устройства ввода; lpWaveInHdr - адрес структуры TWaveHdr; uSize - размер WaveHdr в байтах; Функция waveInReset останавливает операцию загрузки данных. Все текущие буферы отмечаются как обработанные и приложение уведомляется о завершении загрузки данных (см. waveInOpen). function waveInReset( hWaveIn: HWAVEIN ): MMRESULT; stdcall; Здесь: hWaveIn - идентификатор открытого Waveform audio устройства. Функция waveInClose закрывает открытое устройство ввода:
function waveInClose( hWaveIn: HWAVEIN ): MMRESULT; stdcall; hWaveIn - идентификатор открытого устройства;
MMRESULT может принимать следующие значения: | MMSYSERR_NOERROR | нет ошибок; | | MMSYSERR_ALLOCATED | указанный ресурс уже выделен; | | MMSYSERR_BADDEVICEID | указанный идентификатор устройства вне диапазона; | | MMSYSERR_NODRIVER | отсутствует драйвер устройства; | | MMSYSERR_NOMEM | невозможно выделить или зафиксировать блок памяти; | | | WAVERR_BADFORMAT | попытка открытия с неподдерживаемым форматом данных; | | MMSYSERR_INVALHANDLE | параметром является недопустимый идентификатор; | | WAVERR_STILLPLAYING | указанный буфер все еще в очереди; | | WAVERR_UNPREPARED | буфер не был подготовлен; | Пример реализации описанного в статье механизма (Delphi 3) Вы можете скачать (17.7 K)
Александр Галилов
WMI - практика применения в Delphi
Содержание: | Предисловие. Подготовка. Порядок действий. | Примеры. |
XML сериализация объекта Delphi
В статье рассмотрены возможности прямой загрузки/сохранения XML документов в объекты Delphi/С++Builder и генерации соответствующих DTD. Предлагается оптимизированный компонент для реализации этих возможностей.
Язык XML предоставляет нам чрезвычайно удобный и почти универсальный подход к хранению и передаче информации. Существует множество парсеров для разбора XML документов по модели DOM. На платформе Microsoft Windows - это, в первую очередь, парсеры MSXML от Microsoft. Парсеры взаимодействуют с вызывающими приложениями посредством интерфейса SAX (Simple API for XML) и/или DOM (Document Object Model). Во всех анализаторах, за исключением продукта фирмы Microsoft, используется SAX, и почти во всех их возможно применение DOM. Реализация парсера MSXML не плоха, поддерживает проверку семантической корректности документа и с его помощью достаточно удобно загружать небольшие XML документы. Однако для работы с каждым типом документов, реализованном на XML разработчику приходится создавать некий оберточный код для загрузки данных из объекта Microsoft.XMLDOM во внутренние структуры программы или для удобного перемещения по DOM. При изменении формата документа, что часто возможно в части расширения его спецификации, изменения созданного кода могут быть достаточно трудоемкими и требующими тщательной отладки. Возникает вопрос возможности упростить работу с XML документами, интегрировать их обработку в разрабатываемые программы. Для модели DOM наилучшим является непосредственная загрузка XML документа в объект Delphi/С++Builder. И эта возможность есть. Используя RTTI можно загружать данные непосредственно из тегов XML документа в атрибуты заданного объекта. Соответственно, становится возможным и XML-сериализация published интерфейсов объектов любых классов Delphi. Рассматриваемый подход дает возможность наиболее удобно интегрировать обработку XML в среду разработки Delphi и C++Builder. Возможность доступа к свойствам объектов определяется через механизмы RTTI. Его возможности в Delphi очень велики, т.к. среда разработки сама хранит ресурсы объектов в текстовом формате. Очевидно, что за предлагаемыми преимуществами скрываются и ряд ограничений. В первую очередь, это касается атрибутов тегов. У нас нет простых механизмов отличить атрибут от тега при сохранении свойства объекта. Поэтому в предлагаемой реализации мы будем обрабатывать XML документы, не содержащие атрибутов. Это ограничение может стать критическим только если мы хотим поддержать уже существующий тип XML документа. Если же мы разрабатываем формат сами, то вполне можем отказаться от атрибутов. Зато наш парсер будет работать не просто быстро, а очень быстро. ;)
Алгоритм XML-сериализации реализуется в виде рекурсивного обхода published интерфейса объекта. Для начала определим ряд простых функций для формирования XML кода. Они позволят нам добавлять открывающие, закрывающие теги и значения в выходной поток.
{ пишет строку в выходящий поток. Исп-ся при сериализации } procedure WriteOutStream(Value: string); begin OutStream.Write(Pchar(Value)[0], Length(Value)); end; { Добавляет открывающий тег с заданным именем } procedure addOpenTag(const Value: string); begin WriteOutStream(CR + DupStr(TAB, Level) + ''); inc(Level); end; { Добавляет закрывающий тег с заданным именем } procedure addCloseTag(const Value: string; addBreak: boolean = false); begin dec(Level); if addBreak then WriteOutStream(CR + DupStr(TAB, Level)); WriteOutStream('' + Value + '>'); end; { Добавляет значение в результирующую строку } procedure addValue(const Value: string); begin WriteOutStream(Value); end;
|
Следующее, что предстоит реализовать - это перебор всех свойств объекта и формирование тегов. Сведения о свойствах получаются через интерфейс компонента. Это информация о типе. Для каждого свойства, за исключением классовых получается их имя и текстовое значение, после чего формируется XML-тег. Значение загружается через ф-ию TypInfo.GetPropValue();
procedure TglXMLSerializer.SerializeInternal(Component: TObject; Level: integer = 1); var PropInfo: PPropInfo; TypeInf, PropTypeInf: PTypeInfo; TypeData: PTypeData; i, j: integer; AName, PropName, sPropValue: string; PropList: PPropList; NumProps: word; PropObject: TObject; begin { Playing with RTTI } TypeInf := Component.ClassInfo; AName := TypeInf^.Name; TypeData := GetTypeData(TypeInf); NumProps := TypeData^.PropCount; GetMem(PropList, NumProps*sizeof(pointer)); try { Получаем список строк } GetPropInfos(TypeInf, PropList); for i := 0 to NumProps-1 do begin PropName := PropList^[i]^.Name; PropTypeInf := PropList^[i]^.PropType^; PropInfo := PropList^[i]; case PropTypeInf^.Kind of tkInteger, tkChar, tkEnumeration, tkFloat, tkString, tkSet, tkWChar, tkLString, tkWString, tkVariant: begin { Получение значения свойства } sPropValue := GetPropValue(Component, PropName, true); { Перевод в XML } addOpenTag(PropName); addValue(sPropValue); { Добавляем значение свойства в результат } addCloseTag(PropName); end; ...
Для классовых типов придется использовать рекурсию для загрузки всех свойств соответствующего объекта. Более того, для ряда классов необходимо использовать особый подход. Сюда относятся, к примеру, строковые списки и коллекции. Ими и ограничимся.
Для текстового списка TStrings будем сохранять в XML его свойство CommaText, а в случае коллекции после обработки всех ее свойств сохраним в XML каждый элемент TCollectionItem отдельно. При этом в качестве контейнерного тега будем использовать имя класса TCollection(PropObject).Items[j].ClassName.
|
... tkClass: { Для классовых типов рекурсивная обработка } begin addOpenTag(PropName); PropObject := GetObjectProp(Component, PropInfo); if Assigned(PropObject)then begin { Для дочерних свойств-классов - рекурсивный вызов } if (PropObject is TPersistent) then Result := Result + SerializeInternal(PropObject, Level); { Индивидуальный подход к некоторым классам } if (PropObject is TStrings) then { Текстовые списки } begin WriteOutStream(TStrings(PropObject).CommaText); end else if (PropObject is TCollection) then { Коллекции } begin Result := Result + SerializeInternal(PropObject, Level); for j := 0 to (PropObject as TCollection).Count-1 do begin addOpenTag(TCollection(PropObject).Items[j].ClassName); SerializeInternal(TCollection(PropObject).Items[j], Level); addCloseTag(TCollection(PropObject).Items[j].ClassName, true); end end; { Здесь можно добавить обработку остальных классов: TTreeNodes, TListItems } end; addCloseTag(PropName, true); end;
|
Описанные функции позволят нам получить XML код для объекта включая все его свойства. Остается только 'обернуть' полученный XML в тег верхнего уровня - имя класса объекта. Если мы поместим вышеприведенный код в функцию SerializeInternal(), то результирующая функция Serialize() будет выглядеть так:
procedure Serialize(Component: TObject; Stream: TStream); ... WriteOutStream( PChar(CR + '') ); SerializeInternal(Component); WriteOutStream( PChar(CR + '' + Component.ClassName + '>') );
|
К вышеприведенному можно добавить еще ф-ии для форматирования генерируемого XML кода. Также можно добавить возможность пропуска пустых значений и свойств со значениями по умолчанию. Все эти расширения мы реализуем при создании готового компонента.
Следует заметить, что при желании можно переписать этот код для генерации также и атрибутов элементов. Для отличия элементов от их атрибутов в интерфейсе сохраняемого объекта можно принять следующее соглашение: элементами являются только классовые типы, все же прочие свойства кодируются как атрибуты соответствующих классов. Соответственно можно модифицировать и парсер. При этом появляется возможность использования XML схем вместо DTD. Тут, однако, возникает проблема описания модели содержания для текста #PCDATA. Для разрешения проблемы придется выделить отдельный класс для хранения подобных данных. Но это тема уже другой статьи.
Продолжение:
Загрузка XML в объект
Содержание
После того, как мы рассмотрели возможность превода данных объекта в XML следует перейти к следующей задаче. Задача состоит в реализации обратного процесса, а именно - загрузки XML данных в объект. Загрузка XML данных в объект, или десериализация, представляет собой более сложный процесс, т.к. в ходе его необходимо осуществить корректный разбор текстового XML документа на предмет инициализации содержащимися в нем данными заданного объекта. Примем ряд упрощений, которые сократят число проверок корректности входящего XML документа к минимуму. Первое, что необходимо делать, тек это проверять соответствие тега верхнего уровня имени класса нашего объекта. Синтаксическая правильность документа будет проверяться в ходе загрузки данных. При необходимости более жесткой проверки загружаемых XML документов можно привлечь, к примеру, парсер MSXML. Последний поможет нам проверить документ на синтаксическую, а также семантическую корректность при наличии соответствующего DTD. Первое, что следует реализовать, это процедура верхнего уровня, которая получает объект для инициализации, а также потоковый источник данных с текстом XML документа.
var Buffer: PChar; { Буфер, в котором находится XML документ } TokenPtr: PChar; { Указатель на текущее положение парсера XML документа } { Загружает в компонент данные из потока с XML-кодом. Вход: Component - компонент для конвертации Stream - источник загрузки XML Предусловия: Объект Component должен быть создан до вызова процедуры } procedure DeSerialize(Component: TObject; Stream: TStream); begin GetMem(Buffer, Stream.Size); try { Получаем данные из потока } Stream.Read(Buffer[0], Stream.Size + 1); { Устанавливаем текущий указатель чтения данных } TokenPtr := Buffer; { Вызываем загрузчик } DeSerializeInternal(Component, Component.ClassName); finally FreeMem(Buffer); end; end;
|
|
Следующий код занимается тривиальным разбором XML текта. Ищется первый открывающий тег, затем его закрывающая пара. Найденная пара содержит в себе данные для свойств объекта. Внутри найденной пары тегов последовательно выбираются теги (TagName) и текст их содержания (TagValue). Эти теги предположительно соответствуют свойствам объекта, что мы тут же и проверяем. Обратите внимание, что функция StrPos заменена на StrPosExt для ускорения обработки. Среди свойств объекта отыскивается через FindProperty() оноименное свойство. При неудаче генерируется исключение об ошибочности XML тега. Если для тега найден соответвующее свойство, то передаем дальнейшую обработку процедуре SetPropertyValue(), которая заданное свойство с именем TagName проинициализирует найденным значением TagValue. Не забываем также передвигать указатель чтения данных TokenPtr по мере выборки данных.
{ Рекурсивная процедура загрузки объекта их текстового буфера с XML Вызывается из: Serialize() Вход: Component - компонент для конвертации ComponentTagName - имя XML тега объекта } procedure DeSerializeInternal(Component: TObject; const ComponentTagName: string); var BlockStart, BlockEnd, TagStart, TagEnd: PChar; TagName, TagValue: PChar; TypeInf: PTypeInfo; TypeData: PTypeData; PropIndex: integer; AName: string; PropList: PPropList; NumProps: word; { Поиск у объекта свойства с заданным именем } function FindProperty(TagName: PChar): integer; var i: integer; begin Result := -1; for i := 0 to NumProps-1 do if CompareText(PropList^[i]^.Name, TagName) = 0 then begin Result := i; break; end; end; procedure SkipSpaces(var TagEnd: PChar); begin while (TagEnd[0] in [#0..#20]) do inc(TagEnd); end; begin { Playing with RTTI } TypeInf := Component.ClassInfo; AName := TypeInf^.Name; TypeData := GetTypeData(TypeInf); NumProps := TypeData^.PropCount; GetMem(PropList, NumProps*sizeof(pointer)); try GetPropInfos(TypeInf, PropList); { ищем открывающий тег } BlockStart := StrPosExt(TokenPtr, PChar('<' + ComponentTagName + '>'), BufferLength); inc(BlockStart, length(ComponentTagName) + 2); { ищем закрывающий тег } BlockEnd := StrPosExt(BlockStart, PChar('<' + ComponentTagName + '>'), BufferLength); TagEnd := BlockStart; SkipSpaces(TagEnd); { XML парсер } while TagEnd < BlockEnd do begin TagStart := StrPosExt(TagEnd, '<', BufferLength); TagEnd := StrPos(TagStart, '>', BufferLength); GetMem(TagName, TagEnd - TagStart + 1); try { TagName - имя тега } StrLCopy(TagName, TagStart + 1, TagEnd - TagStart - 1); TagEnd := StrPos(TagStart, PChar('' + TagName + '>')); TokenPtr := TagStart; inc(TagStart, length('' + TagName + '>')-1); GetMem(TagValue, TagEnd - TagStart + 1); try { TagValue - значение тега } StrLCopy(TagValue, TagStart, TagEnd - TagStart); { поиск свойства, соответствующего тегу } PropIndex := FindProperty(TagName); if PropIndex = -1 then raise Exception.Create( 'TglXMLSerializer.DeSerializeInternal: Uncknown property: ' + TagName); SetPropertyValue(Component, PropList^[PropIndex], TagValue); inc(TagEnd, length('' + TagName + '>')); SkipSpaces(TagEnd); finally FreeMem(TagValue); end; finally FreeMem(TagName); end; end; finally FreeMem(PropList, NumProps*sizeof(pointer)); end; end;
|
Остается только код, который загрузит найденные данные в заданной свойство. Процедуре SetPropertyValue() передаются данные о соответствующем свойстве (PropInfo), которое на следует проинициализировать. Также процедура получает и текстовое значение, содержащееся в найденном теге.
В случае, если тип данные не является классовым типом, то, очевидно, текст Value следует просто загрузить в свойство. Это реализуется вызовом процедуры TypInfo.SetPropValue(). Последняя самостоятельно разберется, как корректно преобразовать тестовое значение в значение свойства в завистимости от его типа.
Если свойство имеет классовый тип, то его значение Value должно содержать XML код, описывающий свойства данного класса. В этом случае воспользуемся рекурсией и передадим обработку вышеприведенной процедуре DeSerializeInternal(). При этом передаем ей в качестве объекта ссылку на найденное свойство PropObject и его имя PropInfo^.Name.
Нам также необходимо озаботиться отдельной обработкой данных для таких классовых типов как списки TStrings и коллекции TCollection. Данные для списков мы загружаем из значения Value как CommaText. Тут все понятно. В сллучае же коллеций данные о элементах коллекции в XML документе содержаться в виде последовательных контейнерных тегов с именем типа элемента коллекци. Т.е., к примеру, ... ... ... и так далее. Внутри каждой пары тегов содержатся свойства объекта TMyCollection.
| procedure SetPropertyValue(Component: TObject; PropInfo: PPropInfo; Value: PChar); var PropTypeInf: PTypeInfo; PropObject: TObject; CollectionItem: TCollectionItem; sValue: string; begin PropTypeInf := PropInfo.PropType^; case PropTypeInf^.Kind of tkInteger, tkChar, tkEnumeration, tkFloat, tkString, tkSet, tkWChar, tkLString, tkWString, tkVariant: begin sValue := StrPas(Value); { Для корректного преобразования парсером tkSet нужны угловые скобки } if PropTypeInf^.Kind = tkSet then sValue := '[' + sValue + ']'; SetPropValue(Component, PropInfo^.Name, sValue); end; tkClass: begin PropObject := GetObjectProp(Component, PropInfo); if Assigned(PropObject)then begin { Индивидуальный подход к некоторым классам } if (PropObject is TStrings) then { Текстовые списки } TStrings(PropObject).CommaText := Value else if (PropObject is TCollection) then { Коллекции } begin while true do { Заранее не известно число элементов в коллекции } begin CollectionItem := (PropObject as TCollection).Add; try DeSerializeInternal(CollectionItem, CollectionItem.ClassName); except { Исключение, если очередной элемент не найден } CollectionItem.Free; break; end; end; end else { Для остальных классов - рекурсивная обработка } DeSerializeInternal(PropObject, PropInfo^.Name); end; end; end; end; { StrPosExt - ищет позицию одной строки в другой с заданной длиной. На длинных строках превосходит StrPos. } function StrPosExt(const Str1, Str2: PChar; Str2Len: DWORD): PChar; assembler; asm PUSH EDI PUSH ESI PUSH EBX OR EAX,EAX // Str1 JE @@2 // если строка Str1 пуста - на выход OR EDX,EDX // Str2 JE @@2 // если строка Str2 пуста - на выход MOV EBX,EAX MOV EDI,EDX // установим смещение для SCASB - подстрока Str2 XOR AL,AL // обнулим AL push ECX // длина строки MOV ECX,0FFFFFFFFH // счетчик с запасом REPNE SCASB // ищем конец подстроки Str2 NOT ECX // инвертируем ECX - получаем длину строки+1 DEC ECX // в ECX - длина искомой подстроки Str2 JE @@2 // при нулевой длине - все на выход MOV ESI,ECX // сохраняем длину подстроки в ESI pop ECX SUB ECX,ESI // ECX == разница длин строк : Str1 - Str2 JBE @@2 // если длина подсроки больше длине строки - выход MOV EDI,EBX // EDI - начало строки Str1 LEA EBX,[ESI-1] // EBX - длина сравнения строк @@1: MOV ESI,EDX // ESI - смещение строки Str2 LODSB // загужаем первый символ подстроки в AL REPNE SCASB // ищем этот символ в строке EDI JNE @@2 // если символ не обнаружен - на выход MOV EAX,ECX // сохраним разницу длин строк PUSH EDI // запомним текущее смещение поиска MOV ECX,EBX REPE CMPSB // побайтно сравниваем строки POP EDI MOV ECX,EAX JNE @@1 // если строки различны - ищем следующее совпадение первого символа LEA EAX,[EDI-1] JMP @@3 @@2: XOR EAX,EAX @@3: POP EBX POP ESI POP EDI end; |
К приведенному коду следует добавить еще ряд возможностей для более корректной реакции для обработки неверного XML кода. Также можно достаточно просто реализовать автоматическую генерацию DTD для любого класса Delphi. После этого можно собрать полноценный компонент, объединяющий в себе всю необходимую функциональность для XML сериализации.
Продолжение
с использованием API вы может
В архиве все исходники примера (73К).
Дополнительные сведения о программировании с использованием API вы может посмотреть в справочных файлах, которые идут с CR (PROGRAM FILES\SEAGATSOFTWARE\CRYSTAL REPORTS\DEVELOPER FILES\HELP\), файлы DEVELOPR.HLP и RUNTIME.HLP. Если их у вас нет, то скачайте с . В будущем я надеюсь развить тему CR более углубленно, но это зависит от интереса читателей и наличия времени :-).
Специально для
я оставил доступными все математические
При составлении этого примера я оставил доступными все математические функции, которые были перечислены выше. Вообще функции типа "random", "frac" или "int" с точки зрения математики не желательны. Но при использовании некоторых из них получаются очень любопытные графики (например, int abs x ^ frac abs x или random * x * sin x). Обратите также внимание, что при изменении максимальных значений осей X или Y перерасчет графика происходит практически мгновенно.
Запрос данных от программы Map Info
Для выполнения запроса из Вашей программы-клиента значения MapBasic используйте OLE-методEval. Например: MyVar:= FServer.Eval('здесь команда MapBasic');
Примечание: В компоненте это реализовано процедурой Eval, но в сущносте вызывается FServer.Eval При использовании метода Eval программа MapInfo интерпретирует строку как выражение языка MapBasic, определяет значение выражения и возвращает это значение в виде строки. Замечание: Если выражение приводится к логическому значению (тип Logical), MapInfo возвращает односимвольную строку, "Т" или "F" соответственно.
Запуск и остановка пакета
Для того чтобы произвести запуск (активацию) компонентов, установленных в пакете, можно воспользоваться методом StartApplication интерфейса ICOMAdminCatalog. При этом в качестве параметра можно передавать как имя пакета, так и его GUID ComAdminCatalog.StartApplication('COMTest'); Аналогично выполняется и операция остановки компонентов данного пакета. ComAdminCatalog.ShutdownApplication('COMTest');
Запуск MapInfo
Запуск уникального экземпляра программы MapInfо осуществляется вызовом функции CreateObject() Visual Basic с присваиванием возвращаемого значения объектной переменной. (Вы можете декларировать объектную переменную как глобальную; в противном случае объект MapInfо освобождается после выхода из локальной процедуры.) Например: FServer := CreateOleObject('MapInfo.Application'); Для подключения к ранее исполнявшемуся экземпляру MapInfo, который не был запущен вызовом функции CreateObject(), используйте функцию GetObject(). // Данная реализация оставлена вам уважаемые читатели для тренировки FServer := GetObject('MapInfo.Application'); Внимание: Если Вы работаете с Runtime-версией MapInfo, а не с полной копией, задавайте "MapInfo. Runtime" вместо "MapInfo. Арplication". Runtime-версия и полная версия могут работать одновременно.
Функции CreateObject() и GetObject() используют механизм управления объектами OLE (OLE Automation) для связи с MapInfo. Примечание: В 32-разрядной версии Windows (Windows95 или Windows NT) можно запускать несколько экземпляров MapInfo. Если Вы запустите MapInfo и вслед за этим программу, использующую Интегрированную Картографию и вызывающую CreateObjectf), то будут работать два независимых экземпляра MapInfo. Однако в 16-разрядной версии программа использующая Интегрированную Картографию с запущенным MapInfo работать не сможет.
Завершающие штрихи
Вот, собственно, и все, что мне хотелось объяснить при описании заявленной темы. Остается только добавить, что представленный в статье инспектор доступен в исходных текстах как FreeWare без каких-либо оговорок, кроме единственной - уважать тельные метаклассы, менеджер, реестр и вспомогательные процедуры. Весь код достаточно полно комментирован, так что можно всегда обратиться к нему при возникновении вопросов.
Скачать : (88К)
Кроме того, к тексту инспектора приложен простенький пример, картинки из которого использованы в статье: модули UnitMainForm (главная форма примера) и UnitInfo (классы метаданных для объектов, инспектируемых в примере). Пример можно компилировать не устанавливая компонент инспектора в палитру компонентов, так как компонент создается явно во время выполнения.
Россия, Томск. Специально для
Статьи Королевства Дельфи
с обычными логическими операция над
1. Введение. Логические операции. Введение Наряду с обычными логическими операция над логическими типами Boolean, часто приходятся выполнять операции и над отдельными битами, обычно используемыми, как флаги. Для эффективной работы необходимо понятие логических операций. Паскаль поддерживает следующие логические операции AND - логическое И; OR - (включающие) логическое ИЛИ; XOR - (исключающие) логическое ИЛИ; NOT - отрицание или инверсия бита; SHL - логический сдвиг влево; SHR - логический сдвиг вправо. Другие логические операции над числами в Паскаль не включены, но доступны через ассемблерные вставки.
Каждый бит может иметь только два состояния ЛОЖЬ (FALSE) или ИСТИНА (TRUE)
Состояние бита можно описывать и другими словами, часть которых пришла из математики, часть из электроники, часть из логики.
Для значения ЛОЖЬ, альтернативные варианты такие - [НЕТ, НОЛЬ, ВЫКЛЮЧЕНО, НЕ УСТАНОВЛЕНО, СБРОШЕНО, FALSE, F, 0, -] и другие.
Для значения ИСТИНА, альтернативные варианты такие - [ДА, ЕДИНИЦА, ВКЛЮЧЕНО, УСТАНОВЛЕНО, ВЗВЕДЕНО, TRUE, T, 1, +] и другие.
Рассмотрим эти операции по отдельности AND - логическое И, эта операции выглядит так | A | B | Y | | 0 | 0 | 0 | | 0 | 1 | 0 | | 1 | 0 | 0 | | 1 | 1 | 1 | Выражение истинно, когда истинны оба бита. Присказка "И там И там"
OR - (включающие) логическое ИЛИ, эта операции выглядит так | A | B | Y | | 0 | 0 | 0 | | 0 | 1 | 1 | | 1 | 0 | 1 | | 1 | 1 | 1 | Выражение истинно, когда истинен хотя бы один бит. Присказка "ИЛИ там ИЛИ там, включая и там и там"
XOR - (исключающие) логическое ИЛИ, эта операции выглядит так | A | B | Y | | 0 | 0 | 0 | | 0 | 1 | 1 | | 1 | 0 | 1 | | 1 | 1 | 0 | Выражение истинно, когда истинен только один бит. Присказка "ИЛИ там ИЛИ там, исключая и там и там"
NOT - отрицание или инверсия бита, эта операции применяется только к одному биту, действие простое — текущее значение бита изменяется на противоположное
SHL - логический сдвиг влево, операции применяется только к группе битов, одного из целочисленных типов Паскаля, например к байту, слову и т.д. Сдвиг байта влево на один разряд. | Разряды | B7 | B6 | B5 | B4 | B3 | B2 | B1 | B0 | | До | 1 | 0 | 0 | 1 | 1 | 1 | 0 | 1 | | После | 0 | 0 | 1 | 1 | 1 | 0 | 1 | 0 | Сдвиг байта влево на два разряда | Разряды | B7 | B6 | B5 | B4 | B3 | B2 | B1 | B0 | | До | 1 | 0 | 0 | 1 | 1 | 1 | 0 | 1 | | После | 0 | 1 | 1 | 1 | 0 | 1 | 0 | 0 |
Байт смещается влево на один или более разрядов, позиции справа замещаются нулями, позиции слева теряются.
SHR - логический сдвиг вправо, операции применяется только к группе битов, одного из целочисленных типов Паскаля, например к байту, слову и т.д. Сдвиг байта вправо на один разряд. | Разряды | B7 | B6 | B5 | B4 | B3 | B2 | B1 | B0 | | До | 1 | 0 | 0 | 1 | 1 | 1 | 0 | 1 | | После | 0 | 1 | 0 | 0 | 1 | 1 | 1 | 0 | Сдвиг байта вправо на два разряда. | Разряды | B7 | B6 | B5 | B4 | B3 | B2 | B1 | B0 | | До | 1 | 0 | 0 | 1 | 1 | 1 | 0 | 1 | | После | 0 | 0 | 1 | 0 | 0 | 1 | 1 | 1 |
Байт смещается вправо на один или более разрядов, позиции слева замещаются нулями, позиции справа теряются.
На этом описание операций заканчивается, и переходим к практическим примерам. Но вначале немного слов о нотации
Применяемая нотация при отображении чисел в литературе
Числа в символьной форме принято отображать, так чтобы младшие разряды были справа, а строки слева, при этом если используется выравнивание, то оно тоже подчиняется этим правилам.
Нумерация разрядов начинается с нуля в соответствии со степень разряда и описывается формулой K*M^N, где K это коэффициент в диапазоне от 0 до M-1, M это основание числа, а N это степень. Число в степени 0 для всех оснований равно 1.
Посмотрим на примере следующей таблицы для четырех основных оснований.
Для числа 100 | Основание | Значение | Формула | | 2 | 4 | 1*2^2 + 0*2^1 +0*2^0 | | 8 | 64 | 1*8^2 + 0*8^1 +0*8^0 | | 10 | 100 | 1*10^2 + 0*10^1 + 0*2^0 | | 16 | 256 | 1*16^2 + 0*16^1 + 0*2^0 |
Для числа 123 | Основание | Значение | Формула | | 2 | X | Недопустимая комбинация | | 8 | 83 | 1*8^2 + 2*8^1 + 3*8^0 | | 10 | 123 | 1*10^2 + 2*10^1 + 3*10^0 | | 16 | 291 | 1*16^2 + 2*16^1 + 3*16^0 |
Практические примеры
В начале несколько простых примеров по использованию логических операций, а в заключение будет рассмотрено применение этих приемов для работы с каталогами.
Получение позиции бита или его значения 1 shl N
В данном примере единица сдвигается влево на нужное количество разрядов, и в результате получаем двоичное значение, равное 2^N, где установлен один единственный бит, соответствующий разряду числа. Этот прием может использоваться с переменной для расчета позиции во время выполнения или во время компиляции, во втором случае код генерироваться не будет, а компилятор просто рассчитает значение и подставит его в программу, не генерируя дополнительного кода. Это удобно для указания номера бита, не представляя его в виде десятичной или шестнадцатеричной константы. Но чаще бывает удобнее использовать именованные константы, поскольку они более информативны, примеры этого будут приведены в конце статьи.
Установка бита
Для установки отдельного бита или группы битов используется операция ИЛИ, использование иллюстрируется ниже приведенным кодом в виде отдельной функции и результатом выполнения в виде таблицы.
function SetBit(Src: Integer; bit: Integer): Integer; begin Result := Src or (1 shl Bit); end; Здесь происходит следующее: Сначала мы рассчитываем позицию бита - (1 shl Bit), затем устанавливаем полученный бит и возвращаем результат через предопределенную переменную Result. Пример использования: DummyValue := SetBit(DummyValue, 2); | Разряды | B7 | B6 | B5 | B4 | B3 | B2 | B1 | B0 | | До (1) | 1 | 0 | 0 | 1 | 1 | 1 | 0 | 1 | | После | 1 | 0 | 0 | 1 | 1 | 1 | 0 | 1 | | До (2) | 1 | 0 | 0 | 1 | 1 | 0 | 0 | 1 | | После | 1 | 0 | 0 | 1 | 1 | 1 | 0 | 1 |
Как видим, вне зависимости от начального состояние бита, после выполнения операции бит становится равны единице.
Сброс бита Для сброса отдельного бита или группы битов используется операция И совместно с инверсной маской, использование иллюстрируется ниже приведенным кодом в виде отдельной функции и результатом выполнения в виде таблицы. function ResetBit(Src: Integer; bit: Integer): Integer; begin Result := Src and not (1 shl Bit); end; Здесь происходит следующее: Сначала мы рассчитываем позицию бита - (1 shl Bit), затем с помощью операции NOT инвертируем полученную маску, устанавливая, не затрагиваемые биты маски в единицу, а затрагиваемый бит в ноль, затем сбрасываем этот бит, а результат возвращаем результат через предопределенную переменную Result. Пример использования: DummyValue := ResetBit(DummyValue, 2); | Разряды | B7 | B6 | B5 | B4 | B3 | B2 | B1 | B0 | | До (1) | 1 | 0 | 0 | 1 | 1 | 1 | 0 | 1 | | После | 1 | 0 | 0 | 1 | 1 | 0 | 0 | 1 | | До (2) | 1 | 0 | 0 | 1 | 1 | 0 | 0 | 1 | | После | 1 | 0 | 0 | 1 | 1 | 0 | 0 | 1 |
Как видим, вне зависимости от начального состояние бита, после выполнения операции бит становится равны нулю.
Переключение бита Для переключения отдельного бита или группы битов используется операция исключающие ИЛИ, использование иллюстрируется ниже приведенным кодом в виде отдельной функции и результатом выполнения в виде таблицы. function InvertBit(Src: Integer; bit: Integer): Integer; begin Result := Src xor (1 shl Bit); end; Здесь происходит следующее: Сначала мы рассчитываем позицию бита - (1 shl Bit), затем с помощью операции XOR переключаем бит, а результат возвращаем результат через предопределенную переменную Result. Пример использования: DummyValue := InvertBit(DummyValue, 2); | Разряды | B7 | B6 | B5 | B4 | B3 | B2 | B1 | B0 | | До (1) | 1 | 0 | 0 | 1 | 1 | 1 | 0 | 1 | | После | 1 | 0 | 0 | 1 | 1 | 0 | 0 | 1 | | До (2) | 1 | 0 | 0 | 1 | 1 | 0 | 0 | 1 | | После | 1 | 0 | 0 | 1 | 1 | 1 | 0 | 1 | Как видим, состояние бита B2 изменяется на противоположное
Проверка бита Для проверки бита используется операция AND и анализ результата на равенство нулю. if Value and (1 shl N) <> 0 then ... установлен if Value and (1 shl N) = 0 then ... не установлен чаще всего это используется в другой форме, вместо расчета позиции используется именованная константа, например const B2 = 4 // B2 (1 shl 2) Begin if Value and B2 = B2 then ... установлен if Value and B2 = 0 then ... не установлен end; Это более наглядно, особенно если константе дано более значимое имя, чем B2, например, для проверки готовности передатчика мы можем определить константу с именем TxReady, тогда это будет выглядеть очень красиво. const TxReady = 4 Begin if Value and TxReady then begin ... обработка готовности передатчика end; end;
Ну, вот с базисом мы покончили и пора приступить к более полезным и практическим примерам. В качестве примера выберем поиск папок и файлов. Пример был разработан для FAQ конференции fido7.ru.delphi, в дальнейшем был немного модернизирован по замечаниям от Юрия Зотова. Полный пример и остальные статьи из FAQ доступны для загрузки с моего .
procedure ScanDir(StartDir: string; Mask:string; List:TStrings); var SearchRec : TSearchRec; begin if Mask = '' then Mask := '*.*'; if StartDir[Length(StartDir)] <> '\' then StartDir := StartDir + '\'; if FindFirst(StartDir + Mask, faAnyFile, SearchRec) = 0 then begin repeat Application.ProcessMessages; if (SearchRec.Attr and faDirectory) <> faDirectory then List.Add(StartDir + SearchRec.Name) else if (SearchRec.Name <> '..') and (SearchRec.Name <> '.') then begin List.Add(StartDir + SearchRec.Name + '\'); ScanDir(StartDir + SearchRec.Name + '\', Mask, List); end; until FindNext(SearchRec) <> 0; FindClose(SearchRec); end; end; Рассмотрим ключевые моменты, относящиеся к данной статье. if FindFirst(StartDir + Mask, faAnyFile, SearchRec) = 0 then
Здесь является битовой маской, описанной в модуле SysUtils, ее значение равно $3F, она предназначена для включения в поиск специальных файлов и одновременно для изоляции лишних бит из структуры TsearchRec, отдельные биты данной маски описаны как именованные константы.
| Наименование | Значение | Описание | | FaReadOnly | $00000001 | Read-only files | Файлы с защитой от записи | | faHidden | $00000002 | Hidden files | Невидимые файлы | | faSysFile | $00000004 | System files | Системные файлы | | faVolumeID | $00000008 | Volume ID files | Метка тома | | faDirectory | $00000010 | Directory files | Папки | | faArchive | $00000020 | Archive files | Архивные файлы (для системы архивации) | | faAnyFile | $0000003F | Any file | Все файлы - комбинация выше указанных флагов | if (SearchRec.Attr and faDirectory) <> faDirectory
здесь мы видим проверку флага faDirectory, работает это следующим образом, сначала изолируются не нужные биты, затем проводится проверка на неравенство нулю, поскольку все остальные биты изолированы, то возможны только два значения, ноль, если флаг не установлен и не ноль установлен, в зависимости от результата выполняется, или часть THEN, или часть ELSE. Других вещей касаемо нашей статьи в примере нет и поэтому рассматривать больше нечего. Прочие логические операции работают с булевыми, а не с битовыми значения.
В заключение статьи можно еще привести примеры использования масок для изоляции битов и выполнения операций над оставшимися битами, возьмем для примера какую ни будь абстрактную комбинацию бит и выполним, что ни будь с ними.
Например, у нас есть такая структура некоторого устройства, и при поступлении данных происходит прерывание, обработка которого поступает в наш обработчик и в другие вместе с кодом состояния, если мы обработали сообщение, то мы должны возвратить значение TRUE, если то FALSE и тогда управление будет передано следующему в цепочке обработчику. Бит TxReady проверять не надо, управление будет поступать, только тогда когда он установлен.
abcccddd - где a - бит готовности b - бит разрешения прерывания ccc - тип операции ddd - счетчик function MyHandler(Code: byte): Boolean; const TxReady = $80; IntBit = $40; TypeMask = $38; CounterMask = $07; var I: Integer; TypeBits: Byte; begin if (Code and Intbit) = Intbit then begin // изоллируем биты типа и смещаем вправо для дальнейшей обработки TypeBits := (Code and TypeMask) shr 3; Case TypeBits of 0: begin for I := 1 to (Code and CounterMask) do begin считываем N данных, количесво указано в битах CounterMask, которые мы изолировали и использовали в качестве значения для окончания цикла. end; Result := TRUE; // обрабатали, пусть больше никто не трогает end; 1: begin команда 1, что то делаем Result := TRUE; // обрабатали, пусть больше никто не трогает end; 2: begin команда 2, что то делаем Result := TRUE; // обрабатали, пусть больше никто не трогает end; else Result := FALSE; // другие команды не наше дело end; end else begin Result := FALSE; // пусть другой обрабатывает end; end;
Ошибки при работе с битами
Например, для сложения бит мы можем использовать два варианта или операцию + или операцию OR. Первый вариант является ошибочным. AnyValue + 2, если бит два установлен, то в результате этой операции произойдет перенос в следующий разряд, а сам бит окажется сброшенным вместо его установки, так можно поступать если только если есть уверенность в результате, то если заранее известно начальное значение. А вот в случае использования варианта AnyValue or 2, такой ошибки не произойдет. Тоже относится к операции вычитания для сброса бита. faAnyFiles - faDirectory ошибки не даст, а вот AnyFlags - AnyBit может, дать правильный вариант, а может нет. Зато AnyFlags and not AnyBit всегда даст то что задумали, использования этой техники будет правильнее и для работы с аттрибутами файлов - faAnyFiles and not faDirectory. В качестве домашнего задания попробуйте выполнить это на бумаге для разных комбинацияй бит. Еще одна распростаненая ошибка, это логическая при выполнении операций над группами бит. Например неверено выполнять операцию сравнения над следующей конструкцией AnyFlags and 5 <> 0, если истина должна быть при установке обеих бит, надо писать так AnyFlags and 5 = 5, зато если устраивает истина при установке любого из бит, выражение AnyFlags and 5 <> 0 будет верныи.
На этом статья закончена и вы смогли получить начальные сведения по выполнению логических операций с битами, в заключении приведу и таблицу весовых коэффициентов, чтобы было легче рассчитывать константы.
Приложения
Таблица весовых множителей для 32 битного числа БитDecHexБитDecHexБитDecHexБитDecHex | 0 | 1 | 1 | 8 | 256 | 100 | 16 | 65536 | 10000 | 24 | 16777216 | 1000000 | | 1 | 2 | 2 | 9 | 512 | 200 | 17 | 131072 | 20000 | 25 | 33554432 | 2000000 | | 2 | 4 | 4 | 10 | 1024 | 400 | 18 | 262144 | 40000 | 26 | 67108864 | 4000000 | | 3 | 8 | 8 | 11 | 2048 | 800 | 19 | 524288 | 80000 | 27 | 134217728 | 8000000 | | 4 | 16 | 10 | 12 | 4096 | 1000 | 20 | 1048576 | 100000 | 28 | 268435456 | 10000000 | | 5 | 32 | 20 | 13 | 8192 | 2000 | 21 | 2097152 | 200000 | 29 | 536870912 | 20000000 | | 6 | 64 | 40 | 14 | 16384 | 4000 | 22 | 4194304 | 400000 | 30 | 1073741824 | 40000000 | | 7 | 128 | 80 | 15 | 32768 | 8000 | 23 | 8388608 | 800000 | 31 | 2147483648 | 80000000 | С уважением, 6 сентября 2003 года
Примечание: Статья написана специально для , как эксклюзивный материал, использование данной статьи на других сайтах разрешено только по получению особого разрешения от
Для разработки архива использован PHP 4.3.5, разработка скрипта
в Паскале была собственная поддержка
FONT.Quoted {color : #996600; font-size : x-small; font-style : italic;} CODE {font-size : x-small;} 2. Работа с файлами Паскаля Работа с файлами Паскаля Введение Еще с древних времен в Паскале была собственная поддержка файлов, а к тому времени, когда мамонты уже вымерли, в нем появилась поддержка работы с файлами через ОС, а когда наши предки уже научились добывать огонь, появилась VCL. В современной литературе работа с файлами Паскаль или совсем не рассматривается или рассматривается в скользь. В тоже время поддержка файлов в Паскале достаточно высоко уровневая и значительно превосходит то, что предоставляется средствами ОС и VCL, в которых работа абстрагирована от типов в сторону работы с абстрактными байтами. Это выражается в поддержке работы с текстовыми файлами и в наличии поддержки работы с типами, не в даваясь в подробности реализации на уровне операционной системы. Правда VCL поддерживает загрузку и разбор файлов определенного типа в объекты списков, графических образов и других типов объектов, но не включает поддержки строк и типов. Сочетание этих двух средств позволяет получить хорошие результаты. Данная статья затрагивает использование только файлов Паскаль и немного затронет VCL. Основное ее назначение помочь начинающим освоить этот вид работы с файлами, а работа с VCL вполне достаточно описана в современной литературе, да и сама по себе она простая. Статья разбита на главы с объяснением по каждому отдельному направлению и будут использованы практические, а не абстрактные примеры. Статья состоит из пяти основных глав. Текстовые файлы – рассматривается простая работа с текстовыми файлами; Типизированные файлы – то же самое, но для типизированных файлов; Не типизированные файлы – немного будет рассмотрена работа и с не типизированными файлами, основные моменты, но вместо этого лучше использовать класс TFileStream, работа с которым и проще, а возможности шире; Расширенная работа с тестовыми файлами, в данной главе будут рассмотрены более сложные методы работы, работа со строкой не как с целой строкой, а как с набором различных типов, Паскаль поддерживает автоматическое преобразование типов в текстовый формат и обратно, производя разбор строки при чтении и ее формирование при записи; Использование текстовых файлов для импорта/экспорта, рассмотрим импорт в Эксель. Работа с файлами Паскаля едина для трех основных типов файлов и очень простая. Ведется она через файловую переменную, одного из трех типов, к которой применяются функции и процедуры. Типовая последовательность следующая: Объявляется файловая переменная нужного типа; С этой файловой переменной связывается файл, функцией AssignFile; Затем файл открывается Reset/Rewrite/Append; Производятся операции чтения или записи, разновидности Read/Write; Файл закрывается с помощью функции CloseFile. С уважением, Анатолий Подгорецкий 06..13 сентября 2003 года Примечание: Статья написана специально для Королевства Дельфи, как эксклюзивный материал, использование данной статьи на других сайтах разрешено только по получению особого разрешения от Королевства Дельфи
Для разработки архива использован PHP 4.3.5, разработка скрипта
Для начала разберемся, что такое
FONT.Quoted {color : #996600; font-size : x-small; font-style : italic;} CODE {font-size : x-small;} 3. Текстовые файлы Текстовые файлы Для начала разберемся, что такое текстовые файлы и в чем их различие от двоичных файлов. Текстовые файлы являются подмножеством двоичных файлов, но в отличие от двоичных не могут содержать весь набор символов. Вся информация в файле разбивается на строки, ограниченные символам возврат каретки (CR) и перевод строки (LF). Допустимые символы это символы с кодами от 32 до 255, символы с кодами ниже 32 являются управляющими и допустимы только следующие коды: 08 (BS) - возврат на шаг 09 (TAB) - табуляция 0A (LF) - перевод строки 0C (FF) – перевод листа 0D (CR) – возврат каретки 1A (EOF) – конец файла Такая ситуация сложилась в стародавние время, когда устройством вывода были телетайпы, затем пишущие машинки и потом появились дисплеи. А каналы связи не позволяли передавать двоичные данные, да и сам они были сначала 5 битные, затем 7 битные и только потом таблицу символов расширили до 8 бит, для поддержки национальных языков и для полной совместимости с компьютерами, где основной единицей был байт. Остальные коды использовались или для управления каналом передачи или для управления специальными устройствами. Паскаль поддерживает работу с такими файлами, через файловую переменную типа TextFile, где основной единицей является строка, состоящая из основных базовых типов (в текстовом виде, разделенных пробелом), наиболее часто это просто строка, как набор символов. В качестве примера напишем программу преобразования из DOS кодировки (OEM) в Windows (ANSI). Техническое задание: Программа должна работать в консольном режиме и получать входные параметры через командную строку; Имя программы Oem2Ansi; На вход поступают два параметра, имя исходного файла и имя выходного файла; Имя выходного файла может быть опущено, в этом случае используется имя входного файла, с изменением расширения выходного на .ANS; Если имена не указаны, то должна выводиться справка о синтаксисе команды; ошибки обрабатывать будем в минимальном объеме, коды ошибок выдаются как ErrorLevel и доступны для обработки в .bat файле. Текст программы Program Oem2Ansi; {$APPTYPE CONSOLE} uses Windows, SysUtils; var InFile: TextFile; OutFile: TextFile; InFilename: string; OutFilename: string; S: string; begin if ParamCount = 0 then begin WriteLn('Syntax is: Oem2Ansi Infile [outfile]'); Halt(1); // Ошибка синтаксиса end; InFilename := ParamStr(1); if ParamCount = 2 then OutFileName := ParamStr(1) else OutFileName := ChangeFileExt(InFilename, '.ans'); AssignFile(InFile, InFilename); // связываем входной файл AssignFile(OutFile, OutFilename); // и выходной выходной файл try try Reset(InFile); // открываем входной файл Rewrite(OutFile); // создаем выходной файл while not EOF(InFile) do // крутим пока не конец файла begin Readln(Infile, S); // читаем строку if Length(S) > 0 // на вход функции можно then // подавать только не пустые строки begin OemToChar(Pchar(S), Pchar(S)); WriteLn(Outfile, S); // записываем строку end else begin WriteLn(Outfile); // записываем пустую строку end; end; except Halt(2); // любая ошибка // не удалось преобразовать файлы end; finally CloseFile(InFile); CloseFile(OutFile); end; end. Разберем работу программы по кусочкам. Вначале объявляются две файловые переменные текстового типа и две переменные для имен файлов, а также одна переменная для хранения и обработки строки. Затем анализируются входные параметры, если параметры не указываются, то выводится сообщение об ошибки и программа заканчивается с кодом выхода 1. Обратите внимание на форму процедуру WriteLn, если в параметрах не указывается файловая переменная, то вывод производится на консоль, что удобно для выдачи различных сообщений, данная форма возможна только для консольного приложения и не применима в GUI приложении. После этого первый параметр копируется в переменную InFilename, если параметров два, то второй параметр копируется в переменную OutFilename, иначе используется имя входного файла и изменяется расширение, на расширение по умолчанию .ANS После этого имена файлов связываются с файловыми переменными. Теперь мы готовы к преобразованию файла, которое будет делаться в двух защищенных блоках, первый блок для защиты ресурсов, а второй блок для защиты от возможных ошибок при работе с файлами, для любых ошибок возвращается код ошибки 2. Первое действие состоит в открытии файлов, входной файл открывается с помощью процедуры Reset - это открытие текстового файла в режиме чтения, а второй с помощью Rewrite – открытие в режиме записи, если файл существует, то он переписывается. Есть еще одна форма открытия текстовых файлов, это функция Append(FileVar), открытие в режиме добавления строк, если файл существует, то курсор позиционируется в конец файла и файл открывается в режиме записи, если файла нет, то он создается. После нее управление передается в блок finally. В случае ошибки управление сначала передается в блок except, а затем в блок finally. После этого создается цикл чтения строк, пока не будет достигнут конец файла, или физический или будет встречен символ EOF. Функция EOF(FileVar). Внутри цикла читается строка во временную переменную Readln(Infile, S) и тут принята одна предосторожность, в функцию Oem2Char НЕЛЬЗЯ передавать пустые строки, поэтому производится анализ длины строки, если строка не нулевая, то производится конвертирование и запись ее в выходной файл, процедурой WriteLn(Outfile, S), иначе в файл пишется пустая строка. По окончанию цикла или в случае ошибки управление поступает в защищенный блок finally, где оба файла закрываются и управление передается операционной системе. Домашнее задание - переписать в Ansi2Oem для выполнения обратной функции, с тем же техническим заданием, расширение по умолчанию OEM Для особо желающих предлагается сделать GUI версию, с диалогами выбора файлов, с прогресс-бар, с предварительным просмотром первых 10-20 строк входного файла (переключение кнопкой OEM/ANSI), с целью определения направления перекодирования, с остальными наворотами, которые сумеет придумать душа, например пакетная обработка всех файлов из папки.
Для разработки архива использован PHP 4.3.5, разработка скрипта
Второй тип файлов, для которого
FONT.Quoted {color : #996600; font-size : x-small; font-style : italic;} CODE {font-size : x-small;} 4. Типизированные файлы Типизированные файлы Второй тип файлов, для которого нет поддержки в OS и VCL – это типизированные файлы. Это такой вид файлов, в котором содержатся записи одного типа и фиксированной длины. Часто используется или для организации мини баз, конфигураций, иногда для импорта/экспорта в специальных форматах. Работа с такими файлами не сложнее, чем работа с текстовыми файлами, наряду с освоенными методами добавляется только одно новое свойство. Если текстовые файлы чисто последовательные, то в типизированных файлах можно перемещаться на любую запись и затем производить последовательное чтение или запись. Это очень похоже на работу с TFileStream за одним исключением, единицей информации является не байт, а тип. Типизированный файл определяется следующим образом var FileVar: file of тип; Где тип это или предопределенный или пользовательский типы. В качестве типов не могут фигурировать динамические структуры, такие как динамические массивы, длинные строки или любые указатели, поскольку все записи должны быть одинаковой длины и не должны указывать на внешние данные. Для обработки таких данных надо использовать не типизированные файлы. Наряду с ранее указанными процедурами нам надо знать еще об некоторых функциях, это процедура Seek, которая не применима для текстовых файлов, а для типизированных файлов используется для перемещения указателя на нужную запись. Для определения количества записей в файле можно использовать функцию FileSize, которая возвращает именно количество записей, а не длину файла, как это следует из ее названия. Для определения текущей позиции в файле можно использовать функцию FilePos. Для уменьшения длины файла можно использовать процедуру Truncate, которая обрезает файл по текущей позиции Замечания по поводу открытия файлов, для этого используются две ранее описанные процедуры: Rewrite - создает новый файл для чтения/записи, если такой файл существует, его длина устанавливается в ноль, а Reset - открывает файл для чтения/записи и не изменяет его длины. Сразу видно различие в этих процедурах по отношению к текстовым файлам. Примечание: записи считаются с нуля Для примеров возьмем любимую в ранних книгах по Паскалю, где в качестве примера использовался пример реализации телефонного справочника с помощью типизированных файлов или же подобные примеры для хранения персональных записей. Эту основу мы будем использовать и в других главах. Для этого определим следующий тип: type TPhoneRec = packed record PersonName: string[25]; Address: string[25]; Phone: string[16]; end; Теперь переходим к демонстрационному примеру, определения типа повторять не будем. Напишем только основные функции работы с файлом, а визуализацию самих данных оставим за бортом, но для самой визуализации очень хорошо подходит TStringGrid. Примеры работы с типизированными файлами // опредение глобальных для модуля или программы переменных var PersonFile: file of TPhoneRec; // определили файл нашего типа DbOpen: Boolean; // флаг состояния базы PhonesCount: Integer; // количество записей в базе // открытие базы и нициализация // не пытаться работать с базой если результат = FALSE function OpenDB(const DbName: string): Boolean; begin AssignFile(PersonFile, DbName); try Reset(PersonFile); // открываем базу PhonesCount := FileSize(PersonFile); // текущее кол. записей DbOpen := TRUE; // открытие прошло нормально except PhonesCount := 0; DbOpen := FALSE; end; Result := DbOpen; end; // создание новой базы и инициализация // не пытаться работать с базой если результат = FALSE function CreateDB(const DbName: string): Boolean; begin AssignFile(PersonFile, DbName); try Rewrite(PersonFile); // открываем базу DbOpen := TRUE; // открытие прошло нормально except DbOpen := FALSE; end; PhonesCount := 0; // записей нет Result := DbOpen; end; // закрытие базы procedure CloseDB; begin if DbOpen then CloseFile(PersonFile); end; // Удалить все после указанной записи procedure TruncateDB(const RecNo: Integer); begin Seek(PersonFile, RecNo); Truncate(PersonFile); end; // Читать следующую запись function ReadNextRec: TPhoneRec; begin Read(PersonFile, Result); end; // Читать указанную запись function ReadRec(const RecNo: Integer): TPhoneRec; begin Seek(PersonFile, RecNo); Result := ReadNextRec; end; // изменить текущую запись procedure ModifyNextRec(const Rec: TPhoneRec); begin Write(PersonFile, Rec); end; // изменить указанную запись procedure ModifyRec(const RecNo: Integer; const Rec: TPhoneRec); begin Seek(PersonFile, RecNo); ModifyNextRec(Rec); end; // Добавить новую запись в конец файла procedure AddRec(const Rec: TPhoneRec); begin Seek(PersonFile, PhonesCount); // переместиться на последнею запись ModifyNextRec(Rec); // и добавить запись PhonesCount := FileSize(PersonFile); // новое кол. записей end; Попробуем воспользоваться написанными функциями и для этого попробуем загрузить все записи в динамический массив. program ReadPhoneBook; {$APPTYPE CONSOLE} uses PhoneDb; var I: Integer; PhoneEntry: TPhoneRec; // отдельная запись PhoneBook: array of TPhoneRec; // телефонный справояник begin if not CreateDB('C:\DB\Phones.dbf') then Exit; WriteLn('Created C:\DB\Phones.dbf'); for I := 0 to 3 do begin PhoneEntry.PersonName := 'PersonName ' + IntToStr(I); PhoneEntry.Address := 'Address ' + IntToStr(I); PhoneEntry.Phone := '(012) 3456789-' + IntToStr(I); AddRec(PhoneEntry); WriteLn(PhoneEntry.PersonName,' ', PhoneEntry.Address,' ' , PhoneEntry.Phone); end; CloseDB; WriteLn('Check database'); if OpenDB('C:\DB\Phones.dbf') then begin SetLength(PhoneBook, PhonesCount); for I := 0 to PhonesCount - 1 do begin PhoneBook[I] := ReadNextRec; WriteLn(PhoneBook[I].PersonName,' ', PhoneBook[I].Address,' ' , PhoneBook[I].Phone); end; end; WriteLn('Press ENTER to exit'); ReadLn; end. Конечно, вместо использования процедур и функций, лучше это оформить в виде классов и иметь все преимущество от использования ООП, но в учебных целях это сделано именно так.
Для разработки архива использован PHP 4.3.5, разработка скрипта
Третий тип файлов Паскаля, это
FONT.Quoted {color : #996600; font-size : x-small; font-style : italic;} CODE {font-size : x-small;} 5. Нетипизированные файлы Нетипизированные файлы Третий тип файлов Паскаля, это нетипизированные файлы, этот тип характеризуется тем, что данные имеют не регулярную структуру, например записи различных типов или записи переменной длины, или это просто двоичные данные. После появления поддержки файлов в VCL, на уровне потоков, а также прямой доступ к файловой системе, через функции АПИ, его ценность стала низкой и почти во всех случаях правильнее использовать потоковые методы из VCL, и только тогда когда требуется небольшой размер программы, стоит использовать их или Win API. Хотя предполагается работа с двоичными данными неопределенного типа, все равно ведется работа с понятиями запись (блок), размер которой задается при открытии файла, по умолчанию размер записи равен 128 байт. Размер файла должен быть кратным размеру блока, иначе при чтении последней записи возникнет ошибка. Новых понятий немного, это понятие размер блока, режим открытия и вместо процедур Read/Write используются процедуры BlockRead/BlockWrite. Посмотрим на изменения по отношению к текстовым и типизированным файлам. Объявление файла делается так: var FileVar: file; Сразу бросается в глаза отсутствие типа, вместо file of тип, просто file, то есть файл данных любого типа. Функции открытия файла Reset и Rewrite имеют дополнительный параметр, который указывает размер записи, если этот параметр не указан, то используется значение по умолчанию в 128 байт, кстати, это часто является одной из причин для возникновения ошибок, забываем указать этот размер, а при работе считаем, что работаем с байтом. Что бы работать с файлом, как с байтом, надо или установить размер записи в 1 или использовать типизированный файл следующего типа - file of byte. При получении размера файла, результат выдается так же в записях, и если опять же нужно получить размер файла в байтах, также надо устанавливать размер записи в единицу или умножить количество записей на размер записи, в этом кстати и состоит различие между файлами Паскаля и файлами в АПИ или VCL, те не оперируют понятиями запись, а только понятиями байт. Другая ошибка у начинающих состоит в том, что длина файла должна быть кратна длине записи, частичные записи не допустимы. Различие между Reset и Rewrite такое же, как и у нетипизированных файлов, первый открывает файл без уничтожения старого файла, а второй создает новый файл, режим открытия файлов задается отдельно. Примеры открытия файла с размером записи в 1 байт Reset(F, 1); // открытие с сохранением файла, файл должен существовать Rewrite(F, 1); // открытие с созданием нового файла, или с удалением старого Для управления режимом открытия файлов существует глобальная переменная FileMode. Назначение этой переменной одного из значений влияет на режим последующего открытия файлов, все последующие файлы открываются в соответствии с ее значением, режим ранее открытых файлов не изменяется. Данная переменная не применима для текстовых файлов, которые открываются в соответствии с типом функции, для Reset это режим только чтение, для Rewrite и Append это режим записи. В модуль Sysutils находится определение констант для потоков, часть констант совпадает с нужными нам. Для полноценного управления режимами доступа надо использовать класс TFileStream. FmOpenRead = 0 открытие только в режиме чтения FmOpenWrite = 1 открытие только в режиме записи fmOpenReadWrite = 2 открытие в режиме чтения/записи Примечание: Переменная FileMode не является потоко безопасной. Теперь можно приступить к примерам и поскольку трудно придумать практический пример, то я приведу по три основных примера использования данного типа и с использованием TFileStream. Это позволит оценить оба метода. Пример 1 - абстрактные данные (file) Использование с двоичными данными абстрактного типа. Просто набор байт. Для демонстрации возьмем простой набор строк, скажем из TStringList. Запись в файл будем производить в следующем формате - длина, строка. Особого практического применения нет, но данная техника часто используется в потоках. Когда надо передавать данные переменной длины, записывающий поток передает, принимающий поток сначала считывает длину, а затем считывает уже известное количество данных. Умолчания для примера: 1. SL создан и содержит строки; 2. Переменная FileName инициализирована и содержит имя файла; 3. Обработка ошибок не ведется, кроме необходимых случаев. var SL: TStringList; I: Integer; F: file; FileName: string; begin try AssignFile(F, Filename); // связали файл с переменной FileMode := fmOpenWrite; // только запись Rewrite(F, 1); // размер записи один байт for I := 0 to Sl.Count –1 do // проход по всем строкам bеgin BlockWrite(F, Length(Sl.Strings[I]), SizeOf(LongInt)); BlockWrite(F, Sl.Strings[I], Length(Sl.Strings[I]); end; finally CloseFile(F); end; end. Пример 2 - абстрактные данные (TFileStream)var SL: TStringList; I: Integer; FS: TFileStream; FileName: string; I: Integer; begin FS := TFileStream.Create(Filename, fmOpenWrite or fmShareExclusive); try for I := 0 to Sl.Count –1 do // проход по всем строкам bеgin FS.Write(Length(Sl.Strings[I]), SizeOf(LongInt)); FS.Write(Sl.Strings[I], Length(Sl.Strings[I])); end; finally FS.Free; end; end. Преимущество состоит в использовании расширенных режимов открытия файлов и нет нужды объявлять размер записи, поскольку самих записей не существует, при записи в файл просто указывается длина данных. Пример 3 – записи фиксированной длины (file) Использование записей, но разного типа. Обратите внимание, что в записях используется не Integer, а LongInt, это связано с тем, что Integer не является фундаментальным типом и его размер зависить от версии компилятора, в то же время LongInt всегда 4 байта. Также что размер string[3] совпадает с размером LongInt, этим обеспечивается одинаковый размер записи. Вторым параметром, влияющим на размер записи - являет выравнивание элементов записи, на какую либо границу 2, 4 8 байт, это также предмет для изменений в различных версиях компилятора или его настроек. Использование ключевого слова packed позволяет избежать этой неприятности, в этом случае запись занимает ровно столько место, сколько требуется и не байтом больше. Это обеспечит нам переносимость. Настоятельно рекомендую обратить особое внимания на эти замечания, поскольку это распространенная ошибка при написании программ. Умолчания для примера: 1. Массив DevArray создан и содержит данные; 2. Переменная FileName инициализирована и содержит имя файла; 3. Обработка ошибок не ведется, кроме необходимых случаев. type TCmd: string[3]; // команда устройству, аббревиатура из 3 символов TRecType = (rtNone, rtCmd, ctData); THdr = packed record TypeID: TRecType; // идентификатор записи, // общая часть во всех остальных типах. end; TCmd = packed record Hdr: THdr; // идентификатор записи DevCmd: TCmd; // команда устройству, аббревиатура из 3 символов end; TData = packed record Hdr: THdr; // идентификатор записи DevData: LongInt; // данные для устройства или из устройства end; TDevEntry = packed record Cmd: TCmd; Data: LongInt; end; var Cmd: TCmd; Data: Tdata; DevArray: array[1..100] of TDevEntry; F: file; FileName: string; I: Integer; begin try AssignFile(F, Filename); // связали файл с переменной FileMode := fmOpenWrite; // только запись Rewrite(F, SizeOf(TCmd)); // TData имеет тот же размер for I := Low(DevArray) to High(DevArray) do // проход по массиву bеgin Cmd.Hdr.TypeID := rtCmd; Cmd.DevCmd := DevArray[I].Cmd; BlockWrite(F, Cmd, SizeOf(TCmd)); Data.Hdr.TypeID := rtData; Data.DevData := DevArray[I].Data; BlockWrite(F, Data, SizeOf(TData)); end; finally CloseFile(F); end; end. Пример 4 – записи фиксированной длины (TFileStream) Объявления типов прежние. var DevArray: array[1..100] of TDevEntry; FS: TFileStream; I: Integer; begin FS := TFileStream.Create(Filename, fmOpenWrite or fmShareExclusive); try for I := Low(DevArray) to High(DevArray) do // проход по массиву bеgin FS.Write(rtCmd, SizeOf(THdr.TypeID)); FS.Write(DevArray[I].Cmd, SizeOf(TCmd.DevCmd)); FS.Write(rtData, SizeOf(THdr.TypeID)); FS.Write(DevArray[I].Data, SizeOf(TData.DevData)); end; finally FS.Free; end; end. Как только код стал сложнне, так сразу стало видно, что использование TFileStream проще и прозрачнее, код более четкий. Отпала необходимости в копировании данных во временные переменные. Пример 5 - записи переменной длины (file)Использование записей переменной длины для организации сложных структур. Записи состоят из двух частей, фиксированной с информацией о дальнейших записях и переменной 0 сами записи. Возможно построение сложных иерархических структур, когда одна запись содержит в себе другие вложенные данные, наглядным примером являются объектовые (.obj) и исполнимые файлы (.exe). Умолчания для примера: 1. Массив DevArray создан и содержит данные; 2. Переменная FileName инициализирована и содержит имя файла; 3. Обработка ошибок не ведется, кроме необходимых случаев. В качестве основы определим следующие типы: type THdr = packed record RecID: TRecType; // идентификатор записи, необязательная часть, // зависит от задачи, // но очень полезная в сложных структурах RecLg: Integer; // длина данных, следуют сразу за заговоком // данные могут быть простыми, но также и сложными // то есть включать другие структуры // со своими заголовками end; TCmd = string[6]; TPacked = packed record DevCmd: TCmd; // команда устройству, аббревиатура из 3 символов DevData: string; // переменная длина end; TDevEntry = packed record Cmd: TCmd; Data: string; end; В файл будем писать данные в следующим формате 1. Заголовок типа THdr, В качестве RecID будем использовать порядковый номер записи начиная с 1. RecLg будет включать полную длину последующего пакета, размер которого переменный. 2. Данные в формате TPacked, где DevCmd аббревиатура команды из 6 символов (string[6]), фиксированной длины и строковые данные переменной длины. Общая длина пакета отражается в заголовке записи, в поле RecLg. var Hdr: THdr; DevArray: array[1..100] of TDevEntry; F: file; FileName: string; I: Integer; begin try AssignFile(F, Filename); // связали файл с переменной FileMode := fmOpenWrite; // только запись Rewrite(F, 1); // так как записи переменной длины, // то размер записи 1 байт for I := Low(DevArray) to High(DevArray) do // проход по массиву bеgin Hdr.RecId := I; Hdr.RecLg := SizeOf(TCmd) + Length(DevArray[I].Data); BlockWrite(F, Hdr, SizeOf(THdr)); // записали заголовок BlockWrite(F, DevArray[I].Cmd, SizeOf(TCmd)); BlockWrite(F, DevArray[I].Data[0], Length(DevArray[I].Data); end; finally CloseFile(F); end; end. В примере происходит следующее: Во временную переменную записывается номер записи Рассчитывается длина переменного блока Заголовок пишется в файл Затем в файл пишется фиксированная часть блока DevArray[I].Cmd И затем пишется переменная часть блока DevArray[I].Data[0] Цикл повторяется по всему массиву, по окончанию файл закрывается, теперь реализуем это пример с помощью TFileStream. Пример 6 - записи переменной длины (TFileStream) var DevArray: array[1..100] of TDevEntry; FS: TFileStream; I: Integer; begin FS := TFileStream.Create(Filename, fmOpenWrite or fmShareExclusive); try for I := Low(DevArray) to High(DevArray) do // проход по массиву bеgin FS.Write(I, SizeOf(THdr.RecID)); FS.Write(SizeOf(TCmd)+Length(DevArray[I].Data),SizeOf(THdr.RecLg)); FS.Write(DevArray[I].Cmd, SizeOf(TCmd)); FS.Write(DevArray[I].Data[0], Length(DevArray[I].Data); end; finally FS.Free; end; end. Опять видим, что код стал проще и прозрачнее. Отпала необходимость во временных переменных. На этом данный урок закончен, в принципе Вы уже должны представлять основные методы работы с тремя базовыми файлами и начальные сведенья по TFileStream, что позволит сделать выбор в сторону правильного для задачи метода.
Для разработки архива использован PHP 4.3.5, разработка скрипта
с текстовыми файлами Расширенная работа
FONT.Quoted {color : #996600; font-size : x-small; font-style : italic;} CODE {font-size : x-small;} 6. Расширенная работа с текстовыми файлами Расширенная работа с текстовыми файлами Кроме работы со строками в текстовых файлах, Паскаль поддерживает и более расширенные методы, можно оперировать и данными в строке. Можно читать данные из строки в одну или более переменных. Паскаль сам обеспечивает разбор строки на составляющие части. Полный синтаксис процедур следующий procedure ReadLn([ var F: TextFile; ] V1 [, V2, ...,Vn ]); procedure WriteLn([ var F: TextFile; ] P1 [, P2, ...,Pn ] ); F - файловая переменная типа TextFile, это означает, что процедуры Readln и Writeln могут работать с текстовыми файлами и не применимы для других типов файлов. Если эта переменная опущена, то в качестве текстового файла используется консоль, это также означает, что вывод возможен на консоль и не применим для GUI приложений. На самом деле эта конструкция равносильна следующему вызову Readln([Input) или Writeln(Output). Просто это значение по умолчанию и компилятор сам подставляет эти файловые переменные, который уже описаны в модуле System. Если создано консольное приложение, то Дельфи автоматически ассоциирует эти файловые переменные с окном консоли приложения. Vn – это одна или несколько переменных строкового, символьного, целочисленного или плавающего типа и также логические переменные. Возможно это не полный список типов, но можете попробовать проверить сами. Не поддержанные типа можно выводить с помощью функций преобразования в строку, например DateTimeToStr(Now). Pn – это один или более параметров процедуры, которые могут являться строкой, символом, целым числом или числом с плавающей запятой. В справке по процедуре Write ошибочно указано, что в качестве параметров могут использоваться только переменные, на самом деле это могут быть как переменные, так и константные выражения. Кроме того, справка полностью умалчивает о формате вывода данных в файл, о возможности форматирования данных непосредственно в параметрах процедуры. Форматирования вывода осуществляется в следующем виде X [:Width [:Decimals]], где Width общая длина вывода, а Decimals это количество знаков после десятичной точки. Для получения нужной длины вывода слева добавляется нужное количество пробелов, если результат не помещается в указанную длину, то тогда длина увеличивается до должного количества знаков. Спецификатор Decimals применим только к числам с плавающей запятой. При этом при необходимости производится округление числа. Если спецификаторы не указаны, то для вывода используется научный формат, то есть #.##############E+#### Для целочисленных чисел, строк и символов, без указания длины используется столько символов, что бы значение полностью вывелось. Значения выводятся без разделителей между ними, поэтому надо или использовать длину на один символ больше нужной или вставлять в список параметров пробел, например так Writeln(F, A,' ',B,' ',C); Теперь по поводу чтения обратно в переменные. Все аналогично выводу, за исключением одного неприятного отличия. В случае указания в списке переменных, переменной строкового типа, в нее считывает все до окончания строки. Вроде в старом Паскале такого не было, там вроде бы считывалось слово до первого разделителя, но так ли это было уже не помню, но это и не важно, важно знать как это работает сейчас и учитывать это при чтении данных. Поэтому если надо считывать элементы строки в переменные, то можно использовать только один элемент и он должен быть последним в строке. Приведу пример неправильной логики. WriteLn(F, 1, ' ', 2.5:3:1, ' ', ‘string’, ' ', 3); ReadLn(F, Int1, Real1, Str1, Int2); В файле будет следующая информация 1 2.5 string 3 После считывания значения переменных следующие: Int1 = 1 Real1 = 2.5 Str1 = string 3 Int2 = 0 Правильно написать так WriteLn(F, 1, ' ', 2.5:3:1, ' ', 3, ' ', ‘string’); ReadLn(F, Int1, Real1, Int2, Str1); Теперь в переменных правильные значения: Int1 = 1 Real1 = 2.5 Int2 = 3 Str1 = string Ну и на последок напишем простой пример чтения и записи двухмерного массива в файл. var Column: Integer; F: TextFile; IntArray: array[1..10, 1..3] of Integer; Row: Integer; S: string; TmpInt: Integer; begin // Инициализация for Row := 1 to 10 do begin for Column := 1 to 3 do begin IntArray[Row, Column] := Row*100 + Column; end; end; AssignFile(F, 'Test.txt'); try Rewrite(F); // открытие для записи for Row := 1 to 10 do begin for Column := 1 to 3 do begin Write(F, IntArray[Row, Column]:11); end; WriteLn(F, ' Строка: ', Row); end; finally CloseFile(F); end; // Чтение try WriteLn('Test Reading'); Reset(F); // открытие для чтения for Row := 1 to 10 do begin for Column := 1 to 3 do begin Read(F, TmpInt); Write(TmpInt:11); end; ReadLn(F, S); WriteLn(S); end; finally CloseFile(F); end; Readln; // Закрытие окна по ENTER end. После проверки, можете открыть файл Блокнотом и убедиться, что он действительно текстовый. Примечание: при выводе текстовых сообщений учтите, что на консоль надо выводить в OEM кодировке, поэтому если надо вывести текст на национальном языке, то предварительно преобразуйте его из ANSI в OEM, см. главу «Текстовые файлы - домашнее задание». То есть, любой текст надо преобразовывать функцией CharToOem. Это же касается и текстовых констант в коде программы.
Для разработки архива использован PHP 4.3.5, разработка скрипта
Использование текстовых файлов для импорта
FONT.Quoted {color : #996600; font-size : x-small; font-style : italic;} CODE {font-size : x-small;} 7. Использование текстовых файлов для импорта и экспорта Использование текстовых файлов для импорта и экспорта Текстовые файлы являются универсальным средство импорта/экспорта, например, Excel может очень легко импортировать текстовые файлы, в одном из распознаваемых им форматов. Допустимы следующие форматы: Comma Separated Value (CSV), данные разделенные запятой; Tab Delimited (TXT), данные разделенные символом табуляции, легко распознается Экселем; Symbol Delimited (TXT), данные разделенные указанным определенным символом, частный случай это Tab delimited, но его достоинство состоит в том, что в Экселе можно определить множество символов разделения, например одновременно разделителями могут быть ЗАПЯТАЯ, ТАБУЛЯЦИЯ и ТОЧКА С ЗАПЯТОЙ, Эксель разделит правильно; Fixed (TXT), данные имеют фиксированную длину колонок. Все четыре формата имеют свое назначение, по умолчанию CSV формат считается универсальным форматом, поскольку многие программы и даже некоторые языки программирования поддерживают его. Недостатком является некоторая избыточность. Самые экономные это Tab delimited и Symbol Delimited, поскольку для разделение используется только один символ. Самый не экономный формат Fixed, поскольку для размещения данных всегда используется полная длина, его достоинством является простота обработки файла, можно просто читать фиксированными порциями или даже определить структуру в программе. Многие программы пишут свои логи именно в этом формате. Какой использовать формат определяется задачей. Но я рассмотрю в примерах все форматы. Данные для экспорта могут находиться где угодно: в базе данных, в TStringList, в другом текстовом файл, поступать из потока. В примерах будет использоваться экспорт из TStringGrid, это позволит нам убить двух зайцев, дополнить возможности TStringGrid и освоить экспорт. В дополнение к примерам по экспорту, я рассмотрю и обратную операцию, загрузку данных в TstringList из ранее сохраненных данных в текстовой файл. Пример 1, экспорт в файл в формате Comma Separated ValueОсновой для экспорта в CSV понимание некоторых вещей: 1. Первая строка должна быть строкой заголовков колонок; 2. Данные разделяются запятой; 3. Числовые данные пишутся, как есть; 4. Строковые данные заключаются в двойные кавычки; 5. Даты распознаются если они в формате MM/DD/YYYY, заключать в кавычки не надо; 6. Расширение файла должно быть CSV. Будьте осторожнее, обратный экспорт из Экселя работает не так как ожидается, формат далек от CSV, для обратного экспорта правильнее использовать формат Tab Delimited, с ним не ожидается таких сложностей и странностей. Есть еще странности, например, очень отличаются по действию открытие этих файлов из меню и открытие по ассоциации с расширением. Результаты очень удивят. Попробуйте поэкспериментировать с файлами и с Экселем, только сохраняйте в файлы с различными именами. При открытии по ассоциации (двойной щелчок) и расширении CSV получается полностью автоматический импорт. Умолчания для примера: 1. StringGrid создан и содержит некоторое количество колонок и строк, количество определяется при экспорте; 2. Информация о типах данных в колонках StringGrid, то будем считать, что первая колонка целое число, вторая это дата в региональном формате и третья колонка это текст, больше колонок у нас нет. 3. Количество строк зависит от наполнения. 4. Нулевая строка как обычно содержит заголовки колонок. 5. Переменная FileName инициализирована и содержит имя файла, с должным расширением; 6. Обработка ошибок не ведется, кроме необходимых случаев. var F: TextFile; FileName: string; I: Integer; SG: TStringGrid; TempStr: string; Y, M, D: Word; begin try AssignFile(F, Filename); // связали файл с переменной Rewrite(F); // создаем пустой файл // если строка с заголовком не нужна, то можно эту строку удалить. WriteLn(F, '"', SG.Cells[0, 0], '",', '"', SG.Cells[1, 0], '",', '"', SG.Cells[2, 0], '"'); for I := 1 to SG.RowCount – 1 do // проход по всем строкам begin try // конвертирование строки из регионального в американский формат DecodeDate(StrToDate(Trim(SG.Cells[1, I])), Y, M, D); TempStr := IntToStr(M)+'/'+ IntToStr(D)+'/'+IntToStr(Y); except TempStr := ' '; // дата не указана или неверная end; WriteLn(F, SG.Cells[0, I], // число TempStr, // конвертированная дата '"', SG.Cells[2, I], '"'); // текст end; finally CloseFile(F); end; end; Как видим код весьма простой, первым WriteLn выводим заголовки таблицы, а поскольку все заголовки это текст, то обрамляем элементы двойными кавычками и разделяем запятой. Далее в цикле проходим по всем строкам данных и выводим сами данные, но в отличии от строки заголовка делаем следующее: Первая колонка у нас число, поэтому выводим, как есть; Вторая колонка у нас дата, приводим ее к формату MM/DD/YYYY, но также без кавычек; Третья колонка у нас строка, ее выводим в кавычках. Если дата опущена или неверная, то экспортируем пустое значение. Пример 2, экспорт в файл в формате Tab Delimited При выводе в данном формате преобразования не нужны, наша задача состоит в том, чтобы вставить символ табуляции между колонками данных, поэтому код будет еще проще. Заголовки и данные выводятся в едином цикле и разделяются символом табуляции. Этот же пример пригоден и для формата Symbol Delimited, достаточно заменить символ табуляции на любой нужный символ. Умолчания для примера: 1. StringGrid создан и содержит некоторое количество колонок и строк, количество определяется при экспорте; 2. Так как отсутствует информация о типах данных в колонках StringGrid, то будем считать, что первая колонка целое число, вторая это дата в региональном формате и третья колонка это текст, больше колонок у нас нет. Но для экспорта данная информация не нужна. 3. Количество строк зависит от наполнения. 4. Нулевая строка как обычно содержит заголовки колонок. 5. Переменная FileName инициализирована и содержит имя файла, с должным расширением; 6. Обработка ошибок не ведется, кроме необходимых случаев. const TAB = #9; // код символа табуляции // константа для удобства // можно было бы использовать и #9 var F: TextFile; FileName: string; I: Integer; SG: TStringGrid; begin try AssignFile(F, Filename); // связали файл с переменной Rewrite(F); // создаем пустой файл // если строка с заголовком не нужна, // то начните цикл не с нуля, а с единицы. for I := 0 to SG.RowCount – 1 do // проход по всем строкам begin WriteLn(F, SG.Cells[0, I] + TAB + SG.Cells[1, I] + TAB + SG.Cells[2, I]); end; finally CloseFile(F); end; end; Пример 3, экспорт в файл в формате FixedПри выводе в данном формате преобразования не нужны, наша задача состоит в том, чтобы сделать данные колонок одинаковой ширины, нам даже не нужны разделители для колонок, но удобнее будет их сделать в виде одного пробела, что бы можно было обрабатывать файл любым текстовым редактором. Заголовки и данные выводятся в едином цикле и как договорились будем их разделять пробелом. Умолчания для примера: 1. StringGrid создан и содержит некоторое количество колонок и строк, количество определяется при экспорте; 2. Так как отсутствует информация о типах данных в колонках StringGrid, то будем считать, что первая колонка целое число, вторая это дата в региональном формате и третья колонка это текст, больше колонок у нас нет. Но для экспорта данная информация не нужна. 3. Количество строк зависит от наполнения. 4. Нулевая строка как обычно содержит заголовки колонок. 5. Переменная FileName инициализирована и содержит имя файла, с должным расширением; 6. Обработка ошибок не ведется, кроме необходимых случаев. var F: TextFile; FileName: string; I: Integer; SG: TStringGrid; begin try AssignFile(F, Filename); // связали файл с переменной Rewrite(F); // создаем пустой файл // если строка с заголовком не нужна, // то начните цикл не с нуля, а с единицы. for I := 0 to SG.RowCount – 1 do // проход по всем строкам begin WriteLn(F, Format('%-25s %-25s %16s', [SG.Cells[0, I], SG.Cells[0, I], SG.Cells[0, I] ])); end; finally CloseFile(F); end; end; Для выравнивание ширины колонок использована функция Format, вместо встроенного выравнивания WriteLn, поскольку последняя добавляет пробелы слева, а нам нужны пробелы справа. Вместо функции Format можно использовать свою функцию, или функции из других библиотек, или из Дельфи. Не важно, что использовать, важно чтобы строки были дополнены справа пробелами до нужной длины. Разберем форматную строку %-25.25s – Символ % это признак спецификатора формата, символ «-» означает выравнивание влево, 25 означает, что длина будет дополняться до 25 символов, а .25 означает максимальное количество символов в строке будет 25, остальные символы будут отбрасываться, символ s указывает не тип данных, в данном случае это означает, что в функцию передается строковое значение. Количество спецификаторов не ограничено, в нашем случае их три и мы обязаны передать именно три параметра, что и делается, значения передаются как открытый массив. Символы пробелов, между спецификаторами, у нас выполняют роль разделителей колонок. На первый взгляд это сложно, но после того, как Вы освоите синтаксис этой функции, Вы сможете оценить всю ее мощь. Пример 4, импорт и экспорт данных для TStringList Остался еще один, последний пример, обеспечения импорта и экспорта данных из таблицы TStringList и обратно. Для этого выберем формат Tab Delimited, как очень экономичный и гибкий для нашей цели. Нам не придется бороться с их количеством, поскольку эти значения будут восстановлены автоматически. Нулевая строка будет содержать всю необходимую информацию о таблице. Единственно, что требуется обеспечить, чтобы все колонки данных в таблице имели свой заголовок. Наша задача состоит в том, чтобы вставить символ табуляции между колонками данных. Заголовки и данные выводятся в едином цикле и разделяются символом табуляции, при импорте заголовки будут играть важную роль, по ним будет определяться количество колонок. Умолчания для примера: 1. StringGrid создан и содержит некоторое количество колонок и строк, количество определяется во время выполнения; 2. Информация о типах данных в колонках StringGrid отсутствует, но она нам и не нужна, мы должны уметь экспортировать любую информацию. 3. Количество строк зависит от наполнения. 4. Нулевая строка как обычно содержит заголовки колонок. 5. Переменная FileName инициализирована и содержит имя файла, с должным расширением; 6. Обработка ошибок не ведется, кроме необходимых случаев. const TAB = #9; // код символа табуляции // константа для удобства // можно было бы использовать и #9 procedure Export(const FileName: string; SG: TStringGrid); var F: TextFile; I: Integer; J: Integer; begin try AssignFile(F, Filename); // связали файл с переменной Rewrite(F); // создаем пустой файл for I := 0 to SG.RowCount – 1 do // проход по всем строкам begin for J := 0 to SG.ColCount – 1 do // проход по всем колонкам begin Write(F,SG.Cells[J, I]); // пишем отдельную ячейку if J < SG.ColCount – 1 then Write(F, TAB) // тогда пишем разделитель else WriteLn(F); // иначе закрываем строку end; end; finally CloseFile(F); end; end; procedure Import(const FileName: string; var SG: TStringGrid); var F: TextFile; S: string; begin try AssignFile(F, Filename); // связали файл с переменной Reset(F); // открываем файд с данными SG.ColCount := 1; // начальные значения SG.RowCount := 1; // количества колонок и строк while not EOF(F) do // проход по всем строкам begin ReadLn(F, S); // читаем строку данных SG.Col := 0; // проход всегда начинается с нуля while Pos(TAB, S) > 0 do begin // вычленение колонок SG.Cells[SG.Col, SG.Row] := Copy(S, 1, Pos(TAB, S) - 1); Delete(S, 1, Pos(TAB, S)); if SG.ColCount - SG.Col = 1 then begin SG.ColCount := SG.ColCount + 1;// нужна новая колонка end; SG.Col := SG.Col + 1; // следующая колонка end; SG.Cells[SG.Col, SG.Row] := S; // последняя колонка SG.RowCount := SG.RowCount + 1; // добавим еще одну строку SG.Row := SG.Row + 1; // следующая строка end; SG.RowCount := SG.RowCount - 1; // лишняя строка finally SG.FixedCols := 1; // восстанавливаем SG.FixedRows := 1; // значение по умолчанию CloseFile(F); end; end; Рекомендация: Если необходимо сохранить ширину колонок, количество фиксированных строк и колонок или другие характеристики, то перед импортом сохраните эти значения, а после восстановите их, или установите в нужное значения. После импортирования эти параметры устанавливаются в значение по умолчанию. В дополнение к обычной работе с файлами, можно отметить еще и следующее. Все ранее изученные нами методы пригодны для создания стандартных консольных приложений для динамических ВЕБ страниц. Для создания достаточно использования процедур ReadLn и WriteLn, если конечно этот сервер работает под управлением Windows. Это так называемые консольные CGI приложения (Standalone CGI Application). Вот выдержка из книги доктора Боба «Интернет решения от доктора Боба», которую можно найти на моем сайте Для начала посмотрим на стандартное "hello world" CGI приложение. Единственное, что оно должно сделать, это вернуть HTML страницу со строкой "hello, world". Перед тем как мы начнем делать это - обратим внимание на следующее: CGI приложение должно сообщить миру какой (MIME) формат оно выдает. В нашем случае это "text/html", которое мы должны указать как: content-type: text/html, и затем одну пустую строку. Вот код нашего первого "Hello, world!" CGI приложения: program CGI1; {$APPTYPE CONSOLE} begin writeln('content-type: text/html'); writeln; writeln(' writeln(' writeln('Hello, world!'); writeln(' writeln(' end. Если вы откомпилируете данную программу в Дельфи 2 или 3 и затем запустите ее из web браузера подключенного к web серверу, где оно записано в исполнимом виде в исполняемом каталоге таком как cgi-bin, то вы увидите текст "Hello, world!" на странице. Заключение Ну вот теперь вы знаете про файлы Паскаля все, ну или почти все :), остальное в ваших руках.
Для разработки архива использован PHP 4.3.5, разработка скрипта
Лицей.
Поводом для написания этих уроков послужила дискуссия на сайте об организации уроков для начинающих. Я также решил поделиться своим опытом, в основном по более старым, давно забытым темам. В конце восьмидесятых и в начале девяностых годов, в любой книжке по Паскалю можно было подробно прочитать о работе с битами, о работе с файлами, но в современной литературе эти вопросы или умалчиваются вообще или рассматриваются вскользь. Конечно подобной информации в Сети много, но ее надо еще найти. Я напишу как минимум две статьи - битовая логика и работа с файлами Паскаля, может статей будет больше, но пока не знаю. Статьи будут рассматривать отдельную тему с практическими примерами и надеюсь на уровне доступном для начинающих. Не знаю как получится, поскольку опыт писательской работы у меня не большой, зато есть приличный опыт работы в дискуссионных группах. Кроме того вся микропроцессорная техника и ее программирование прошли совместно с моей жизнью, начинал я с 8 разрядных машин, затем вплотную от самых первых персональных компьютеров и по текущие дни. Паскаль же от 4 версии до самой последней, а после уже Дельфи, начиная с первой версии. Это было интересное время, все начинали с нуля, знаний и опыта у всех было мало, все учились друг у друга, в то же время начал развиваться Интернет, что позволило эффективно обмениваться знаниями. Не было обширного парка компонент, только потом развились файловые архивы уровня и Delphi Super Pages. Все это позволило наработать большой опыт и пришло время отдавать долги, что и делаю в виде серии этих статей для начинающих. Данный сайт был выбран из за его большой популярности и есть надежда, что уроки не пропадут в безвестности в пучине форумов, также есть надежда, что ко мне и к Юрию Зотову присоединятся и другие писатели, которые смогут поделиться своим опытом с начинающими в виде практических или учебных статей. Такие люди есть и их много, только их надо расшевелить.
Статьи Королевства Дельфи
А что, Delphi + Corel Draw даже очень ничего.
| Раздел Сокровищница | абец Олег, дата публикации 10 мая 2001г. |
Надеюсь, многие сталкивались с Corel Draw? А у многих слюнки текли, что это мощнейший графический редактор и хотелось бы под него свои программы писать, к примеру, чертежи выводить? Я один из вас :)
Формат файлов *.cdr конечно, не представлю, т.к. сам его не знаю :), но как с этим зверем работать расскажу. Вычитал, что с Corel Draw можно работать только через скрипт, причем изначально я готовил файлы скриптов *.csc, а затем их запускал в самом редакторе. Рабочий инструмент для освоения - Corel Script Editor. Если Вы хотите действительно что-то написать, то он вам просто необходим, хотя бы ради того, что смотреть как Corel Draw их сам создает, ну и самое главное - дока по языку и функциям. Все замечательно, только вот скрипты медленно работают т.к. они эмитируют работу человека - т.е. кнопочки сами нажимаются, панельки меняются и т.д. А чертеж, к примеру на котором около 3000 объектов мог загружаться и исполнятся до часу! Нет, кода это утомляет, то можно и самому посидеть - глядишь за неделю сделаешь :)
И тут я "чисто случайно" наткнулся на статейку . Оказывается можно и через OLE этот Corel Draw дергать, и как оказывается, не так уж оно и сложно. Да, совершенно верно, нужно использовать CorelDraw.Automation.xx. Я возился с 8-й версией. Забегая на перед, скажу, что тот же чертеж выводился в течении 5-10 минут.
Ну что, начнем?
var CorelDraw: Variant; … CorelDraw := CreateOleObject('CorelDraw.Automation.8'); // цифирку можете свою поставить CorelDraw.FileNew; // или CorelDraw.FileOpen(FileName); CorelDraw.SetDocVisible(True); // можно и не показывать, что он там делает, но ведь интересно! :) // кстати, можно нарисовать, а потом показать - будет на 30% быстрее ... // ну и в конце CorelDraw.FileSave('NewName', 0, False, 0, False); CorelDraw.FileExit(False); // можно не писать, если не надо закрывать CorelDraw := Unassigned;
Формат функций доступным английским языком описан в draw_scr.hlp. Ну а дальше, чего душа (или начальство :) ) желает: CorelDraw.SetPageOrientation(0); CorelDraw.SetPageSize(PageW, PageH); CorelDraw.NewLayer('NewLayer1'); CorelDraw.SelectLayer('NewLayer1'); CorelDraw.CreateEllipse(CalcY(Y1)), CalcX(X1), CalcY(Y2), CalcX(X2), 0, 0, 0); // ничего я не перепутал!!! именно так у них координаты! CorelDraw.CreateRectangle(CalcY(Y1)), CalcX(X1), CalcY(Y2), CalcX(X2), CalcX(Radius)); ...
А это Unit1.dfm
object Form1: TForm1 Left = 175 Top = 107 Width = 596 Height = 375 Caption = 'Form1' Color = clBtnFace Font.Charset = DEFAULT_CHARSET Font.Color = clWindowText Font.Height = -11 Font.Name = 'MS Sans Serif' Font.Style = [] OldCreateOrder = False OnCreate = FormCreate PixelsPerInch = 96 TextHeight = 13 object Label1: TLabel Left = 448 Top = 56 Width = 6 Height = 13 Caption = '[]' end object Label2: TLabel Left = 19 Top = 12 Width = 13 Height = 13 Caption = 'X=' end object Edit1: TEdit Left = 16 Top = 32 Width = 417 Height = 21 TabOrder = 0 Text = '((24/2)+3*(7-x))' OnChange = Edit1Change end object BitBtn1: TBitBtn Left = 448 Top = 32 Width = 75 Height = 22 TabOrder = 1 OnClick = BitBtn1Click Kind = bkOK end object Memo1: TMemo Left = 16 Top = 80 Width = 241 Height = 249 TabOrder = 2 end object Button1: TButton Left = 448 Top = 2 Width = 75 Height = 25 Caption = 'prepare' TabOrder = 3 OnClick = Button1Click end object Edit2: TEdit Left = 36 Top = 8 Width = 53 Height = 21 TabOrder = 4 Text = '2' end object Button2: TButton Left = 264 Top = 80 Width = 75 Height = 25 Caption = 'Speed test' TabOrder = 5 OnClick = Button2Click end end
Алгоритм проверки контрольного числа ИНН и страхового номера ПФ
Модуль содержит две функции для проверки контрольного числа ИНН и страхового номера ПФ CheckINN - Функция вычисляет контрольное число ИНН и возвращает True если ИНН введен правильно или False в противном случае В качестве параметра передается проверяемый ИНН Для справки: структура ИНН 10-ти разрядный ИНН - NNNNXXXXXC 12-ти разрядный ИНН - NNNNXXXXXXCC где: NNNN - номер налоговой инспекции XXXXX, XXXXXX - порядковый номер налогоплательщика (номер записи в госреестре) C - контрольное число в 10-ти разрядном ИНН CC - контрольное число в 12-ти разрядном ИНН (фактически, идущие подряд две контрольные цифры) CheckPFCertificate - Функция вычисляет контрольное число страхового номера ПФ и возвращает True если оно введено правильно или False в противном случае В качестве параметра передается страховой номер ПФ без разделителей Проверка контрольного числа Страхового номера проводится только для номеров больше номера 001-001-998. Контрольное число Страхового номера рассчитывается следующим образом: каждая цифра Страхового номера умножается на номер своей позиции (позиции отсчитываются с конца), полученные произведения суммируются, сумма делится на 101, последние две цифры остатка от деления являются Контрольным числом.
Скачать архив (1.2K)
Алгоритм расчета контрольного числа ОГРН (основной государственный регистрационный номер)
Государственный регистрационный номер записи, вносимой в Единый государственный реестр юридических лиц (далее - государственный реестр), состоит из 13 цифр, расположенных в следующей последовательности: СГГККХХХХХХХЧ, где С (1-й знак) - признак отнесения государственного регистрационного номера записи: 1 - к основному государственному регистрационному номеру (ОГРН); 2 - к иному государственному регистрационному номеру записи; ГГ (со 2-го по 3-й знак) - две последние цифры года внесения записи в государственный реестр; КК (4-й, 5-й знаки) - порядковый номер субъекта Российской Федерации по перечню субъектов Российской Федерации, установленному статьей 65 Конституции Российской Федерации; ХХХХХХХ (с 6-го по 12-й знак) - номер записи, внесенной в государственный реестр в течение года; Ч (13-й знак) - контрольное число: младший разряд остатка от деления предыдущего 12-значного числа на 11.
| unit OGRN; interface function CheckOGRN(const OGRN: string): Boolean; implementation uses Sysutils; function CheckOGRN(const OGRN: string): Boolean; var VerifNumb: Int64; CheckNumb, ResultNumb: Byte; begin Result := Length(OGRN) = 13; if Not Result then Exit; VerifNumb := StrToInt64(Copy(OGRN, 1, 12)); CheckNumb := StrToInt(OGRN[13]); ResultNumb := VerifNumb mod 11; if ResultNumb > 9 then ResultNumb := ResultNumb - Round(ResultNumb/10)*10; Result := ResultNumb = CheckNumb; end; end. |
Автоматическое определение занятости приложения
Компонент:TBusyDetector V0.1b Описание: Компонент для автоматического определения занятости приложения с целью развлечь пользователя при выполнении тяжелых операций, приводящих к зависанию GUI (пользовательского интерфейса). Интерфейс: property Enabled: Boolean; вкл/выкл слежения property Interval: Cardinal; интервал (в мсек) проверки занятости property Caption: TCaption; заголовок окна property Text: TCaption; текст окна property Stated: Boolean; вкл/выкл строки состояние property State: TCaption; текст сотсояние property Progressed: Boolean; вкл/выкл полосу прогресса property Min: Integer; property Max: Integer; property Position: Integer; позиция в полосе прогресса property Step: Integer; property OnGetWindowClass: TBusyWindowClassEvent; получение класса окна property OnBusyDetect: TNotifyEvent; событие на обнаружения занятости property OnIdleDetect: TNotifyEvent; конец занятости Показания: Delphi 3 и выше; OS Win9X/WinNTX; Руки2X; Халява. Комментарий:Очень часто, особенно в связи с одно-потоковой архитектурой приложения в Delphi, требуется вставлять хоть какие то предупреждения для пользователя перед и в процессе выполнения тяжелых операций таких как подключения к базе, массовые математические вычисления и т.п. но так как на это нет времени да и не во все влезешь я взялся в своем проекте решить эту проблему "глобально". Компонент сырой, требует серьезной доработки (например, если компилить в режиме рантайм пакетов - то ресурсы с анимацией недоступны), так что жду критики, пожеланий и конкретных предложений по email. Скачать компонент: (15.7K)
Митронов Станислав
Автоматизация создание BackUp-ов проектов
Пробовал я много разного рода 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)
Bat-файл в Группе проектов
| Раздел Сокровищница | амм на Delphi и объединении их в одну группу, в ту же группу можно добавить любой bat файл.
Он будет выполнятся при каждой компиляции группы. Это можно использовать для выполнения какого-либо действий, необходимого для корректной компиляции или для выполнения действий сразу после перекомпиляции. Например, я это использую для подсчета "сборок" (build), т.е. компиляций.
Как это сделать: File -> New -> Batch file Редактировать содержимое в Project -> Options (Shift+Ctrl+F11) Дальше - у кого какая фантазия...
| |
Bdetry
Использование EDBEngineError Exceptions Перевод с английского |
Перевод и адаптация - Елена Филиппова Информация, которая описывает ошибки, возникающие при работе BDE, может быть получена приложением с помощью обработки исключения EDBENGINEERROR конструкцией try..except. Когда возникает исключение EDBENGINEERROR, создается объект EDBEngineError и различные поля в этом объекте используются, чтобы программно определить какая именно некорректная ситуация произошла. Также, для данного исключения, может быть сгенерировано больше чем одно сообщение об ошибке. Таким образом необходимо выполнения итераций, чтобы получить всю необходимую информацию. Поля объекта, которые наиболее интересны в этом контексте: ErrorCount: type Integer; Определяет количество ошибок, которые содержит свойство Errors, отсчет начинается с нуля. Errors: type TDBError; Структура типа Record, которая содержит информацию о каждой сгенерированной специфической ошибке. Обращаться к каждой записи через нумерованный индекс. Errors.ErrorCode: type DBIResult; Определяет код ошибки, которая содержится в текущей записи. Errors.Category: type Byte; Категория ошибки. Errors.SubCode: type Byte; Дополнительный код для значения ErrorCode. Errors.NativeError: type LongInt; Код удаленной ошибки, возвращается сервером. Из он равен нулю, ошибка произошла не на сервере; в этом поле появляется код возврата SQL-выражения. Errors.Message: type TMessageStr; Если произошла ошибка на сервере, то это строка-сообщение сервера об ошибке в текущей записи Errors, если нет, то сообщение BDE. Объект EDBEngineError создается непосредственно в конструкции try..except в секции except. Однажды созданный, объект может передаваться другим процедурам для обработки ошибок. Пример использования конструкции 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; В данной процедуре происходит попытка записать значение в поле таблице и при возникновении ошибки BDE, обработка ее перехватывается. Объект типа EDBEngineError передается как параметр процедуре ShowError. Обратите внимание, что в данном примере обрабатывается только ошибка BDE, что на самом деле недостаточно. В реальных условиях необходимо проверять все типы исключительных ситуаций. Процедура ShowError, в свою очередь, отображает весь список сообщений, которые содержатся в переданной ей переменной: procedure TForm1.ShowError(AExc: EDBEngineError); var i: Integer; begin Memo1.Lines.Clear; Memo1.Lines.Add('Number of errors: ' + IntToStr(AExc.ErrorCount)); Memo1.Lines.Add(''); {Iterate through the Errors records} for i := 0 to AExc.ErrorCount - 1 do begin Memo1.Lines.Add('Message: ' + AExc.Errors[i].Message); Memo1.Lines.Add(' Category: ' + IntToStr(AExc.Errors[i].Category)); Memo1.Lines.Add(' Error Code: ' + IntToStr(AExc.Errors[i].ErrorCode)); Memo1.Lines.Add(' SubCode: ' + IntToStr(AExc.Errors[i].SubCode)); Memo1.Lines.Add(' Native Error: ' + IntToStr(AExc.Errors[i].NativeError)); Memo1.Lines.Add(''); end; end;
Библиотека для работы с LAN.
| Раздел Сокровищница | Alex, 09 апреля 2001г. |
unit NetProcs; interface uses Classes, Windows; type TAdapterStatus = record adapter_address: array [0..5] of Char; filler: array [1..4*SizeOf(Char)+19*SizeOf(Word) +3*SizeOf(DWORD)] of Byte; end; THostInfo = record username: PWideChar; logon_domain: PWideChar; oth_domains: PWideChar; logon_server: PWideChar; end;{record} function IsNetConnect : Boolean; {Возвращает TRUE если компьютер подключен к сети, иначе - FALSE} function AdapterToString(Adapter: TAdapterStatus): string; {Преобразует MAC адес в привычный xx-xx-xx-xx} function GetMacAddresses(const Machine: string; const Addresses: TStrings): Integer; {Заполняет Addresses MAC-адресами компьютера с сетевым именем Machine. Возвращает число МАС адресов на компьютере} function GetNetUser(HostName: WideString): THostInfo; {Возвращает LOGIN текущего пользователя на HOSTNAME компьютере} implementation uses NB30, SysUtils; function IsNetConnect : Boolean; begin if GetSystemMetrics(SM_NETWORK) AND $01 = $01 then Result:= True else Result:= False; end;{function} function AdapterToString(Adapter: TAdapterStatus): string; begin with Adapter do Result := Format('%2.2x-%2.2x-%2.2x-%2.2x-%2.2x-%2.2x', [ Integer(adapter_address[0]), Integer(adapter_address[1]), Integer(adapter_address[2]), Integer(adapter_address[3]), Integer(adapter_address[4]), Integer(adapter_address[5])]); end;{function} function GetMacAddresses(const Machine: string; const Addresses: TStrings): Integer; const NCBNAMSZ = 16; // absolute length of a net name MAX_LANA = 254; // lana's in range 0 to MAX_LANA inclusive NRC_GOODRET = $00; // good return NCBASTAT = $33; // NCB ADAPTER STATUS NCBRESET = $32; // NCB RESET NCBENUM = $37; // NCB ENUMERATE LANA NUMBERS type PNCB = ^TNCB; TNCBPostProc = procedure (P: PNCB); stdcall; TNCB = record ncb_command: Byte; ncb_retcode: Byte; ncb_lsn: Byte; ncb_num: Byte; ncb_buffer: PChar; ncb_length: Word; ncb_callname: array [0..NCBNAMSZ - 1] of Char; ncb_name: array [0..NCBNAMSZ - 1] of Char; ncb_rto: Byte; ncb_sto: Byte; ncb_post: TNCBPostProc; ncb_lana_num: Byte; ncb_cmd_cplt: Byte; ncb_reserve: array [0..9] of Char; ncb_event: THandle; end; PLanaEnum = ^TLanaEnum; TLanaEnum = record length: Byte; lana: array [0..MAX_LANA] of Byte; end; ASTAT = record adapt: TAdapterStatus; namebuf: array [0..29] of TNameBuffer; end; var NCB: TNCB; Enum: TLanaEnum; I: Integer; Adapter: ASTAT; MachineName: string; begin Result := -1; Addresses.Clear; MachineName := UpperCase(Machine); if MachineName = '' then MachineName := '*'; FillChar(NCB, SizeOf(NCB), #0); NCB.ncb_command := NCBENUM; NCB.ncb_buffer := Pointer(@Enum); NCB.ncb_length := SizeOf(Enum); if Word(NetBios(@NCB)) = NRC_GOODRET then begin Result := Enum.Length; for I := 0 to Ord(Enum.Length) - 1 do begin FillChar(NCB, SizeOf(TNCB), #0); NCB.ncb_command := NCBRESET; NCB.ncb_lana_num := Enum.lana[I]; if Word(NetBios(@NCB)) = NRC_GOODRET then begin FillChar(NCB, SizeOf(TNCB), #0); NCB.ncb_command := NCBASTAT; NCB.ncb_lana_num := Enum.lana[i]; StrLCopy(NCB.ncb_callname, PChar(MachineName),NCBNAMSZ); StrPCopy(@NCB.ncb_callname[Length(MachineName)], StringOfChar(' ', NCBNAMSZ - Length(MachineName))); NCB.ncb_buffer := PChar(@Adapter); NCB.ncb_length := SizeOf(Adapter); if Word(NetBios(@NCB)) = NRC_GOODRET then Addresses.Add(AdapterToString(Adapter.adapt)); end; end; end; end;{function} function NetWkstaUserEnum(servername: PWideChar; level : DWord; var bufptr: Pointer; prefmaxlen: DWord; var entriesread: PDWord; var totalentries: PDWord; var resumehandle: PDWord ): LongInt ; stdcall; external 'netapi32.dll' name 'NetWkstaUserEnum'; function GetNetUser(HostName: WideString): THostInfo; var Info: Pointer; ElTotal: PDWord; ElCount: PDWord; Resume: PDWord; Error: LongInt; begin Resume:=0; NetWkstaUserEnum(PWideChar(HostName),1, Info,0, ElCount,ElTotal,Resume); Error:=NetWkstaUserEnum(PWideChar(HostName),1,Info,256*Integer(ElTotal), ElCount,ElTotal,Resume); case Error of ERROR_ACCESS_DENIED: Result.UserName:= 'ERROR - ACCESS DENIED'; ERROR_MORE_DATA: Result.UserName:= 'ERROR - MORE DATA'; ERROR_INVALID_LEVEL: Result.UserName:= 'ERROR - INVALID LEVEL'; else if Info <> nil then Result:=THostInfo(info^) else begin Result.username:= '???'; Result.logon_domain:= '???'; Result.oth_domains:= '???'; Result.logon_server:= '???'; end;{if} end;{case} end; {function} end.
Библиотека, реализующая некоторые алгоритмы линейной алгебры
в Александр Васильевич, дата публикации 30 октября 2003г. |
Основным ядром, значительной части вычислительных алгоритмов (решение систем линейных и нелинейных уравнений, оптимизация), является алгоритмы линейной алгебры. Вашему вниманию предлагается библиотека, реализующая некоторые алгоритмы линейной алгебры. Выбор алгоритмов был довольно субъективным, и отбор был нацелен на решение систем линейных уравнений. Возможности библиотеки: Элементарные векторные и матричные операции с целыми, вещественными и комплексными числами, матрицы и вектора динамические. Оптимизация этих операций под FPU. Решение СЛАУ с квадратными матрицами: LU, LDL^{T} разложение, вычисление детерминантов, обращение квадратных матриц. Поддерживаются вещественные и комплексные числа. Решение СЛАУ с прямоугольными матрицами (задачи МНК): QR разложение преобразованием Хаусхолдера, SVD разложение, вычисление псевдообратных матриц. Библиотека написана на Delphi 6.
Архивные файлы: — исходные тексты (51K) — исходные тексты демонстрационной программы, она же и являлась отладочной. (43K) — описание библиотеки. (17K) Как альтернативу данной библиотеку могу рекомендовать: -- библиотека линейной алгебры + библиотека для работы с графами (Pascal). Пожалуй. самая богатая и хорошо отлаженная библиотека LAPACK, многие идеи из которой используется в коммерческих библиотеках NAG, IMSL. (Fortran), (С++).
Библиотеки пользовательских функций UDF для Interbase на Free Pascal.
Всем известно, что возможности interbase можно расширить за счет написания пользовательских функций UDF. Но почему на Free Pascal?
Есть ряд веских причин. 1. При переносе Вашего сервера на другую платформу, например, с win32 на FreeBSD или Linux, возникает проблема переноса также и UDF. Как известно, есть дистрибутивы Free Pascal на эти платформы. 2. В Pascal имеется очень удачная концепция библиотеки (library). При переносе на другую платформу достаточно перекомпилировать библиотеку, и она будет работать. При написании аналогичной библиотеки на с приходится переделывать make файл. 3. Вы имеете возможность выбора: сделать или на с, или на паскале. 4. Free Pascal - хорошее подспорье для программиста на Delphi. Знакомый синтаксис, наверное, поможет многим сделать шаг в изучении Unix и использовании серверных возможностей платформ FreeBSD и Linux. Приведем небольшой пример такой библиотеки. Все примеры приведены не в отдельном файле, а на одной странице для удобства чтения.
library libosh; {$mode objfpc} {$PACKRECORDS C} const // Чтобы не было проблем с распознаванием кодировок на разных платформах rus_chars:pChar = #197#210#211#206#208#192#205#202#213#209 +#194#204#229#243#232#238#240#224#234#245#241#236 ; lat_chars:pChar = 'ETYOPAHKXCBMeyuopakxcm'; small_chars:pChar = #113#119#101#114#116#121#117#105#111#112#97#115#100#102#103 +#104#106#107#108#122#120#99#118#98#110#109#233#246#243#234 +#229#237#227#248#249#231#245#250#244#251#226#224#239#240#238 +#235#228#230#253#255#247#241#236#232#242#252#225#254#184 ; cap_chars:pChar = #81#87#69#82#84#89#85#73#79#80#65#83#68#70#71#72#74#75#76#90 +#88#67#86#66#78#77#201#214#211#202#197#205#195#216#217#199 +#213#218#212#219#194#192#207#208#206#203#196#198#221#223#215 +#209#204#200#210#220#193#222#168 ; cp1251:pChar = #233#246#243#234#229#237#227#248#249#231#245#250#244#251#226 +#224#239#240#238#235#228#230#253#255#247#241#236#232#242#252 +#225#254#184#201#214#211#202#197#205#195#216#217#199#213#218 +#212#219#194#192#207#208#206#203#196#198#221#223#215#209#204 +#200#210#220#193#222#168 ; cp866:pChar = #169#230#227#170#165#173#163#232#233#167#229#234#228#235#162 +#160#175#224#174#171#164#166#237#239#231#225#172#168#226#236 +#161#238#241#137#150#147#138#133#141#131#152#153#135#149#154 +#148#155#130#128#143#144#142#139#132#134#157#159#151#145#140 +#136#146#156#129#158#240 ; koi8:pChar = #202#195#213#203#197#206#199#219#221#218#200#223#198#217#215#193 +#208#210#207#204#196#214#220#209#222#211#205#201#212#216#194#192 +#163 +#234#227#245#235#229#238#231#251#253#250#232#255#230#249#247#225 +#240#242#239#236#228#246#252#241#254#243#237#233#244#248#226#224 +#179 ; function replace_it(CString: PChar;scr: PChar;dest: PChar):PChar; var i,j:integer; begin i:=0; while (CString[i]<>#0) do begin j:=0; while (scr[j]<>#0) do begin if CString[i]=scr[j] then begin CString[i]:=dest[j]; Break; end; inc(j); end; inc(i); end; result:=CString; end; function latrus(CString: PChar): PChar;stdcall;export; begin result:=replace_it(CString,lat_chars,rus_chars); end; function rupper(CString: PChar): PChar;stdcall;export; begin result:=replace_it(CString,small_chars,cap_chars); end; function rlower(CString: PChar): PChar;stdcall;export; begin result:=replace_it(CString,cap_chars,small_chars); end; function dos2win(CString: PChar): PChar;stdcall;export; begin result:=replace_it(CString,cp866,cp1251); end; function win2dos(CString: PChar): PChar;stdcall;export; begin result:=replace_it(CString,cp1251,cp866); end; function koi82win(CString: PChar): PChar;stdcall;export; begin result:=replace_it(CString,koi8,cp1251); end; function koi82dos(CString: PChar): PChar;stdcall;export; begin result:=replace_it(CString,koi8,cp866); end; function dos2koi8(CString: PChar): PChar;stdcall;export; begin result:=replace_it(CString,cp866,koi8); end; function win2koi8(CString: PChar): PChar;stdcall;export; begin result:=replace_it(CString,cp1251,koi8); end; function UDF_strcat(dest,source : pchar) : pchar; stdcall;export; begin result:=strcat(dest,source); end; exports latrus name 'latrus', // преобразование латинских бук, похожих на кирилличесике // в кириллические 1251. Иногда надо при переделке существующих // баз данных, в которых некоторые русские буквы по ошибке // набраны латинницей rupper name 'rupper', // перевод русских в верхний и нижний регистры rlower name 'rlower', dos2win name 'dos2win', // перевод различных кодировок кириллицы win2dos name 'win2dos', koi82win name 'koi82win', koi82dos name 'koi82dos', dos2koi8 name 'dos2koi8', win2koi8 name 'win2koi8' ; end.
Откомпилированные библиотеки должны иметь названия libosh.dll для win32 и libosh.so для FreeBSD и Linux. Для подключения этих функций используйте скрипт CONNECT 'mysvr:/db/test.gdb' USER 'UZVER' PASSWORD 'uzver'; DECLARE EXTERNAL FUNCTION LATRUS CSTRING(255) RETURNS CSTRING(255) ENTRY_POINT 'latrus' MODULE_NAME 'libosh'; DECLARE EXTERNAL FUNCTION RUPPER CSTRING(255) RETURNS CSTRING(255) ENTRY_POINT 'rupper' MODULE_NAME 'libosh'; DECLARE EXTERNAL FUNCTION RLOWER CSTRING(255) RETURNS CSTRING(255) ENTRY_POINT 'rlower' MODULE_NAME 'libosh'; DECLARE EXTERNAL FUNCTION DOS2WIN CSTRING(255) RETURNS CSTRING(255) ENTRY_POINT 'dos2win' MODULE_NAME 'libosh'; DECLARE EXTERNAL FUNCTION WIN2DOS CSTRING(255) RETURNS CSTRING(255) ENTRY_POINT 'win2dos' MODULE_NAME 'libosh'; DECLARE EXTERNAL FUNCTION KOI82WIN CSTRING(255) RETURNS CSTRING(255) ENTRY_POINT 'koi82win' MODULE_NAME 'libosh'; DECLARE EXTERNAL FUNCTION KOI82DOS CSTRING(255) RETURNS CSTRING(255) ENTRY_POINT 'koi82dos' MODULE_NAME 'libosh'; DECLARE EXTERNAL FUNCTION DOS2KOI8 CSTRING(255) RETURNS CSTRING(255) ENTRY_POINT 'dos2koi8' MODULE_NAME 'libosh'; DECLARE EXTERNAL FUNCTION WIN2KOI8 CSTRING(255) RETURNS CSTRING(255) ENTRY_POINT 'win2koi8' MODULE_NAME 'libosh'; commit; В порте freepascal для freeBSD немного недоделан модуль sysutils, и вызов некоторых функций из него приводит к runtime error. Пример использования функций библиотеки SELECT WIN2KOI8(NAME) FROM PEOPLE и т.д.
Найти freepascal можно по адресу
Кубанычбек Тажмамат уулу, 03 мая 2001г Специально для
Быстрая обработка спрайтов без применения DirectX
Каждый, кто пытался интенсивно работать со спрайтами при помощи WinAPI, или, что то же самое, методами TCanvas, убеждался в их чрезвычайно низкой производительности. Такова цена универсальности, ничего с этим не поделаешь. Кроме того, средства WinAPI не покрывают все типовые задачи, которые возникают при работе со спрайтовой графикой. Многие ют переход к аппаратному ускорению — DirectX и OpenGL. Но для повседневных задач желательно не отрываться от привычных средств, к тому же самую главную функцию — вывод на экран — WinAPI выполняет вполне удовлетворительно. По-моему, лучшим выходом может стать использование обычных, хранимых в памяти изображений (TBitmap), создание и вывод их при помощи обычных средств при замене промежуточного звена, а именно процедур обработки спрайтов. За счет применения низкоуровнего кода, жесткой привязки изображений к определенному формату удается достичь вполне приемлемой производительности. Ускорение в 5-10-20 раз далеко не предел, особенно в процедурах, использующих MMX. Для лучшей совместимости и с VCL, и с KOL я решил не инкапсулировать код в виде объекта, а оставить его в виде отдельных процедур с осмысленными названиями. Модуль SpriteUtils решает 5 типовых задач: простое копирование спрайта в спрайт [процедуры Get/Put]; отбрасывание (клиппирование) выходящих за границы областей [см. описание]; наложение с заданной прозрачностью [процедуры серии TransPaint]; наложение с заданным прозрачным цветом [процедуры серии TransPut]; снятие/наложение маски (определенного цвета) [процедуры серии MaskPut]; при наложении спрайта — автоматический его поворот вокруг вертикальной или горизонтальной осей [см. описание процедур Get/Put].
Не все функции можно достичь одной операцией и не все подходят для всех форматов. Более всего приветствуется формат 24 бита на пиксел, поэтому в прикладных задачах, скорее всего, понадобится комбинировать несколько процедур последовательно. Для тех, кто заботится о совместимости со старым "железом", есть процедуры, не использующие MMX (и оттого более медленные). Несколько примеров: Сложение спрайтов с заданной прозрачностью. Цвет источника умножается на прозрачность (0.0 — 1.0), цвет приемника на дополнение до 1, результат складывается
Наложение спрайта с одним маскированным цветом Копируются все пикселы источника кроме тех, которые равны выбранному прозрачному цвету
Наложение маски из спрайта Копируются только те пикселы источника, которые равны выбранному цвету
В архиве прилагаются: описание всех процедур в виде HTML-каталога; тестовый проект для сравнения скорости процедур со стандартными; тестовые примеры для KOL и VCL. Скачать архив с документацией и примерами (83 Кб)
Михаил Рудаков, Freeware, 2003.
Цели использования
В некоторых областях часто встречается ситуация, когда требуется вычислять значения функций, задаваемых непосредственно во время работы программы. Иногда для этого достаточно создания интерпретаторов, которые производят как анализ, так и вычисление выражения средствами языка высокого уровня. Однако в случаях, когда на первое место среди качеств программы выходит ее быстродействие, такой вариант становится неприемлемым. Как раз для приложений, производящих длительные вычисления, и был создан данный модуль, анализ формулы в котором производится так же, как и в интерпретаторах (благодаря однократности действия оно не оказывает влияния на скорость), но ее расчет при конкретных значениях переменных происходит с той же (либо большей) скоростью, что и при вызове обычных функций Delphi.
Что это и зачем или Немного наглой саморекламы
Эта программа представляет собой простенький компилятор синтаксических выражений. "Ну опять", - скажет невнимательный читатель, но мы то с тобой внимательные, и понимаем что компилятор, это совсем не то что валяется на каждом программистском сайте. В отличие от парсера (или интерпретатора) такую штуку встретить можно несколько реже. Если честно, то когда она мне была нужна, я ее нигде не встретил. И поэтому родилась эта программа.
Что он может или Какие мы маленькие
Да в общем-то немного, и ценности в ней мало :). Она может вычислять выражения (тип - вещественное число с плавающей точкой (на момент написания это называлось Real)) с использованием операций (+,-,/,*). Мало... А разве сложно дописать пару строк чтобы обработать Y или экспоненту коли они будут нужны?
Д о п о л н е н и е
| Раздел Сокровищница | , дата публикации 04 апреля 2001г. |
В наше время крупных проектов на Васике и Яве, фантастического снижения цен на мегагерцы и мегабайты, скриптуемых языков, COM и супер технологии NET на вагончике сидюков, писать критичные по времени процедуры на ассемблере не модно, можно получить жалостливую усмешку. Все многозначительно обсуждают что <вот у си оптимизация>, а ассемблер это так не переносимо - вдруг Intel загнется ;-).
Где-то два года назад, я разрабатывал программу сервер для интенсивной круглосуточной работы, в том числе работы по забору и переформатированию почты. Одним из этапов форматирования являлось преобразования из koi8,iso,dos,mac в любимый win1251.
Поскольку это часто выполняемая для больших объемов данных операция, то был смысл её оптимизировать. Сразу отбрасывались вложенные циклы, кучи условий вида if (Ch>X) and (Ch
Поэтому шлю свой вариант (это не чудо оптимизации, но всё же лучше того что было в оригинале). TmcCodePageCharsetTable = array [Byte] of Byte; PmcCodePageCharsetTable =^TmcCodePageCharsetTable; // Из таблиц перекодировки A->B, B->C создать A->C // if SafeASCII then в позициях 0..127 будут байты 0..127 procedure mcCodePageCharsetGen (pS1,pS2,pDst: PmcCodePageCharsetTable; SafeASCII: Boolean); Asm//eax-pS1, edx-pS2, ecx-pDst push EBX push ESI push EDI mov ESI,EAX //pS1 mov EBX,EDX //pS2 mov EDI,ECX //pDst mov EDX,ECX //pDst - save xor ecx,ecx //index xor eax,eax @@R: lodsb //A[i] xlat //B[A[i]] stosb //C[i]:=B[A[i]] inc ecx test cl,cl jnz @@R //SafeAscii (0..127) cmp SafeASCII,cl //0 je @@q //FALSE xor ecx,ecx mov edi,edx //pDst @@Fill: mov al,cl stosb inc ecx cmp cl,$80 jb @@Fill @@q: pop EDI pop ESI pop EBX End;// {var i: LongInt; Begin FillChar(pDst^,SizeOf(TmcCodePageCharsetTable),0); for i:=0 to 255 do pDst^[i]:=pS2^[pS1^[i]]; End;//mcCodePageCharsetJoin} //Создать обратную таблицу перекодировки procedure mcCodePageCharsetGen (pSrc,pDst: PmcCodePageCharsetTable; SafeASCII: Boolean); const xBound = 32;//эвристический порог { Несколько слов об xBound: Поскольку в общем случае (пример dos<->win) одному символу одной таблицы может соотвествовать несколько символов другой, надо выбирать какой из них считать правильным. Я решил считать им первый помещаемый символ >=xBound, а само значение xBound выбрать = ' ' (поскольку все символы меньше пробела M$ не любит и вряд ли будет рассовывать по всей табличке) } Asm//eax-pSrc, edx-pDst, cl-1/0-boolean push EBX push ESI push EDI push ECX //push SafeASCII mov ESI,EAX //pSrc //Clear Dst Table xor eax,eax xor ecx,ecx //index mov cl,$40 mov EDI,EDX //pDst rep stosd //Create Reverse @@R: lodsb //A[i] lea ebx, [edx+eax] cmp byte ptr [ebx],xBound jae @@Already mov [ebx], cl //B[A[i]]:=i @@Already: inc ecx test cl,cl jnz @@R //SafeAscii (0..127) pop ECX //pop SafeASCII test cl,cl jz @@q //FALSE xor ecx,ecx mov edi,edx //pDst @@Fill: mov al,cl stosb inc ecx cmp cl,$80 jb @@Fill @@q: pop EDI pop ESI pop EBX End;// // по табличке преобразования pCPCT преобразовать данные из pSrc и записать их в pDst procedure mcCodePageCharsetConvert (pSrc,pDst: Pointer; DataLen: LongInt; pCPCT: PmcCodePageCharsetTable); Asm//eax-pSrc, edx-pDst, ecx-DataLen push ESI push EDI push EBX test ecx,ecx //DataLen jz @@q //=0 mov esi,eax test edx,edx jnz @@pDstAssigned mov edx,eax //pDst:=pSrc @@pDstAssigned: mov edi,edx //pDst mov ebx,pCPCT test ebx,ebx jnz @@pCPCTAssigned call Move //eax,edx,ecx jmp @@q @@pCPCTAssigned: xor eax,eax //??? @@R: lodsb xlat stosb dec ecx jnz @@R @@q: pop EBX pop EDI pop ESI End;//mcCodePageCharsetConvert
Специально для
DCOM permissions
Технология DCOM основана на технологии COM и представляет собой ее продолжение. Основное назначение DCOM - организация взаимодействия клиента с удаленным сервером.
Как пользоваться DCOM Чтобы воспользоваться возможностями DCOM должны быть соблюдены следующие требования: Наличие Клиент.exe, Сервер.exe. ("Каркасы" этих приложений прилагаются к документу см Samples\DCOMSvr). Наличие сети как минимум из двух компьютеров (платформы 9x, Me, NT, 2000). Компьютеры должны "видеть" друг друга. На клиентском и серверном компьютере должна быть установлена поддержка DCOM (на NT и 2000 поддержка DCOM есть по умолчанию, в 9x и Me поддержка отключена, ее можно получить по адресу ). Компьютеры должны быть в одном домене (на сколько критично это требование под вопросом, я не исследовал, информация из ). Сервер.exe должен быть зарегистрирован на клиентской машине и серверной машине (после регистрации на клиентской машине Сервер.exe можно удалить). Регистрация Сервер.exe производится из командной строки: сервер.exe regserver. Разрегистрация также из командной строки: сервер.exe unregserver Должен быть настроен DCOM (можно не задумываясь продублировать настройки, как на клиенте, так и на сервере) для запуска и доступа к Сервер.exe (настроить DCOM можно при помощи утилиты DCOMCNFG.EXE или программно, см. Samples\DcomPerm). Если DCOM настраивается для Win9x, то после настройки необходимо перезагрузить компьютер. Если изменяются настройки протоколов используемых в DCOM, то следует перезагрузить компьютер (действительно для любой платформы). Если Сервер.exe запускается на платформе 9x, то сервер должен быть предварительно загружен, можно поместить запуск сервера в StartUp.
Скачать исходные коды: (28 K)
Decod
| | "Knowledge itself is power" F.Bacon | Таблицы перекодировки Win1251 - KOI8 и их применение.
Раздел Сокровищница
(17.01.00) (21.01.00) (31.01.00) (31.01.00) (01.09.00)
Вариант №5 (01.09.00) Автор: Павленко Алексей
Я же делал несколько по-другому, вернее больше: Взял стандартные таблицы из FARа. Достаточно иметь iso2dos.tbl (двоичные файлы длиной 256 байт, сейчас их буду прилинковывать к exe, как это сделать, посоветуете?) koi2dos.tbl mac2dos.tbl win2dos.tbl При запуске программы читаю таблицы и запоминаю в массивах type ChTable=array [0..255] of byte; var iso2dos, koi2dos, mac2dos, win2dos: ChTable; После этого легко переводить из одной кодировки в другую. Для этого надо заполнить массив t: ChTable; Есть несколько вариантов: 1) Переводим в ДОС case fm.cbCharsetIn.ItemIndex of 1: t:=win2dos; 2: t:=koi2dos; 3: t:=iso2dos; 4: t:=mac2dos; end; 2) Переводим из ДОС case fm.cbCharsetOut.ItemIndex of 1: t2:=win2dos; 2: t2:=koi2dos; 3: t2:=iso2dos; 4: t2:=mac2dos; end; for i:=128 to 255 do t[t2[i]]:=i; for i:=0 to 127 do t[i]:=i; 3) Не ДОС-кодировки // из входной кодировки в ДОС case fm.cbCharsetIn.ItemIndex of 1: t1:=win2dos; 2: t1:=koi2dos; 3: t1:=iso2dos; 4: t1:=mac2dos; end; // таблица для ДОС->выходная case fm.cbCharsetOut.ItemIndex of 1: t2:=win2dos; 2: t2:=koi2dos; 3: t2:=iso2dos; 4: t2:=mac2dos; end; for i:=128 to 255 do t3[t2[i]]:=i; for i:=0 to 127 do t3[i]:=i; // теперь уже окончательная таблица для входной кодировки в выходную for i:=0 to 255 do t[i]:=t3[t1[i]]; Ну а сам перевод делается уже легко: while not eof(f) do begin readln(f, s); s2:=''; for i:=1 to Length(s) do s2:=s2+chr(t[byte(s[i])]); writeln(fout, s2); end; Вроде еще быстрее сделать невозможно. Но это только теоретически ;) Готовую программу можно скачать с
Вариант №4 (31.01.00) Автор: Еремеев Алексей
const Koi = 'юабцдефгхийклмнопярстужвьызшэщчъЮАБЦДЕФГХИЙКЛМНОПЯРСТУЖВЬЫЗШЭЩЧЪ'; Win = 'бвчздецъйклмнопртуфхжигюыэящшьасБВЧЗДЕЦЪЙКЛМНОПРТУФХЖИГЮЫЭЯЩШЬАС'; SerH = 'АБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ'; SerL = 'абвгдежзийклмнопрстуфхцчшщъыьэюя'; procedure ANSI2KOI(var Str: string); var i: integer; k: byte; begin for i := 1 to Length(Str) do begin k := byte(Str[i]); if k = $B8 then Str[i] := char($A3) else if k = $A8 then Str[i] := char($B3) else if k > $BF then Str[i] := Win[k - $BF]; end; end; procedure KOI2ANSI(var Str: string); var i: integer; k: byte; begin for i := 1 to Length(Str) do begin k := byte(Str[i]); if k = $A3 then Str[i] := 'ё' else if k = $B3 then Str[i] := 'Ё' else if k > $BF then Str[i] := Koi[k - $BF]; end; end; procedure ANSI2IBM(var Str: string); var i: integer; k: byte; begin for i := 1 to Length(Str) do begin k := byte(Str[i]); if k = $B8 then Str[i] := char($F1) else if k = $A8 then Str[i] := char($F0) else if k > $EF then Str[i] := char(k - 16) else if (k > $BF) and (k < $F0) then Str[i] := char(k - 64); end; end; procedure IBM2ANSI(var Str: string); var i: integer; k: byte; begin for i := 1 to Length(Str) do begin k := byte(Str[i]); if k = $F0 then Str[i] := 'Ё' else if k = $F1 then Str[i] := 'ё' else if (k > $7F) and (k < $A0) then Str[i] := SerH[k - $7F] else if (k > $9F) and (k < $B0) then Str[i] := SerL[k - $9F] else if (k > $DF) and (k < $F0) then Str[i] := SerL[k - $CF]; end; end; procedure ANSI2Mac(var Str: string); var i: integer; k: byte; begin for i := 1 to Length(Str) do begin k := byte(Str[i]); if k = $A8 then Str[i] := char($DD) else if k = $B8 then Str[i] := char($DE) else if k = $FF then Str[i] := char($DF) else if (k > $BF) and (k < $E0) then Str[i] := char(k - 64); end; end; procedure Mac2ANSI(var Str: string); var i: integer; k: byte; begin for i := 1 to Length(Str) do begin k := byte(Str[i]); if k = $DD then Str[i] := 'Ё' else if k = $DE then Str[i] := 'ё' else if k = $DF then Str[i] := 'я' else if (k > $7F) and (k < $A0) then Str[i] := SerH[k - $7F] else if (k > $DF) and (k < $FF) then Str[i] := SerL[k - $DF]; end; end; procedure ANSI2ISO(var Str: string); var i: integer; k: byte; begin for i := 1 to Length(Str) do begin k := byte(Str[i]); if k = $A8 then Str[i] := char($A1) else if k = $B8 then Str[i] := char($F1) else if k > $BF then Str[i] := char(k - 16); end; end; procedure ISO2ANSI(var Str: string); var i: integer; k: byte; begin for i := 1 to Length(Str) do begin k := byte(Str[i]); if k = $A1 then Str[i] := 'Ё' else if k = $F1 then Str[i] := 'ё' else if k < $F0 then begin if k > $CF then Str[i] := SerL[k - $CF] else if k > $AF then Str[i] := SerH[k - $AF]; end; end; end;
Вариант №3 (31.01.00) Автор: Constantin G. Nekhoroshkov
Предлагаю всеобщему вниманию вот такой вот unit. Он решает проблемы конвертации не только Win1251->KOI8 но и конвертации в другие кодировки. //---Begin of Unit RusChar Unit RusChar; interface Function ALT2ISO(Ch1: byte): byte; Function ALT2KOI(Ch1: byte): byte; Function ALT2MAC(Ch1: byte): byte; Function ALT2WIN(Ch1: byte): byte; Function ISO2ALT(Ch1: byte): byte; Function ISO2KOI(Ch1: byte): byte; Function ISO2MAC(Ch1: byte): byte; Function ISO2WIN(Ch1: byte): byte; Function KOI2ALT(Ch1: byte): byte; Function KOI2ISO(Ch1: byte): byte; Function KOI2MAC(Ch1: byte): byte; Function KOI2WIN(Ch1: byte): byte; Function MAC2ALT(Ch1: byte): byte; Function MAC2ISO(Ch1: byte): byte; Function MAC2KOI(Ch1: byte): byte; Function MAC2WIN(Ch1: byte): byte; Function WIN2ALT(Ch1: byte): byte; Function WIN2ISO(Ch1: byte): byte; Function WIN2KOI(Ch1: byte): byte; Function WIN2MAC(Ch1: byte): byte; Function ConvertString(InputString: string; Convert_Flag: byte): string; implementation Const //Alt decode contants ALT_2_ISO=1; ALT_2_KOI=2; ALT_2_MAC=3; ALT_2_WIN=4; //Iso decode contants ISO_2_ALT=5; ISO_2_KOI=6; ISO_2_MAC=7; ISO_2_WIN=8; //Koi decode contants KOI_2_ALT=9; KOI_2_ISO=10; KOI_2_MAC=11; KOI_2_WIN=12; //Mac decode contants MAC_2_ALT=13; MAC_2_ISO=14; MAC_2_KOI=15; MAC_2_WIN=16; //Win decode contants WIN_2_ALT=17; WIN_2_ISO=18; WIN_2_KOI=19; WIN_2_MAC=20; ALTTable: array [1..64] of byte =( 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239 ); ISOTable: array [1..64] of byte =( 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239 ); KOITable: array [1..64] of byte =( 225, 226, 247, 231, 228, 229, 246, 250, 233, 234, 235, 236, 237, 238, 239, 240, 242, 243, 244, 245, 230, 232, 227, 254, 251, 253, 255, 249, 248, 252, 224, 241, 193, 194, 215, 199, 196, 197, 214, 218, 201, 202, 203, 204, 205, 206, 207, 208, 210, 211, 212, 213, 198, 200, 195, 222, 219, 221, 223, 217, 216, 220, 192, 209 ); MACTable: array [1..64] of byte =( 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 223 ); WINTable: array [1..64] of byte =( 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255 ); Function ALT2ISO(Ch1: byte): byte; Var i: byte; begin For i:=1 to 64 do begin If ALTTable[i]=ch1 then begin ALT2ISO:=ISOtable[i]; exit; end; end; ALT2ISO:=ch1; end; Function ALT2KOI(Ch1: byte): byte; Var i: byte; begin For i:=1 to 64 do begin If ALTTable[i]=ch1 then begin ALT2KOI:=KOItable[i]; exit; end; end; ALT2KOI:=ch1; end; Function ALT2MAC(Ch1: byte): byte; Var i: byte; begin For i:=1 to 64 do begin If ALTTable[i]=ch1 then begin ALT2MAC:=MACtable[i]; exit; end; end; ALT2MAC:=ch1; end; Function ALT2WIN(Ch1: byte): byte; Var i: byte; begin For i:=1 to 64 do begin If ALTTable[i]=ch1 then begin ALT2WIN:=WINtable[i]; exit; end; end; ALT2WIN:=ch1; end; Function ISO2ALT(Ch1: byte): byte; Var i: byte; begin For i:=1 to 64 do begin If ISOTable[i]=ch1 then begin ISO2ALT:=ALTtable[i]; exit; end; end; ISO2ALT:=ch1; end; Function ISO2KOI(Ch1: byte): byte; Var i: byte; begin For i:=1 to 64 do begin If ISOTable[i]=ch1 then begin ISO2KOI:=KOItable[i]; exit; end; end; ISO2KOI:=ch1; end; Function ISO2MAC(Ch1: byte): byte; Var i: byte; begin For i:=1 to 64 do begin If ISOTable[i]=ch1 then begin ISO2MAC:=MACtable[i]; exit; end; end; ISO2MAC:=ch1; end; Function ISO2WIN(Ch1: byte): byte; Var i: byte; begin For i:=1 to 64 do begin If ISOTable[i]=ch1 then begin ISO2WIN:=WINtable[i]; exit; end; end; ISO2WIN:=ch1; end; Function KOI2ALT(Ch1: byte): byte; Var i: byte; begin For i:=1 to 64 do begin If KOITable[i]=ch1 then begin KOI2ALT:=ALTtable[i]; exit; end; end; KOI2ALT:=ch1; end; Function KOI2ISO(Ch1: byte): byte; Var i: byte; begin For i:=1 to 64 do begin If KOITable[i]=ch1 then begin KOI2ISO:=ISOtable[i]; exit; end; end; KOI2ISO:=ch1; end; Function KOI2MAC(Ch1: byte): byte; Var i: byte; begin For i:=1 to 64 do begin If KOITable[i]=ch1 then begin KOI2MAC:=MACtable[i]; exit; end; end; KOI2MAC:=ch1; end; Function KOI2WIN(Ch1: byte): byte; Var i: byte; begin For i:=1 to 64 do begin If KOITable[i]=ch1 then begin KOI2WIN:=WINtable[i]; exit; end; end; KOI2WIN:=ch1; end; Function MAC2ALT(Ch1: byte): byte; Var i: byte; begin For i:=1 to 64 do begin If MACTable[i]=ch1 then begin MAC2ALT:=ALTtable[i]; exit; end; end; MAC2ALT:=ch1; end; Function MAC2ISO(Ch1: byte): byte; Var i: byte; begin For i:=1 to 64 do begin If MACTable[i]=ch1 then begin MAC2ISO:=ISOtable[i]; exit; end; end; MAC2ISO:=ch1; end; Function MAC2KOI(Ch1: byte): byte; Var i: byte; begin For i:=1 to 64 do begin If MACTable[i]=ch1 then begin MAC2KOI:=KOItable[i]; exit; end; end; MAC2KOI:=ch1; end; Function MAC2WIN(Ch1: byte): byte; Var i: byte; begin For i:=1 to 64 do begin If MACTable[i]=ch1 then begin MAC2WIN:=WINtable[i]; exit; end; end; MAC2WIN:=ch1; end; Function WIN2ALT(Ch1: byte): byte; Var i: byte; begin For i:=1 to 64 do begin If WINTable[i]=ch1 then begin WIN2ALT:=ALTtable[i]; exit; end; end; WIN2ALT:=ch1; end; Function WIN2ISO(Ch1: byte): byte; Var i: byte; begin For i:=1 to 64 do begin If WINTable[i]=ch1 then begin WIN2ISO:=ISOtable[i]; exit; end; end; WIN2ISO:=ch1; end; Function WIN2KOI(Ch1: byte): byte; Var i: byte; begin For i:=1 to 64 do begin If WINTable[i]=ch1 then begin WIN2KOI:=KOItable[i]; exit; end; end; WIN2KOI:=ch1; end; Function WIN2MAC(Ch1: byte): byte; Var i: byte; begin For i:=1 to 64 do begin If WINTable[i]=ch1 then begin WIN2MAC:=MACtable[i]; exit; end; end; WIN2MAC:=ch1; end; Function ConvertString(InputString: string; Convert_Flag: byte): string; Var i: word; ConvertByte: byte; begin ConvertString:=''; If InputString='' then exit; for i:=1 to length(InputString) do begin ConvertByte:=ord(InputString[i]); Case Convert_Flag of ALT_2_ISO: ConvertByte:=Alt2Iso(ConvertByte); ALT_2_KOI: ConvertByte:=Alt2Koi(ConvertByte); ALT_2_MAC: ConvertByte:=Alt2Mac(ConvertByte); ALT_2_WIN: ConvertByte:=Alt2Win(ConvertByte); ISO_2_ALT: ConvertByte:=Iso2Alt(ConvertByte); ISO_2_KOI: ConvertByte:=Iso2Koi(ConvertByte); ISO_2_MAC: ConvertByte:=Iso2Mac(ConvertByte); ISO_2_WIN: ConvertByte:=Iso2Win(ConvertByte); KOI_2_ALT: ConvertByte:=Koi2Alt(ConvertByte); KOI_2_ISO: ConvertByte:=Koi2Iso(ConvertByte); KOI_2_MAC: ConvertByte:=Koi2Mac(ConvertByte); KOI_2_WIN: ConvertByte:=Koi2Win(ConvertByte); MAC_2_ALT: ConvertByte:=Mac2Alt(ConvertByte); MAC_2_ISO: ConvertByte:=Mac2Iso(ConvertByte); MAC_2_KOI: ConvertByte:=Mac2Koi(ConvertByte); MAC_2_WIN: ConvertByte:=Mac2Win(ConvertByte); WIN_2_ALT: ConvertByte:=Win2Alt(ConvertByte); WIN_2_ISO: ConvertByte:=Win2Iso(ConvertByte); WIN_2_KOI: ConvertByte:=Win2Koi(ConvertByte); WIN_2_MAC: ConvertByte:=Win2Mac(ConvertByte); end; InputString[i]:=chr(ConvertByte); end; ConvertString:=InputString; end; begin end. //---End of Unit RusChar
Вариант №2 (21.01.00) Автор: Алексей Вуколов
Этот вариант несколько более длинный (в плане размера таблиц перекодировки), но зато, как мне кажется, более универсальный (и, возможно, более быстрый). //--------------------------------------------------------------------------- type PCharRecodeTable = ^TCharRecodeTable; TCharRecodeTable = array[ #0..#255 ] of char; const WinToKOI8Table : TCharRecodeTable = (#$00, #$01, #$02, #$03, #$04, #$05, #$06, #$07, #$08, #$09, #$0A, #$0B, #$0C, #$0D, #$0E, #$0F, #$10, #$11, #$12, #$13, #$14, #$15, #$16, #$17, #$18, #$19, #$1A, #$1B, #$1C, #$1D, #$1E, #$1F, #$20, #$21, #$22, #$23, #$24, #$25, #$26, #$27, #$28, #$29, #$2A, #$2B, #$2C, #$2D, #$2E, #$2F, #$30, #$31, #$32, #$33, #$34, #$35, #$36, #$37, #$38, #$39, #$3A, #$3B, #$3C, #$3D, #$3E, #$3F, #$40, #$41, #$42, #$43, #$44, #$45, #$46, #$47, #$48, #$49, #$4A, #$4B, #$4C, #$4D, #$4E, #$4F, #$50, #$51, #$52, #$53, #$54, #$55, #$56, #$57, #$58, #$59, #$5A, #$5B, #$5C, #$5D, #$5E, #$5F, #$60, #$61, #$62, #$63, #$64, #$65, #$66, #$67, #$68, #$69, #$6A, #$6B, #$6C, #$6D, #$6E, #$6F, #$70, #$71, #$72, #$73, #$74, #$75, #$76, #$77, #$78, #$79, #$7A, #$7B, #$7C, #$7D, #$7E, #$7F, #$80, #$81, #$82, #$83, #$84, #$85, #$86, #$87, #$88, #$89, #$8A, #$8B, #$8C, #$8D, #$8E, #$8F, #$90, #$91, #$92, #$93, #$94, #$95, #$96, #$97, #$98, #$99, #$9A, #$9B, #$9C, #$9D, #$9E, #$9F, #$A0, #$A1, #$A2, #$A3, #$A4, #$A5, #$A6, #$A7, #$A8, #$A9, #$AA, #$AB, #$AC, #$AD, #$AE, #$AF, #$B0, #$B1, #$B2, #$B3, #$B4, #$B5, #$B6, #$B7, #$B8, #$B9, #$BA, #$BB, #$BC, #$BD, #$BE, #$BF, #$E1, #$E2, #$F7, #$E7, #$E4, #$E5, #$F6, #$FA, #$E9, #$EA, #$EB, #$EC, #$ED, #$EE, #$EF, #$F0, #$F2, #$F3, #$F4, #$F5, #$E6, #$E8, #$E3, #$FE, #$FB, #$FD, #$FF, #$F9, #$F8, #$FC, #$E0, #$F1, #$C1, #$C2, #$D7, #$C7, #$C4, #$C5, #$D6, #$DA, #$C9, #$CA, #$CB, #$CC, #$CD, #$CE, #$CF, #$D0, #$D2, #$D3, #$D4, #$D5, #$C6, #$C8, #$C3, #$DE, #$DB, #$DD, #$DF, #$D9, #$D8, #$DC, #$C0, #$D1); KOI8ToWinTable : TCharRecodeTable = (#$00, #$01, #$02, #$03, #$04, #$05, #$06, #$07, #$08, #$09, #$0A, #$0B, #$0C, #$0D, #$0E, #$0F, #$10, #$11, #$12, #$13, #$14, #$15, #$16, #$17, #$18, #$19, #$1A, #$1B, #$1C, #$1D, #$1E, #$1F, #$20, #$21, #$22, #$23, #$24, #$25, #$26, #$27, #$28, #$29, #$2A, #$2B, #$2C, #$2D, #$2E, #$2F, #$30, #$31, #$32, #$33, #$34, #$35, #$36, #$37, #$38, #$39, #$3A, #$3B, #$3C, #$3D, #$3E, #$3F, #$40, #$41, #$42, #$43, #$44, #$45, #$46, #$47, #$48, #$49, #$4A, #$4B, #$4C, #$4D, #$4E, #$4F, #$50, #$51, #$52, #$53, #$54, #$55, #$56, #$57, #$58, #$59, #$5A, #$5B, #$5C, #$5D, #$5E, #$5F, #$60, #$61, #$62, #$63, #$64, #$65, #$66, #$67, #$68, #$69, #$6A, #$6B, #$6C, #$6D, #$6E, #$6F, #$70, #$71, #$72, #$73, #$74, #$75, #$76, #$77, #$78, #$79, #$7A, #$7B, #$7C, #$7D, #$7E, #$7F, #$80, #$81, #$82, #$83, #$84, #$85, #$86, #$87, #$88, #$89, #$8A, #$8B, #$8C, #$8D, #$8E, #$8F, #$90, #$91, #$92, #$93, #$94, #$95, #$96, #$97, #$98, #$99, #$9A, #$9B, #$9C, #$9D, #$9E, #$9F, #$A0, #$A1, #$A2, #$A3, #$A4, #$A5, #$A6, #$A7, #$A8, #$A9, #$AA, #$AB, #$AC, #$AD, #$AE, #$AF, #$B0, #$B1, #$B2, #$B3, #$B4, #$B5, #$B6, #$B7, #$B8, #$B9, #$BA, #$BB, #$BC, #$BD, #$BE, #$BF, #$FE, #$E0, #$E1, #$F6, #$E4, #$E5, #$F4, #$E3, #$F5, #$E8, #$E9, #$EA, #$EB, #$EC, #$ED, #$EE, #$EF, #$FF, #$F0, #$F1, #$F2, #$F3, #$E6, #$E2, #$FC, #$FB, #$E7, #$F8, #$FD, #$F9, #$F7, #$FA, #$DE, #$C0, #$C1, #$D6, #$C4, #$C5, #$D4, #$C3, #$D5, #$C8, #$C9, #$CA, #$CB, #$CC, #$CD, #$CE, #$CF, #$DF, #$D0, #$D1, #$D2, #$D3, #$C6, #$C2, #$DC, #$DB, #$C7, #$D8, #$DD, #$D9, #$D7, #$DA); //--------------------------------------------------------------------------- function RecodeChar( Ch : char; const Table : TCharRecodeTable ) : char; begin Result := Table[ Ch ]; end; //--------------------------------------------------------------------------- function CharWinToKOI8( Ch : char ) : char; begin Result := WinToKOI8Table[ Ch ]; end; //--------------------------------------------------------------------------- function CharKOI8ToWin( Ch : char ) : char; begin Result := KOI8ToWinTable[ Ch ]; end; //--------------------------------------------------------------------------- function RecodeStr( Source : string; const Table : TCharRecodeTable ) : string; var i : integer; begin Result := ''; for i := 1 to length( Source ) do Result := Result + Table[ Source[i] ]; end; //---------------------------------------------------------------------------
Вариант №1 (17.01.00) Автор: Дмитрий В. Полщанов const Koi: Array[0..66] of Char = ('Ј', 'Ё', 'ё', 'А', 'Б', 'В', 'Г', 'Д', 'Е', 'Ж', 'З', 'И', 'Й', 'К', 'Л', 'М', 'Н', 'О', 'П', 'Р', 'С', 'Т', 'У', 'Ф', 'Х', 'Ц', 'Ч', 'Ш', 'Щ', 'Ъ', 'Ы', 'Ь', 'Э', 'Ю', 'Я', 'а', 'б', 'в', 'г', 'д', 'е', 'ж', 'з', 'и', 'й', 'к', 'л', 'м', 'н', 'о', 'п', 'р', 'с', 'т', 'у', 'ф', 'х', 'ц', 'ч', 'ш', 'щ', 'ъ', 'ы', 'ь', 'э', 'ю', 'я'); Win: Array[0..66] of Char = ('ё', 'Ё', 'Ј', 'ю', 'а', 'б', 'ц', 'д', 'е', 'ф', 'г', 'х', 'и', 'й', 'к', 'л', 'м', 'н', 'о', 'п', 'я', 'р', 'с', 'т', 'у', 'ж', 'в', 'ь', 'ы', 'з', 'ш', 'э', 'щ', 'ч', 'ъ', 'Ю', 'А', 'Б', 'Ц', 'Д', 'Е', 'Ф', 'Г', 'Х', 'И', 'Й', 'К', 'Л', 'М', 'Н', 'О', 'П', 'Я', 'Р', 'С', 'Т', 'У', 'Ж', 'В', 'Ь', 'Ы', 'З', 'Ш', 'Э', 'Щ', 'Ч', 'Ъ'); //--------------------------------------------------------------------------- function WinToKoi(Str: String): String; var i, j, Index: Integer; begin Result := ''; for i := 1 to Length(Str) do begin Index := -1; for j := Low(Win) to High(Win) do if Win[j] = Str[i] then begin Index := j; Break; end; if Index = -1 then Result := Result + Str[i] else Result := Result + Koi[Index]; end; end; //--------------------------------------------------------------------------- function KoiToWin(Str: String): String; var i, j, Index: Integer; begin Result := ''; for i := 1 to Length(Str) do begin Index := -1; for j := Low(Win) to High(Win) do if Koi[j] = Str[i] then begin Index := j; Break; end; if Index = -1 then Result := Result + Str[i] else Result := Result + Win[Index]; end; end; //--------------------------------------------------------------------------- procedure SendFileOnSMTP(Host: String; Port: Integer; Subject, FromAddress, ToAddress, Body, FileName: String); var NMSMTP: TNMSMTP; begin if DelSpace(ToAddress) = '' then Exit; if ToAddress[1] = ';' then Exit; if (DelSpace(FileName) <> '') and not FileExists(FileName) then raise Exception.Create('SendFileOnSMTP: file not exist: ' + FileName); NMSMTP := TNMSMTP.Create(nil); try NMSMTP.Host := Host; NMSMTP.Port := Port; NMSMTP.Charset := 'koi8-r'; NMSMTP.PostMessage.FromAddress := FromAddress; NMSMTP.PostMessage.ToAddress.Text := ToAddress; NMSMTP.PostMessage.Attachments.Text := FileName; NMSMTP.PostMessage.Subject := Subject; NMSMTP.PostMessage.Date := DateTimeToStr(Now); NMSMTP.UserID := 'netmaster'; NMSMTP.PostMessage.Body.Text := WinToKoi(Body); NMSMTP.FinalHeader.Clear; NMSMTP.TimeOut := 5000; NMSMTP.Connect; NMSMTP.SendMail; NMSMTP.Disconnect; finally NMSMTP.Free; end; end; //---------------------------------------------------------------------------
DelphiVCLFAQ
| DELPHI VCL FAQ Перевод с английского |
Подборку, перевод и адаптацию материала подготовил Aziz(JINX) специально для Королевства Дельфи. Скачать (38 K) для просмотра в off-line.
Вернуться к разделу
Вопрос: Как разместить прозрачную надпись на TBitmap? Пример: procedure TForm1.Button1Click(Sender: TObject); var OldBkMode : integer; begin Image1.Picture.Bitmap.Canvas.Font.Color := clBlue; OldBkMode := SetBkMode(Image1.Picture.Bitmap.Canvas.Handle,TRANSPARENT); Image1.Picture.Bitmap.Canvas.TextOut(10, 10, 'Hello'); SetBkMode(Image1.Picture.Bitmap.Canvas.Handle,OldBkMode); end; Вопрос: Можно ли обратиться к колонке или строке grid'а по заголовку? Ответ: В следующем примере приведены две функции: GetGridColumnByName() и GetGridRowByName(), которые возвращают колонку или строку, имеющую заданный заголовок (caption). Пример: procedure TForm1.FormCreate(Sender: TObject); begin StringGrid1.Rows[1].Strings[0] := 'This Row'; StringGrid1.Cols[1].Strings[0] := 'This Column'; end; function GetGridColumnByName(Grid : TStringGrid; ColName : string): integer; var i : integer; begin for i := 0 to Grid.ColCount - 1 do if Grid.Rows[0].Strings[i] = ColName then begin Result := i; exit; end; Result := -1; end; function GetGridRowByName(Grid : TStringGrid; RowName : string): integer; var i : integer; begin for i := 0 to Grid.RowCount - 1 do if Grid.Cols[0].Strings[i] = RowName then begin Result := i; exit; end; Result := -1; end; procedure TForm1.Button1Click(Sender: TObject); var Column : integer; Row : integer; begin Column := GetGridColumnByName(StringGrid1, 'This Column'); if Column = -1 then ShowMessage('Column not found') else ShowMessage('Column found at ' + IntToStr(Column)); Row := GetGridRowByName(StringGrid1, 'This Row'); if Row = -1 then ShowMessage('Row not found') else ShowMessage('Row found at ' + IntToStr(Row)); end; Вопрос: Как использовать клавишу-акселератор в TTabsheets? Я добавляю клавишу-акселератор в заголовок каждого Tabsheet моего PageControl, но при попытке переключать страницы этой клавишей программа пикает и ничего не происходит. Ответ: Можно перехватить сообщение CM_DIALOGCHAR. Пример: type TForm1 = class(TForm) PageControl1: TPageControl; TabSheet1: TTabSheet; TabSheet2: TTabSheet; TabSheet3: TTabSheet; private {Private declarations} procedure CMDialogChar(var Msg:TCMDialogChar); message CM_DIALOGCHAR; public {Public declarations} end; var Form1: TForm1; implementation {$R *.DFM} procedure TForm1.CMDialogChar(var Msg:TCMDialogChar); var i : integer; begin with PageControl1 do begin if Enabled then for i := 0 to PageControl1.PageCount - 1 do if ((IsAccel(Msg.CharCode, Pages[i].Caption)) and (Pages[i].TabVisible)) then begin Msg.Result:=1; ActivePage := Pages[i]; exit; end; end; inherited; end; Вопрос: При использованиии компонента TRegistry под NT пользователь с права доступа ниже чем "администратор" не может получить доступа к информации реестра в ключе HKEY_LOCAL_MACHINE. Как это обойти? Ответ: Проблема вызвана тем, что TRegistry всегда открывает реестр с параметром KEY_ALL_ACCESS (полный доступ), даже если необходим доступ KEY_READ (только чтение). Избежать этого можно используя функции API для работы с реестром (RegOpenKey и т.п.), или создать новый класс из компонента TRegestry, и изменить его так чтобы можно было задавать режим открытия реестра. Вопрос: Можно ли изменить число колонок и их ширину в компоненте TFileListBox? Ответ: В приведенном примере FileListBox приводится к типу TDirectoryListBox - таким образом можно добавиь дополнительные колонки. Пример: with TDirectoryListBox(FileListBox1) do begin Columns := 2; SendMessage(Handle, LB_SETCOLUMNWIDTH, Canvas.TextWidth('WWWWWWWW.WWW'),0); end; Вопрос: Как настроить табуляцию в компоненте TMemo? Ответ: Пошлите в Memo сообщение EM_SETTABSTOPS. Например установим первую позицию табуляции на 20-й пиксел. Пример: procedure TForm1.FormCreate(Sender: TObject); var DialogUnitsX : LongInt; PixelsX : LongInt; i : integer; TabArray : array[0..4] of integer; begin Memo1.WantTabs := true; DialogUnitsX := LoWord(GetDialogBaseUnits); PixelsX := 20; for i := 1 to 5 do begin TabArray[i - 1] :=((PixelsX * i ) * 4) div DialogUnitsX; end; SendMessage(Memo1.Handle, EM_SETTABSTOPS,5,LongInt(@TabArray)); Memo1.Refresh; end; Вопрос: Как перехватить нажатия функциональных клавиш и стрелок? Ответ: Проверяйте значение переменной key на равенство VK_RIGHT, VK_LEFT, VK_F1 и т.д. на событии KeyDown формы. Пример: procedure TForm1.FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); begin if Key = VK_RIGHT then Form1.Caption := 'Right'; if Key = VK_F1 then Form1.Caption := 'F1'; end; Вопрос: При обработке события DrawCell компонента DrawGrid я пишу Font.Color := clRed; и получаю бесконечный цикл мерцаний. Почему? Ответ: Правильно укажите границы используемого канваса. Пример: If (Row = 0) then begin DrawGrid1.Canvas.Font.Color := clRed; DrawGrid1.Canvas.TextOut(Rect.Left,Rect.Top, IntToStr(Col)); end; Вопрос: При использовании 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; Вопрос: Можно ли изменить вид текстового курсора (каретки) edit'а или другого элемента управления Windows? Ответ: Можно! В примере показано как создать два цветных "bitmap'а": "улыбчивый" и "хмурый" и присвоить их курсору edit'а. Для этого нужно перехватить оконную процедуру edit'а. Чтобы сделать это заменим адрес оконной процедуры Edit'а нашим собственным, а старую оконную процедуру будем вызывать по необходимости. Пример показывает "улыбчивый" курсор при наборе текста и "хмурый" при забое клавишей backspace. Пример: unit caret1; interface {$IFDEF WIN32} uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls; {$ELSE} uses WinTypes, WinProcs, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls; {$ENDIF} type TForm1 = class(TForm) Edit1: TEdit; procedure FormCreate(Sender: TObject); procedure FormDestroy(Sender: TObject); private {Private declarations} public {Public declarations} CaretBm : TBitmap; CaretBmBk : TBitmap; OldEditsWindowProc : Pointer; end; var Form1: TForm1; implementation {$R *.DFM} type {$IFDEF WIN32} WParameter = LongInt; {$ELSE} WParameter = Word; {$ENDIF} LParameter = LongInt; {New windows procedure for the edit control} function NewWindowProc(WindowHandle : hWnd; TheMessage : WParameter; ParamW : WParameter; ParamL : LParameter) : LongInt {$IFDEF WIN32} stdcall; {$ELSE} ; export; {$ENDIF} begin {Call the old edit controls windows procedure} NewWindowProc := CallWindowProc(Form1.OldEditsWindowProc, WindowHandle, TheMessage, ParamW, ParamL); if TheMessage = WM_SETFOCUS then begin CreateCaret(WindowHandle, Form1.CaretBm.Handle, 0, 0); ShowCaret(WindowHandle); end; if TheMessage = WM_KILLFOCUS then begin HideCaret(WindowHandle); DestroyCaret; end; if TheMessage = WM_KEYDOWN then begin if ParamW = VK_BACK then CreateCaret(WindowHandle, Form1.CaretBmBk.Handle, 0, 0) else CreateCaret(WindowHandle, Form1.CaretBm.Handle, 0, 0); ShowCaret(WindowHandle); end; end; procedure TForm1.FormCreate(Sender: TObject); begin {Create a smiling bitmap using the wingdings font} CaretBm := TBitmap.Create; CaretBm.Canvas.Font.Name := 'WingDings'; CaretBm.Canvas.Font.Height := Edit1.Font.Height; CaretBm.Canvas.Font.Color := clWhite; CaretBm.Width := CaretBm.Canvas.TextWidth('J') + 2; CaretBm.Height := CaretBm.Canvas.TextHeight('J') + 2; CaretBm.Canvas.Brush.Color := clBlue; CaretBm.Canvas.FillRect(Rect(0, 0, CaretBm.Width, CaretBm.Height)); CaretBm.Canvas.TextOut(1, 1, 'J'); {Create a frowming bitmap using the wingdings font} CaretBmBk := TBitmap.Create; CaretBmBk.Canvas.Font.Name := 'WingDings'; CaretBmBk.Canvas.Font.Height := Edit1.Font.Height; CaretBmBk.Canvas.Font.Color := clWhite; CaretBmBk.Width := CaretBmBk.Canvas.TextWidth('L') + 2; CaretBmBk.Height := CaretBmBk.Canvas.TextHeight('L') + 2; CaretBmBk.Canvas.Brush.Color := clBlue; CaretBmBk.Canvas.FillRect(Rect(0,0, CaretBmBk.Width, CaretBmBk.Height)); CaretBmBk.Canvas.TextOut(1, 1, 'L'); {Hook the edit controls window procedure} OldEditsWindowProc := Pointer(SetWindowLong(Edit1.Handle,GWL_WNDPROC, LongInt(@NewWindowProc))); end; procedure TForm1.FormDestroy(Sender: TObject); begin {Unhook the edit controls window procedure and clean up} SetWindowLong(Edit1.Handle,GWL_WNDPROC, LongInt(OldEditsWindowProc)); CaretBm.Free; CaretBmBk.Free; end; Вопрос: При использовании модулей доступа к BDE (DbiTypes, DbiProcs, DbiErrs), любая попытка вызвать процедуру abort выдает ошибку при компиляции при вызове метода abort "Statement expected, but expression of type 'Integer' found". Я пытался найти DbiTypes.pas, DbiProcs.pas и DbiErrs.pas чтобы разобраться но не нашел этих файлов. Где расположены эти файлы и как обойти ошибку? Ответ: Модули DbiTypes, DbiProcs, DbiErrs это псевдонимы модуля "BDE", обьявлены в Projects->Options->Directories/Conditionals->Unit Aliases. Исходник модуля DBE находится в каталоге "doc" и называется "BDE.INT". В этом файле обьявленна константа ABORT со значением -2. Так как Вы хотите использовать процедуру Abort(), которая обьявлена в модуле SysUtils, Вам нужно добавить префикс SysUtils перед вызовом процедуры Abort. Пример: SysUtils.Abort; Вопрос: Почему при изменении цвета букв StatusBar'а ничего не происходит? Ответ: Status bar - стандартный элемент управления Windows, и соответственно цвет его букв - значение clBtnText которое изменяется с помощью настроек в Control Panel. Этот цвет черный по умолчанию и может изменяться в зависимости от выбранной цветовой схемы. Другие стандартные элемент управления Windows, например кнопки, также имеют цвет букв, настраиваемый из ControlPanel. StatusBar и его панели имеют свойство "owner-draw", позволяющее Вам использовать любой цвет букв. Пример: 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; Вопрос: Как сделать многострочную надпись на TBitBtn? Ответ: Выводите текст надписи непосредственно на "glyph" TBitBtn'а. См. пример. Пример: procedure TForm1.FormCreate(Sender: TObject); var R : TRect; N : Integer; Buff : array[0..255] of Char; begin with BitBtn1 do begin Caption := 'A really really long caption'; Glyph.Canvas.Font := Self.Font; Glyph.Width := Width - 6; Glyph.Height := Height - 6; R := Bounds(0, 0, Glyph.Width, 0); StrPCopy(Buff, Caption); Caption := ''; DrawText(Glyph.Canvas.Handle,Buff,StrLen(Buff),R, DT_CENTER or DT_WORDBREAK or DT_CALCRECT); OffsetRect(R,(Glyph.Width - R.Right) div 2, (Glyph.Height - R.Bottom) div 2); DrawText(Glyph.Canvas.Handle,Buff,StrLen(Buff),R, DT_CENTER or DT_WORDBREAK); end; end; Вопрос: Как изменить стиль шрифта RichEdit нажатиями соответствующих комбинаций клавиш? (например включить курсив по нажатию Ctrl + I) Ответ: В примере стили шрифта меняются по нажатию след. комбинаций клавиш Ctrl + B - вкл/выкл жирного шрифта Ctrl + I - вкл/выкл наклонного шрифта Ctrl + S - вкл/выкл зачеркнутого шрифта Ctrl + U - вкл/выкл подчеркнутого шрифта Пример: const KEY_CTRL_B = 02; KEY_CTRL_I = 9; KEY_CTRL_S = 19; KEY_CTRL_U = 21; procedure TForm1.RichEdit1KeyPress(Sender: TObject; var Key: Char); begin case Ord(Key) of KEY_CTRL_B: begin Key := #0; if fsBold in (Sender as TRichEdit).SelAttributes.Style then (Sender as TRichEdit).SelAttributes.Style := (Sender as TRichEdit).SelAttributes.Style - [fsBold] else (Sender as TRichEdit).SelAttributes.Style := (Sender as TRichEdit).SelAttributes.Style + [fsBold]; end; KEY_CTRL_I: begin Key := #0; if fsItalic in (Sender as TRichEdit).SelAttributes.Style then (Sender as TRichEdit).SelAttributes.Style := (Sender as TRichEdit).SelAttributes.Style - [fsItalic] else (Sender as TRichEdit).SelAttributes.Style := (Sender as TRichEdit).SelAttributes.Style + [fsItalic]; end; KEY_CTRL_S: begin Key := #0; if fsStrikeout in (Sender as TRichEdit).SelAttributes.Style then (Sender as TRichEdit).SelAttributes.Style := (Sender as TRichEdit).SelAttributes.Style-[fsStrikeout] else (Sender as TRichEdit).SelAttributes.Style := (Sender as TRichEdit).SelAttributes.Style+[fsStrikeout]; end; KEY_CTRL_U: begin Key := #0; if fsUnderline in (Sender as TRichEdit).SelAttributes.Style then (Sender as TRichEdit).SelAttributes.Style := (Sender as TRichEdit).SelAttributes.Style-[fsUnderline] else (Sender as TRichEdit).SelAttributes.Style := (Sender as TRichEdit).SelAttributes.Style+[fsUnderline]; end; end; end; Вопрос: В документации компонента TRegIniFile говорится, что можно изменять корневой ключ (root key). Я пытаюсь это сделать но ничего не получается. Ответ: См. пример. Пример: uses Registry; procedure TForm1.Button1Click(Sender: TObject); var WinIni : TRegIniFile; begin WinIni := TRegIniFile.Create(''); WinIni.RootKey := HKEY_LOCAL_MACHINE; WinIni.WriteString('Frank','Borland','Writes Fast Code!'); WinIni.Free; end; Вопрос: Можно ли динамически изменять свойство "owner" компонента во время выполнения программы? Ответ: Вы можете менять свойство "owner" и после создания компонента с помощью методов InsertComponent() и RemoveComponent(). Вопрос: Как очистить содержимое Canvas'а? Ответ: Просто нарисуйте прямоугольник любого цвета. Пример: Canvas.Brush.Color := ClWhite; Canvas.FillRect(Canvas.ClipRect); Вопрос: Можно ли динамически менять какая форма считается главной в приложении во время работы программы? Ответ: Можно, но только во время загрузки приложения. Чтобы сделать это выберите "View->Project Source" и измените код инициализации приложения, так что порядок создания форм зависил от какого-то условия. Примечание: Вам придется редактировать этот код, если Вы добавите в приложение новые формы. begin Application.Initialize; if then begin Application.CreateForm(TForm1, Form1); Application.CreateForm(TForm2, Form2); end else begin Application.CreateForm(TForm2, Form2); Application.CreateForm(TForm1, Form1); end; end. Application.Run; Вопрос: Как программно "щелкнуть" по компоненту speed button? Я пытался использовать SendMessage но у Speedbuttons нет "handle". Наверх к содержанию Вопрос: Можно ли отключить определенный элемент в RadioGroup? Ответ: В примере показано как получить доступ к отдельным элементам компонента TRadioGroup. Пример: procedure TForm1.Button1Click(Sender: TObject); begin TRadioButton(RadioGroup1.Controls[1]). Enabled := False; end; Вопрос: Почему методы рисования Delphi (например MoveTo и LineTo) рисуют на один пиксел короче? Ответ: Так работает большинство графических систем, включая Windows. Библиотека VCL просто передает вызовы в функции GDI. Если Вы хотите нарисовать линию с последним пикселом включительно просто добавте единицу к координатам. Вопрос: Как показать подсказки "hints" для элементов меню? Ответ: В примере создается обработчик события Application.Hint - подсказки меню изображаются на status panel. Пример: type TForm1 = class(TForm) Panel1: TPanel; MainMenu1: TMainMenu; MenuItemFile: TMenuItem; MenuItemOpen: TMenuItem; MenuItemClose: TMenuItem; OpenDialog1: TOpenDialog; procedure FormCreate(Sender: TObject); procedure MenuItemCloseClick(Sender: TObject); procedure MenuItemOpenClick(Sender: TObject); private {Private declarations} procedure HintHandler(Sender: TObject); public {Public declarations} end; var Form1: TForm1; implementation {$R *.DFM} procedure TForm1.FormCreate(Sender: TObject); begin Panel1.Align := alBottom; MenuItemFile.Hint := 'File Menu'; MenuItemOpen.Hint := 'Opens A File'; MenuItemClose.Hint := 'Closes the Application'; Application.OnHint := HintHandler; end; procedure TForm1.HintHandler(Sender: TObject); begin Panel1.Caption := Application.Hint; end; procedure TForm1.MenuItemCloseClick(Sender: TObject); begin Application.Terminate; end; procedure TForm1.MenuItemOpenClick(Sender: TObject); begin if OpenDialog1.Execute then Form1.Caption := OpenDialog1.FileName; end; Вопрос: Как опеделить состояние списка ComboBox, выпал/скрыт? Ответ: Пошлите ComboBox сообщение CB_GETDROPPEDSTATE. Пример: if SendMessage(ComboBox1.Handle, CB_GETDROPPEDSTATE,0,0) = 1 then begin {список ComboBox выпал} end; Вопрос: Как удалить каталог вместе со всеми содержащимися в нем файлами? Ответ: В примере стираются все файлы в каталоге и сам каталог. Чтобы удалить файл, помечанные только для чтения (read only) и занятые другими программами в момент удаления - напишите дополнительную процедуру. procedure TForm1.Button1Click(Sender: TObject); var DirInfo: TSearchRec; r: integer; begin r := FindFirst('C:\Download\*.*', FaAnyfile, DirInfo); while r = 0 do begin if ((DirInfo.Attr and FaDirectory <> FaDirectory) and (DirInfo.Attr and FaVolumeId <> FaVolumeID)) then if DeleteFile(pChar('C:\Download\' + DirInfo.Name))= false then ShowMessage('Unable to delete: C:\Download\'+DirInfo.Name); r := FindNext(DirInfo); end; SysUtils.FindClose(DirInfo); if RemoveDirectory('C:\Download\') = false then ShowMessage('Unable to delete directory: C:\Download\'); end; Вопрос: Как отключить системное меню формы и кнопки Minimize, Maximize, and Close во время выполнения(Runtime)? Ответ: В приведенном примере показано как это сделать Пример: procedure TForm1.Button1Click(Sender: TObject); begin {Disable} Form1.BorderIcons := Form1.BorderIcons - [biSystemMenu, biMinimize, biMaximize]; end; procedure TForm1.Button2Click(Sender: TObject); begin {Enable} Form1.BorderIcons := Form1.BorderIcons + [biSystemMenu, biMinimize, biMaximize]; end; Вопрос: Как извлечь Red, Green, и Blue компонент из определенного цвета? Ответ: Используйте функции Window API Get RValue(), GetGValue(), и GetBValue(). Пример: procedure TForm1.Button1Click(Sender: TObject); begin Form1.Canvas.Pen.Color := clRed; Memo1.Lines.Add('Red := ' + IntToStr(GetRValue(Form1.Canvas.Pen.Color))); Memo1.Lines.Add('Red := ' + IntToStr(GetGValue(Form1.Canvas.Pen.Color))); Memo1.Lines.Add('Blue:= ' + IntToStr(GetBValue(Form1.Canvas.Pen.Color))); end; Вопрос: Как определить номер текущей строки в TMemo? Ответ: Чтобы определить номер текущей строки любого объекта управления edit - пошлите ей сообщение EM_LINEFROMCHAR Пример: procedure TForm1.Button1Click(Sender: TObject); var LineNumber : integer; begin LineNumber := SendMessage(Memo1.Handle, EM_LINEFROMCHAR, word(-1), 0); ShowMessage(IntToStr(LineNumber)); end; Вопрос: Как проигрываеть MPEG файл в Delphi-программе? Ответ: Если в системе Windows MMSystem установлен декодер MPEG - используя компонент TMediaPlayer Пример: procedure TForm1.Button1Click(Sender: TObject); begin MediaPlayer1.Filename := 'C:\DownLoad\rsgrow.mpg'; MediaPlayer1.Open; MediaPlayer1.Display := Panel1; MediaPlayer1.DisplayRect := Panel1.ClientRect; MediaPlayer1.Play; end; Вопрос: Как использовать анимированный курсор? Ответ: Во первых необходимо получит handle курсора, а затем определить его в массиве курсоров компонента TScreen. Индексы предопределенных курсоров системы отрицательны, пользователь может определить курсор, индекс которого положителен. Пример: procedure TForm1.Button1Click(Sender: TObject); var h : THandle; begin h := LoadImage(0, 'C:\TheWall\Magic.ani', IMAGE_CURSOR, 0, 0, LR_DEFAULTSIZE or LR_LOADFROMFILE); if h = 0 then ShowMessage('Cursor not loaded') else begin Screen.Cursors[1] := h; Form1.Cursor := 1; end; end; Вопрос: Как узнать о нажатии "non-menu" клавиши в момент когда меню показано? Ответ: Создайте обработчик сообщения WM_MENUCHAR. Пример: unit Unit1; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, Menus; type TForm1 = class(TForm) MainMenu1: TMainMenu; One1: TMenuItem; Two1: TMenuItem; THree1: TMenuItem; private {Private declarations} procedure WmMenuChar(var m : TMessage); message WM_MENUCHAR; public {Public declarations} end; var Form1: TForm1; implementation {$R *.DFM} procedure TForm1.WmMenuChar(var m : TMessage); begin Form1.Caption := 'Non standard menu key pressed'; m.Result := 1; end; end. Вопрос: Как определить наличие сопроцессора? Ответ: В отличие от общепринятого мнения не всее клоны 486/586/686/ и Pentium имеют сопроцессор для вычислений с плавающей запятой. В примере определяется наличие сопроцессора и под Win16 и под Win32. Пример: {$IFDEF WIN32} uses Registry; {$ENDIF} function HasCoProcesser : bool; {$IFDEF WIN32} var TheKey : hKey; {$ENDIF} begin Result := true; {$IFNDEF WIN32} if GetWinFlags and Wf_80x87 = 0 then Result := false; {$ELSE} if RegOpenKeyEx(HKEY_LOCAL_MACHINE, 'HARDWARE\DESCRIPTION\System\FloatingPointProcessor',0, KEY_EXECUTE, TheKey) <> ERROR_SUCCESS then result := false; RegCloseKey(TheKey); {$ENDIF} end; procedure TForm1.Button1Click(Sender: TObject); begin if HasCoProcesser then ShowMessage('Has CoProcessor') else ShowMessage('No CoProcessor - Windows Emulation Mode'); end; Вопрос: Как узнать серийный номер аудио CD? Ответ: CD может иметь или не иметь серийный номер и/или универсальный код продукта (Universal Product Code). MCI-расширение Windows предоставляет эту информации с помощью комманды MCI_INFO_MEDIA_IDENTITY command. Эта команда возвращает уникальную ID-строку. Пример: uses MMSystem, MPlayer; procedure TForm1.Button1Click(Sender: TObject); var mp : TMediaPlayer; msp : TMCI_INFO_PARMS; MediaString : array[0..255] of char; ret : longint; begin mp := TMediaPlayer.Create(nil); mp.Visible := false; mp.Parent := Application.MainForm; mp.Shareable := true; mp.DeviceType := dtCDAudio; mp.FileName := 'D:'; mp.Open; Application.ProcessMessages; FillChar(MediaString, sizeof(MediaString), #0); FillChar(msp, sizeof(msp), #0); msp.lpstrReturn := @MediaString; msp.dwRetSize := 255; ret := mciSendCommand(Mp.DeviceId, MCI_INFO, MCI_INFO_MEDIA_IDENTITY, longint(@msp)); if Ret <> 0 then begin MciGetErrorString(ret, @MediaString, sizeof(MediaString)); Memo1.Lines.Add(StrPas(MediaString)); end else Memo1.Lines.Add(StrPas(MediaString)); mp.Close; Application.ProcessMessages; mp.free; end; end. Вопрос: Как вывести на элемент управления (Window control) текст, содержащий амперсанд - & ? Ответ: Используя два амперсанда подряд. Windows интерпритирует одиночный амперсанд как указание на то, что следующий символ - горячая клавиша (и поддчеркивает следующий символ вместо излбражения аперсанда). Пример: Button1.Caption := 'Черное && Белое'; Вопрос: Как поместить bitmap в Metafile? Ответ: см. пример Пример: procedure TForm1.Button1Click(Sender: TObject); var m : TmetaFile; mc : TmetaFileCanvas; b : tbitmap; begin m := TMetaFile.Create; b := TBitmap.create; b.LoadFromFile('C:\SomePath\SomeBitmap.BMP'); m.Height := b.Height; m.Width := b.Width; mc := TMetafileCanvas.Create(m, 0); mc.Draw(0, 0, b); mc.Free; b.Free; m.SaveToFile('C:\SomePath\Test.emf'); m.Free; Image1.Picture.LoadFromFile('C:\SomePath\Test.emf'); end; Вопрос: Как узнать, что курсор мыши над моей формой? Ответ: Можно использовать функцию GetCapture() из Windows API. Примечание: Cм. документацию Windows для информации об ограничениях функции GetCapture. Пример: procedure TForm1.FormDeactivate(Sender: TObject); begin ReleaseCapture; end; procedure TForm1.FormMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer); begin If GetCapture = 0 then SetCapture(Form1.Handle); if PtInRect(Rect(Form1.Left,Form1.Top,Form1.Left + Form1.Width, Form1.Top + Form1.Height), ClientToScreen(Point(x, y))) then Form1.Caption := 'Мышка над формой!' else Form1.Caption := 'Мышка вне формы...'; end; Вопрос: Как программно определить, что приложение работает под Windows NT? Ответ:см. пример Пример: function IsNT : bool; var osv : TOSVERSIONINFO; begin result := true; GetVersionEx(osv); if osv.dwPlatformId = VER_PLATFORM_WIN32_NT then exit; result := false; end; procedure TForm1.Button1Click(Sender: TObject); begin if IsNt then ShowMessage('Running on NT') else ShowMessage('Not Running on NT'); end; Вопрос: Как создать bitmap из пиктогрммы (icon)? Ответ: Используя Bitmap.Canvas.Draw нарисуйте пиктограмму на Bitmap'е. Пример: procedure TForm1.Button1Click(Sender: TObject); var TheIcon : TIcon; TheBitmap : TBitmap; begin TheIcon := TIcon.Create; TheIcon.LoadFromFile('C:\Program Files\Borland\IcoCur32\EARTH.ICO'); TheBitmap := TBitmap.Create; TheBitmap.Height := TheIcon.Height; TheBitmap.Width := TheIcon.Width; TheBitmap.Canvas.Draw(0, 0, TheIcon); Form1.Canvas.Draw(10, 10, TheBitmap); TheBitmap.Free; TheIcon.Free; end; Вопрос: Как создать отдельную подсказку (hint) для каждой ячейки StringGrid? Ответ: В приведенном примере отслеживается движение курсора мыши - при перемещении между ячейками StringGrid'а - появляется окно подсказки(hint), показываеющее номер текущей строки и колонки. Пример: type TForm1 = class(TForm) StringGrid1: TStringGrid; procedure StringGrid1MouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer); procedure FormCreate(Sender: TObject); private {Private declarations} Col : integer; Row : integer; public {Public declarations} end; var Form1: TForm1; implementation {$R *.DFM} procedure TForm1.FormCreate(Sender: TObject); begin StringGrid1.Hint := '0 0'; StringGrid1.ShowHint := True; end; procedure TForm1.StringGrid1MouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer); var r : integer; c : integer; begin StringGrid1.MouseToCell(X, Y, C, R); with StringGrid1 do begin if ((Row <> r) or(Col <> c)) then begin Row := r; Col := c; Application.CancelHint; StringGrid1.Hint :=IntToStr(r)+#32+IntToStr(c); end; end; end; Вопрос: Как внести изменения в код VCL? Ответ: Примечание: внесение изменений в VCL не поддерживается Borland или Borland Developer Support. -Но если Вы решили сделать это... Изменеия в код VCL никогда не должны вносится в секцию "interface" модуля - только в секцию "implimentation". Наиболее безопасный способ внести изменения в VCL - создать новый каталог названный "исправленный VCL". Скопируйте файл VCL который Вы хотите изменить в этот каталог. Внесите изменения (лучше прокомментировать их) в этот файл. Затем добавьте путь к Вашему каталогу "исправленный VCL" в самое начало "library path". Перезапустите Delphi/C++ Builder и перекомпилируйте Ваш проект. "library path" можно изменить в меню: Delphi 1 : Options | Environment | Library Delphi 2 : Tools | Options | Library Delphi 3 : Tools | Environment Options | Library Delphi 4 : Tools | Environment Options | Library C++ Builder : Options | Environment | Library Вопрос: Как в Delphi реализовать функцию - эквивалент TwipsPerPixel из VisualBasic? Ответ: Функции TwipsPerPixelX и TwipsPerPixelY, приведенные в примере реализуют ту же функциональность в Delphi. Пример: function TwipsPerPixelX(Canvas : TCanvas) : Extended; begin result := 1440 / GetDeviceCaps(Canvas.Handle, LOGPIXELSX); end; function TwipsPerPixelY(Canvas : TCanvas) : Extended; begin result := 1440 / GetDeviceCaps(Canvas.Handle, LOGPIXELSY); end; procedure TForm1.Button1Click(Sender: TObject); begin ShowMessage(FloatToStr(TwipsPerPixelX(Form1.Canvas))); ShowMessage(FloatToStr(TwipsPerPixelY(Form1.Canvas))); end; Вопрос: Как вставить содержимое файла в текущую позицию курсора в компонете TMemo? Ответ: Считайте файл в TMemoryStream, затем ипользуйте метод TMemo SetSelTextBuf() для вставки текста; var TheMStream : TMemoryStream; Zero : char; begin TheMStream := TMemoryStream.Create; TheMStream.LoadFromFile('C:\AUTOEXEC.BAT'); TheMStream.Seek(0, soFromEnd); //Null terminate the buffer! Zero := #0; TheMStream.Write(Zero, 1); TheMStream.Seek(0, soFromBeginning); Memo1.SetSelTextBuf(TheMStream.Memory); TheMStream.Free; end; Вопрос: Как в компоненте TMemo перехватить нажатие Ctrl-V и вставить специальный текст не из буфера обмена (clipboard)? Ответ: См. пример. Пример: uses ClipBrd; procedure TForm1.Memo1KeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); begin if ((Key = ord('V')) and (ssCtrl in Shift)) then begin if Clipboard.HasFormat(CF_TEXT) then ClipBoard.Clear; Memo1.SelText := 'Delphi is RAD!'; key := 0; end; end; Вопрос: Как создать эквивалент TEdit но только с выравниваением вводимого текста по центру или по правой стороне? Ответ: TEdit не поддерживает выравниваение текста по центру и по правой стороне - лучше использовать компонент TMemo. Вам понадобится запретить пользователю нажимать Enter, Ctrl-Enter и всевозможные комбинации клавиш со стрелками, чтобы избежать появления нескольких сторк в Memo. Этого можно добиться и просматривая содержимое текста в TMemo в поисках кода возврата каретки (13) и перевода строки(10) на событиях TMemo Change и KeyPress. Можно также заменять код возврата каретки на пробел - для того чтобы позволять вставку из буфера обмена многострочного текста в виде одной строки. Пример: procedure TForm1.FormCreate(Sender: TObject); begin Memo1.Alignment := taRightJustify; Memo1.MaxLength := 24; Memo1.WantReturns := false; Memo1.WordWrap := false; end; procedure MultiLineMemoToSingleLine(Memo : TMemo); var t : string; begin t := Memo.Text; if Pos(#13, t) > 0 then begin while Pos(#13, t) > 0 do delete(t, Pos(#13, t), 1); while Pos(#10, t) > 0 do delete(t, Pos(#10, t), 1); Memo.Text := t; end; end; procedure TForm1.Memo1Change(Sender: TObject); begin MultiLineMemoToSingleLine(Memo1); end; procedure TForm1.Memo1KeyPress(Sender: TObject; var Key: Char); begin MultiLineMemoToSingleLine(Memo1); end; Вопрос: Как запрограммировать undo? Ответ:См. пример Memo1.Perform(EM_UNDO, 0, 0); Если Вы хотите узнать, возможно ли выполнить операцию "Undo", проверьте "Undo status": If Memo1.Perform(EM_CANUNDO, 0, 0) <> 0 then begin {Undo is possible} end; Для выполнения "Redo" выполните "Undo" еще раз. Вопрос: Можно ли создать форму, которая получает дополнительные параметры в методе Сreate? Ответ: Просто замените конструктор Create класса Вашей формы. Пример: unit Unit2; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs; type TForm2 = class(TForm) private {Private declarations} public constructor CreateWithCaption(aOwner: TComponent; aCaption: string); {Public declarations} end; var Form2: TForm2; implementation {$R *.DFM} constructor TForm2.CreateWithCaption(aOwner: TComponent; aCaption: string); begin Create(aOwner); Caption := aCaption; end; uses Unit2; procedure TForm1.Button1Click(Sender: TObject); begin Unit2.Form2 :=Unit2.TForm2.CreateWithCaption(Application, 'My Caption'); Unit2.Form2.Show; end; Вопрос: Почему при изменении цвета шрифта в StatusBar's он (шрифт) не меняется? Ответ: Status bar (строка состояния) - стандартный элемент управления Windows и цвет его шрифта задается через Control Panel (константа clBtnText). Этот цвет по умолчанию черный и может меняться при выборе пользователем той или иной цветовой схемы. У компонента ТStatusBar и его панелей есть возможность "owner-draw" - программной перерисовки, которая позволяет выводить на панель текст любого цвета. Измените свойство Style компонента TStatusBar.Panels на 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; Вопрос: Как бы мне создать эдакий trackbar в котором вместо широкой белой полоски с ползунком была бы тонкая линия? Ответ: В примере создается компонент, унаследованный от TTrackbar который переопределяет метод CreateParams и убират флаг TBS_ENABLESELRANGE из Style. Константа TBS_ENABLESELRANGE обьявленна в модуле CommCtrl. Пример: uses CommCtrl, ComCtrls; type TMyTrackBar = class(TTrackBar) procedure CreateParams(var Params: TCreateParams); override; end; procedure TMyTrackBar.CreateParams(var Params: TCreateParams); begin inherited; Params.Style := Params.Style and not TBS_ENABLESELRANGE; end; var MyTrackbar : TMyTrackbar; procedure TForm1.Button1Click(Sender: TObject); begin MyTrackBar := TMyTrackbar.Create(Form1); MyTrackbar.Parent := Form1; MyTrackbar.Left := 100; MyTrackbar.Top := 100; MyTrackbar.Width := 150; MyTrackbar.Height := 45; MyTrackBar.Visible := true; end; Вопрос: Мне нужен временный canvas, но когда я пытаюсь его создать получаю сообщения об ошибках. Как создать TCanvas? Ответ: Создайте Bitmap и используйте свойство canvas класса TBitmap. Пример создает Bitmap, рисует на его canvas'е, выводит этот canvas на форму и освобождает bitmap. Пример: procedure TForm1.Button1Click(Sender: TObject); var bm : TBitmap; begin bm := TBitmap.Create; bm.Width := 100; bm.Height := 100; bm.Canvas.Brush.Color := clRed; bm.Canvas.FillRect(Rect(0, 0, 100, 100)); bm.Canvas.MoveTo(0, 0); bm.Canvas.LineTo(100, 100); Form1.Canvas.StretchDraw(Form1.ClientRect,Bm); bm.Free; end; Вопрос: В некоторых видео режимах прозрачная часть glyph'а стандартного TBitBtn становится видной. Как этого избежать? Ответ: В примере используется техника закраски прозрачной части glyph'а цветом кнопки на которой он находится - таким образом glyph кажется прозрачным. Пример: function InitStdBitBtn(BitBtn : TBitBtn; kind : TBitBtnKind) : bool; var Bm1 : TBitmap; Bm2 : TBitmap; begin Result := false; if Kind = bkCustom then exit; Bm1 := TBitmap.Create; case Kind of bkOK : Bm1.Handle := LoadBitmap(hInstance, 'BBOK'); bkCancel : Bm1.Handle := LoadBitmap(hInstance, 'BBCANCEL'); bkHelp : Bm1.Handle := LoadBitmap(hInstance, 'BBHELP'); bkYes : Bm1.Handle := LoadBitmap(hInstance, 'BBYES'); bkNo : Bm1.Handle := LoadBitmap(hInstance, 'BBNO'); bkClose : Bm1.Handle := LoadBitmap(hInstance, 'BBCLOSE'); bkAbort : Bm1.Handle := LoadBitmap(hInstance, 'BBABORT'); bkRetry : Bm1.Handle := LoadBitmap(hInstance, 'BBRETRY'); bkIgnore : Bm1.Handle := LoadBitmap(hInstance, 'BBIGNORE'); bkAll : Bm1.Handle := LoadBitmap(hInstance, 'BBALL'); end; Bm2 := TBitmap.Create; Bm2.Width := Bm1.Width; Bm2.Height := Bm1.Height; Bm2.Canvas.Brush.Color := ClBtnFace; Bm2.Canvas.BrushCopy(Rect(0, 0, bm2.Width, bm2.Height), Bm1, Rect(0, 0, Bm1.width, Bm1.Height), Bm1.canvas.pixels[0,0]); Bm1.Free; LockWindowUpdate(BitBtn.Parent.Handle); BitBtn.Kind := kind; BitBtn.Glyph.Assign(bm2); LockWindowUpdate(0); Bm2.Free; Result := true; end; procedure TForm1.Button1Click(Sender: TObject); begin InitStdBitBtn(BitBtn1, bkOk); end; Вопрос: Создание PolyPolygon используя массив точек? Ответ: Polygon - метод компонента TCanvas получает в качестве параметра динамический массив точек. Функция PolyPolygon() из Windows GDI получает указатель на массив точек. Пример: procedure TForm1.Button1Click(Sender: TObject); var ptArray : array[0..9] of TPOINT; PtCounts : array[0..1] of integer; begin PtArray[0] := Point(0, 0); PtArray[1] := Point(0, 100); PtArray[2] := Point(100, 100); PtArray[3] := Point(100, 0); PtArray[4] := Point(0, 0); PtCounts[0] := 5; PtArray[5] := Point(25, 25); PtArray[6] := Point(25, 75); PtArray[7] := Point(75, 75); PtArray[8] := Point(75, 25); PtArray[9] := Point(25, 25); PtCounts[1] := 5; PolyPolygon(Form1.Canvas.Handle, PtArray,PtCounts,2); end; Вопрос: Как создать невизуальный компонент без иконоки, которая изображается в палитре компонентов в "design-time" (вроде TField)? Ответ: Невизуальные компоненты без иконоки удобны для субкомпонентов, связанных с какими-то другими компонентами. Создайте компонент как обычно, но используйте RegisterNoIcon вместо RegisterComponent. Вопрос: Как показывать нестандартный встроенный редактор (inplace editor) в ячейке stringgrid (например combobox). Ответ: См. пример Пример: procedure TForm1.FormCreate(Sender: TObject); begin {Высоту combobox'а не изменишь, так что вместо combobox'а будем изменять высоту строки grid'а !} StringGrid1.DefaultRowHeight := ComboBox1.Height; {Спрятать combobox} ComboBox1.Visible := False; ComboBox1.Items.Add('Delphi Kingdom'); ComboBox1.Items.Add('Королевство Дельфи'); end; procedure TForm1.ComboBox1Change(Sender: TObject); begin {Перебросим выбранное в значение из ComboBox в grid} StringGrid1.Cells[StringGrid1.Col, StringGrid1.Row] :=ComboBox1.Items[ComboBox1.ItemIndex]; ComboBox1.Visible := False; StringGrid1.SetFocus; end; procedure TForm1.ComboBox1Exit(Sender: TObject); begin {Перебросим выбранное в значение из ComboBox в grid} StringGrid1.Cells[StringGrid1.Col, StringGrid1.Row] :=ComboBox1.Items[ComboBox1.ItemIndex]; ComboBox1.Visible := False; StringGrid1.SetFocus; end; procedure TForm1.StringGrid1SelectCell(Sender: TObject; ACol, ARow: Integer; var CanSelect: Boolean); var R: TRect; begin if ((ACol = 3) AND (ARow <> 0)) then begin {Ширина и положение ComboBox должно соответствовать ячейке StringGrid} R := StringGrid1.CellRect(ACol, ARow); R.Left := R.Left + StringGrid1.Left; R.Right := R.Right + StringGrid1.Left; R.Top := R.Top + StringGrid1.Top; R.Bottom := R.Bottom + StringGrid1.Top; ComboBox1.Left := R.Left + 1; ComboBox1.Top := R.Top + 1; ComboBox1.Width := (R.Right + 1) - R.Left; ComboBox1.Height := (R.Bottom + 1) - R.Top; {Покажем combobox} ComboBox1.Visible := True; ComboBox1.SetFocus; end; CanSelect := True; end; Вопрос: Как узнать есть ли в заданном CD-ROM'е Audio CD? Ответ: Можно использовать функцию Windows API GetDriveType() чтобы определить является ли дисковод CD-ROM'мом. И функцию API GetVolumeInformation() чтобы проверить VolumeName на равенство 'Audio CD'. Пример: function IsAudioCD(Drive : char) : bool; var DrivePath : string; MaximumComponentLength : DWORD; FileSystemFlags : DWORD; VolumeName : string; Begin sult := false; DrivePath := Drive + ':\'; if GetDriveType(PChar(DrivePath)) <> DRIVE_CDROM then exit; SetLength(VolumeName, 64); GetVolumeInformation(PChar(DrivePath),PChar(VolumeName), Length(VolumeName),nil,MaximumComponentLength,FileSystemFlags,nil,0); if lStrCmp(PChar(VolumeName),'Audio CD') = 0 then result := true; end; function PlayAudioCD(Drive : char) : bool; var mp : TMediaPlayer; begin result := false; Application.ProcessMessages; if not IsAudioCD(Drive) then exit; mp := TMediaPlayer.Create(nil); mp.Visible := false; mp.Parent := Application.MainForm; mp.Shareable := true; mp.DeviceType := dtCDAudio; mp.FileName := Drive + ':'; mp.Shareable := true; mp.Open; Application.ProcessMessages; mp.Play; Application.ProcessMessages; mp.Close; Application.ProcessMessages; mp.free; result := true; end; procedure TForm1.Button1Click(Sender: TObject); begin if not PlayAudioCD('D') then ShowMessage('Not an Audio CD'); end; Вопрос: Как узнать есть ли у мыши колесико? Ответ: Свойство "WheelPresent" глобального обьекта "mouse". Вопрос: События KeyPress и KeyDown не вызываются для клавиши Tab - как определить, что она была нажата? Ответ: На уровне формы клавиша tab обычно обрабатывается Windows. В примере создается обработчик события CM_Dialog для перехвата Dialog keys. Пример: type TForm1 = class(TForm) private procedure CMDialogKey( Var msg: TCMDialogKey ); message CM_DIALOGKEY; end; var Form1: TForm1; implementation {$R *.DFM} procedure TForm1.CMDialogKey(var msg: TCMDialogKey); begin if msg.Charcode <> VK_TAB then inherited; end; procedure TForm1.FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); begin if Key = VK_TAB then Form1.Caption := 'Tab Key Down!'; end; Вопрос: В чем отличие между Create(Self) и Create(Application)? Ответ: Self может быть использовано только в методе класса, и ссылается на текущий экземпляр класса. Таким образом "Self" в методе класса TForm1 ссылается на текущий экземпляр TForm1. При создании компонента Вы передаете его владельца (owner) в конструктор. При уничтожении формы или компонента автоматически уничтожаются и все компоненты владельцем которого она является. Таким образом если при создании формы передать в качестве владельца Application эта форма будет автоматически уничтожена при уничтожении Application. Если же при создании формы передать в качестве владельца другую форму, вновь созданная форма будет автоматически уничтоженна при уничтожении формы-владельца. Вопрос: Как во время выполнения определить поддерживает ли обьект заданное свойство? Ответ: function HasProperty(Obj : TObject; Prop : string) : PPropInfo; begin Result := GetPropInfo(Obj.ClassInfo, Prop); end; procedure TForm1.Button1Click(Sender: TObject); var p : pointer; begin p := HasProperty(Button1, 'Color'); if p <> nil then SetOrdProp(Button1, p, clRed) else ShowMessage('Button has no color property'); p := HasProperty(Label1, 'Color'); if p <> nil then SetOrdProp(Label1, p, clRed) else ShowMessage('Label has no color property'); p := HasProperty(Label1.Font, 'Color'); if p <> nil then SetOrdProp(Label1.Font.Color, p, clBlue) else ShowMessage('Label.Font has no color property'); end; Вопрос: Как при проигрывании музыки с Audio CD показывать сколько прошло минут и секунд? Ответ: В примере время выводится по таймеру. Пример: uses MMSystem; procedure TForm1.Timer1Timer(Sender: TObject); var Trk : Word; Min : Word; Sec : Word; begin with MediaPlayer1 do begin Trk := MCI_TMSF_TRACK(Position); Min := MCI_TMSF_MINUTE(Position); Sec := MCI_TMSF_SECOND(Position); Label1.Caption := Format('%.2d',[Trk]); Label2.Caption := Format('%.2d:%.2d',[Min,Sec]); end; end; Вопрос: Можно ли рисовать на рамке формы? Ответ: Обрабатывайте событие WM_NCPAINT. В примере рамка обводится красной линией толщиной в 1 пиксел. Пример: type TForm1 = class(TForm) private {Private declarations} procedure WMNCPaint(var Msg : TWMNCPaint); message WM_NCPAINT; public {Public declarations} end; var Form1: TForm1; implementation {$R *.DFM} 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; Вопрос: Как выполнить какой-то процесс тогда, когда пользователь не работает с моим приложением? Ответ: Создайте процедуру, которая будет вызываться при событии Application.OnIdle. Обьявим процедуру: {Private declarations} procedure IdleEventHandler(Sender: TObject; var Done: Boolean); В разделе implementation опишем поцедуру: procedure TForm1.IdleEventHandler(Sender: TObject; var Done: Boolean); begin {Do a small bit of work here} Done := false; end; В методе Form'ы OnCreate - укажем что наша процедура вызывается на событии Application.OnIdle. Application.OnIdle := IdleEventHandler; Событие OnIdle возникает один раз - когда приложение переходит в режим "безделья" (idle). Если в обработчике переменной Done присвоить False событие будет вызываться вновь и вновь, до тех пор пока приложение "бездельничает" и переменной Done не присвоенно значение True. Вопрос: При перемещении фокуса ввода клавишей Tab чтобы переместить его в RadioGroup нужно нажать клавишу Tab дважды если какой нибудь пункт RadioGroup уже выбран, но только один раз если не выбран. Можно ли сделать поведение RadioGroup логичным? Ответ: Установка свойства RadioGroup'ы TabStop в false должна решить эту проблему - поскольку клавиша tab будет продолжать работать - перемещаясь сразу на выделенный пункт RadioGroup. Вопрос: Как разместить маленькие картинки в компоненте TPopUpMenu? Ответ: В приведенном примере показано как это сделать с использованием функции Windows API SetMenuItemBitmaps(). Эта функция получает handle popup menu, позицию строчки меню куда будет помещена картинка, и два дескриптора(handles) на две картинки (одна из них - картинка которая будет показана когда строка меню доступна, вторая - когда строка меню недоступна). type TForm1 = class(TForm) PopupMenu1: TPopupMenu; Pop11: TMenuItem; Pop21: TMenuItem; Pop31: TMenuItem; procedure FormCreate(Sender: TObject); procedure FormDestroy(Sender: TObject); procedure FormMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); private {Private declarations} bmUnChecked : TBitmap; bmChecked : TBitmap; public {Public declarations} end; var Form1: TForm1; implementation {$R *.DFM} procedure TForm1.FormCreate(Sender: TObject); begin bmUnChecked := TBitmap.Create; bmUnChecked.LoadFromFile('C:\Program Files\Borland\BitBtns\ALARMRNG.BMP'); bmChecked := TBitmap.Create; bmChecked.LoadFromFile('C:\Program Files\Borland\BitBtns\CHECK.BMP'); {Add the bitmaps to the item at index 1 in PopUpMenu} SetMenuItemBitmaps(PopUpMenu1.Handle,1,MF_BYPOSITION,BmUnChecked.Handle, BmChecked.Handle); end; procedure TForm1.FormDestroy(Sender: TObject); begin bmUnChecked.Free; bmChecked.Free; end; procedure TForm1.FormMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); var pt : TPoint; begin pt := ClientToScreen(Point(x, y)); PopUpMenu1.Popup(pt.x, pt.y); end; Вопрос: Как узнать число кадров AVI файла, и выяснить как долго будет проигрывться этот файл? Ответ: В приведенном примере указано как получить эту информацию. Пример: procedure TForm1.Button1Click(Sender: TObject); begin MediaPlayer1.TimeFormat := tfFrames; ShowMessage('Number of frames = ' + IntToStr(MediaPlayer1.Length)); MediaPlayer1.TimeFormat := tfMilliseconds; ShowMessage('Number of milliseconds = ' + IntToStr(MediaPlayer1.Length)); end; Вопрос: Как изменить число фиксированных колонок в TDbGrid? Пример: procedure TForm1.Button1Click(Sender: TObject); begin TStringGrid(DbGrid1).FixedCols := 2; end; Вопрос: Некоторые компоненты баз данных (и среди них TDBGrid) никак не меняют визуальных свойств, когда к ним отключен доступ (disabled). Как это изменить програмно? Ответ: Ниже приведен пример, меняющий цвет шрифта на clGray, когда доступ к элементу управления (в данном случае TDBGrid) запрещен (disabled). 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; Вопрос: Как определить нажаты ли клавиши Shift, Alt, or Ctrl в какой-либо момент времени? Ответ: В приведенном примере показано как определить нажата ли клавиша Shift при выборе строчки меню. Пример также содержит функции проверки состояния клавиш Alt, Ctrl. Пример: function CtrlDown : Boolean; var State : TKeyboardState; begin GetKeyboardState(State); Result := ((State[vk_Control] And 128) <> 0); end; function ShiftDown : Boolean; var State : TKeyboardState; begin GetKeyboardState(State); Result := ((State[vk_Shift] and 128) <> 0); end; function AltDown : Boolean; var State : TKeyboardState; begin GetKeyboardState(State); Result := ((State[vk_Menu] and 128) <> 0); end; procedure TForm1.MenuItem12Click(Sender: TObject); begin if ShiftDown then Form1.Caption := 'Shift' else Form1.Caption := ''; end; Вопрос: Как изменить шрифта hint'а? Ответ: В примере перехватывается событие Application.OnShowHint и изменяется шрифт Hint'а. Пример: type TForm1 = class(TForm) procedure FormCreate(Sender: TObject); private {Private declarations} public procedure MyShowHint(var HintStr: string; var CanShow: Boolean;var HintInfo: THintInfo); {Public declarations} end; var Form1: TForm1; implementation {$R *.DFM} procedure TForm1.MyShowHint(var HintStr: string; var CanShow: Boolean; var HintInfo: THintInfo); var i : integer; begin for i := 0 to Application.ComponentCount - 1 do if Application.Components[i] is THintWindow then with THintWindow(Application.Components[i]).Canvas do begin Font.Name:= 'Arial'; Font.Size:= 18; Font.Style:= [fsBold]; HintInfo.HintColor:= clWhite; end; end; procedure TForm1.FormCreate(Sender: TObject); begin Application.OnShowHint := MyShowHint; end; Вопрос: Есть ли в Delphi эквивалент функции SendKeys Visual Basic'а? Ответ: Ниже приведена процедура, позволяющаю отправлять нажатия в любой элемент управления (window control), способный принимать ввод с клавиатуры. Вы можете использовать эту технику чтобы включать клавиши NumLock, CapsLock и ScrollLock под Windows NT. Та же техника работает и под Windows 95 для CapsLock и ScrollLock но не работает для клавиши NumLock. Обратите внимание, что приведены четыре поцедуры: SimulateKeyDown() - эмулировать нажатие клавиши (без отпускания) SimulateKeyUp() - эмулировать отпускание клавиши SimulateKeystroke() - эмулировать удар по клавише (нажатие и отпускание) и SendKeys(), позволяющие Вам гибко контролировать посылаемые сообщения клавиатуры. SimulateKeyDown(), SimulateKeyUp() и SimulateKeystroke() получают коды виртуальных клавиш (virtural key) (вроде VK_F1). Процедура SimulateKeystroke() получает дополнительный параметр, полезный при эмуляции нажатия PrintScreen. Когда этот параметр равен нулю весь экран будет скопирован в буфер обмена (clipboard). Если дополнительный параметр равен 1 будет скопированно только активное окно. Четыре метода "button click" демонстрируют использование: ButtonClick1 - включает capslock ButtonClick2 - перехватывает весь экран в буфер обмена (clipboard). ButtonClick3 - перехватывает активное окно в буфер обмена (clipboard). ButtonClick4 - устанавливает фокус в Edit и отправляет в него строку. Пример: procedure SimulateKeyDown(Key : byte); begin keybd_event(Key, 0, 0, 0); end; procedure SimulateKeyUp(Key : byte); begin keybd_event(Key, 0, KEYEVENTF_KEYUP, 0); end; procedure SimulateKeystroke(Key : byte; extra : DWORD); begin keybd_event(Key,extra,0,0); keybd_event(Key,extra,KEYEVENTF_KEYUP,0); end; procedure SendKeys(s : string); var i : integer; flag : bool; w : word; begin {Get the state of the caps lock key} flag := not GetKeyState(VK_CAPITAL) and 1 = 0; {If the caps lock key is on then turn it off} if flag then SimulateKeystroke(VK_CAPITAL, 0); for i := 1 to Length(s) do begin w := VkKeyScan(s[i]); {If there is not an error in the key translation} if ((HiByte(w) <> $FF) and (LoByte(w) <> $FF)) then begin {If the key requires the shift key down - hold it down} if HiByte(w) and 1 = 1 then SimulateKeyDown(VK_SHIFT); {Send the VK_KEY} SimulateKeystroke(LoByte(w), 0); {If the key required the shift key down - release it} if HiByte(w) and 1 = 1 then SimulateKeyUp(VK_SHIFT); end; end; {if the caps lock key was on at start, turn it back on} if flag then SimulateKeystroke(VK_CAPITAL, 0); end; procedure TForm1.Button1Click(Sender: TObject); begin {Toggle the cap lock} SimulateKeystroke(VK_CAPITAL, 0); end; procedure TForm1.Button2Click(Sender: TObject); begin {Capture the entire screen to the clipboard} {by simulating pressing the PrintScreen key} SimulateKeystroke(VK_SNAPSHOT, 0); end; procedure TForm1.Button3Click(Sender: TObject); begin {Capture the active window to the clipboard} {by simulating pressing the PrintScreen key} SimulateKeystroke(VK_SNAPSHOT, 1); end; procedure TForm1.Button4Click(Sender: TObject); begin {Set the focus to a window (edit control) and send it a string} Application.ProcessMessages; Edit1.SetFocus; SendKeys('Delphi Is RAD!'); end; Вопрос: Я загружаю TImageList динамически. Как сделать картинки из TImageList прозрачными? Ответ: См. ответ. Пример: procedure TForm1.Button1Click(Sender: TObject); var bm : TBitmap; il : TImageList; begin bm := TBitmap.Create; bm.LoadFromFile('C:\DownLoad\TEST.BMP'); il := TImageList.CreateSize(bm.Width,bm.Height); il.DrawingStyle := dsTransparent; il.Masked := true; il.AddMasked(bm, clRed); il.Draw(Form1.Canvas, 0, 0, 0); bm.Free; il.Free; end; Вопрос: Как заставить TMediaPlayer проигрывать одно и тоже бесконечно? AVI например? Ответ: В примере AVI файл проигрывается снова и снова - используем событие MediaPlayer'а Notify Пример: procedure TForm1.MediaPlayer1Notify(Sender: TObject); begin with MediaPlayer1 do if NotifyValue = nvSuccessful then begin Notify := True; Play; end; end; Вопрос: При выполнении диалога FontDialog со свойством Device равным fdBoth or fdPrinter, появляется ошибка "There are no fonts installed". Ответ: Эти установки должны показать шрифты совместимые либо с принтером либо с экраном. В примере диалог Windows ChooseFont вызывается напрямую чтобы показать список шрифтов, совместимых одновременно и с экраном и с принтером. Пример: uses Printers, CommDlg; procedure TForm1.Button1Click(Sender: TObject); var cf : TChooseFont; lf : TLogFont; tf : TFont; begin if PrintDialog1.Execute then begin GetObject(Form1.Canvas.Font.Handle, sizeof(lf),@lf); FillChar(cf, sizeof(cf), #0); cf.lStructSize := sizeof(cf); cf.hWndOwner := Form1.Handle; cf.hdc := Printer.Handle; cf.lpLogFont := @lf; cf.iPointSize := Form1.Canvas.Font.Size * 10; cf.Flags := CF_BOTH or CF_INITTOLOGFONTSTRUCT or CF_EFFECTS or CF_SCALABLEONLY or CF_WYSIWYG; cf.rgbColors := Form1.Canvas.Font.Color; if ChooseFont(cf) <> false then begin tf := TFont.Create; tf.Handle := CreateFontIndirect(lf); tf.COlor := cf.RgbColors; Form1.Canvas.Font.Assign(tf); tf.Free; Form1.Canvas.TextOut(10, 10, 'Test'); end; end; end; Вопрос: Как сменить дисковод, откуда MediaPlayer проигрывает аудио CD? Ответ: См. пример. Пример: MediaPlayer1.FileName := 'E:'; Вопрос: Как убрать кнопку с названием моей программы из Панели Задач(Taskbar)? Ответ: Отредактируйте файл-проекта (View -> Project Source) Добавьте модуль Windows в раздел uses. Application.ShowMainForm := False; в строку после "Application.Initialize;". Добавьте ShowWindow(Application.Handle, SW_HIDE); в строку перед "Application.Run;" Ваш файл проекта должен выглядеть приблизительно так: program Project1; uses Windows, Forms, Unit1 in 'Unit1.pas' {Form1}, Unit2 in 'Unit2.pas' {Form2}; {$R *.RES} begin Application.Initialize; Application.ShowMainForm := False; Application.CreateForm(TForm1, Form1); Application.CreateForm(TForm2, Form2); ShowWindow(Application.Handle, SW_HIDE); Application.Run; end. В разделе "initialization" (в самом низу) каждого unit'а добавьте begin ShowWindow(Application.Handle, SW_HIDE); end. Вопрос: Как преобразовать цвета в строку - название цвета VCL? Ответ: Модуль graphics.pas содержит функцию ColorToString() которое преобразует допустимое значение TColor в его строковое представление используя либо константу-название цвета (по возможности) либо шестнадцатиричную строку. Обратная функция - StringToColor() Пример: procedure TForm1.Button1Click(Sender: TObject); begin Memo1.Lines.Add(ColorToString(clRed)); Memo1.Lines.Add(IntToStr(StringToColor('clRed'))); end; Вопрос: При показе максимизированное формы она перекрывает task bar и не выравнивается по верху экрана. В чем тут дело? Ответ: Это может произойти когда свойство position формы установленно в poScreenCenter. Установите position = poDefault. Вопрос: Как заставить 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; Вопрос: Как получить число и список всех компонентов, расположенных на TNoteBook? Ответ: В примере список выводится на Listbox. Пример: procedure TForm1.Button1Click(Sender: TObject); var n: integer; p: integer; begin ListBox1.Clear; with Notebook1 do begin for n := 0 to ControlCount - 1 do begin with TPage(Controls[n]) do begin ListBox1.Items.Add('Notebook Page: ' + TPage(Notebook1.Controls[n]).Caption); for p := 0 to ControlCount - 1 do ListBox1.Items.Add(Controls[p].Name); ListBox1.Items.Add(EmptyStr); end; end; end; end; Вопрос: Я хочу вставить escape code в строку при использовании функции Format(). Например, я хочу создать строку, содержащую символ табуляции. В "C" я бы написал что-то вроде sprintf(buffer, "%s\t%s", str);. А как это будет на Pascal'e? Ответ: Функция Format Pascal'я не использует escape codes. Вместо этого нужно вставить в строку действительное значение символа в кодировке ASCII. Пример: Buffer := Format('%s'#9'%s', [Str1, Str2]); ShowMessage(Format('%s'#9'%s', ['Column1', 'Column2'])); Вопрос: Как показать первый кадр AVI-файла? Ответ: См. пример. Пример: procedure TForm1.Button1Click(Sender: TObject); begin Application.ProcessMessages; MediaPlayer1.Open; Application.ProcessMessages; MediaPlayer1.Step; Application.ProcessMessages; MediaPlayer1.Previous; end; Вопрос: Когда пользователь щелкает по listview, он переходит в режим редактирования. Как перевисти его в редим редактирования по нажатию клавиши (например F2)? Ответ: Перехватите F2 на событии keydown. Пример: procedure TForm1.ListView1KeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); begin if Ord(Key) = VK_F2 then ListView1.Selected.EditCaption; end; Вопрос: Когда я добавляю обьект в список TStrings как мне его потом уничтожить? Ответ: Просто вызовите метод free этого обьекта. Пример: procedure TForm1.FormCreate(Sender: TObject); var Icon: TIcon; begin Icon := TIcon.Create; Icon.LoadFromFile('C:\Program Files\BorlandImages\CONSTRUC.ICO'); ListBox1.Items.AddObject('Item 0', Icon); end; procedure TForm1.FormDestroy(Sender: TObject); begin ListBox1.Items.Objects[0].Free; end; Вопрос: Вместо печати графики я хочу использовать резидентный шрифт принтера. Как? Ответ: Используте функцию Windows API - GetStockObject() чтобы получить дескриптор (handle) шрифта по умолчанию устройства (DEVICE_DEFAULT_FONT) и передайте его Printer.Font.Handle. Пример: uses Printers; procedure TForm1.Button1Click(Sender: TObject); var tm : TTextMetric; i : integer; begin if PrintDialog1.Execute then begin Printer.BeginDoc; Printer.Canvas.Font.Handle := GetStockObject(DEVICE_DEFAULT_FONT); GetTextMetrics(Printer.Canvas.Handle, tm); for i := 1 to 10 do begin Printer.Canvas.TextOut(100,i * tm.tmHeight + tm.tmExternalLeading,'Test'); end; Printer.EndDoc; end; end; Вопрос: Мне нужно программно установить некоторые файлы с установочного диска Windows. На многих компьютерах CAB-файлы установки Windows находятся в каком-то каталоге на жестком диске, на других - Windows был установлен с CD. Как узнать откуда была установленна Windows? Ответ: Эту информацию можно получить из реестра. Пример: uses Registry; procedure TForm1.Button1Click(Sender: TObject); var reg: TRegistry; begin reg := TRegistry.Create; reg.RootKey := HKEY_LOCAL_MACHINE; reg.OpenKey('Software\Microsoft\Windows\CurrentVersion\SETUP',false); ShowMessage(reg.ReadString('SourcePath')); reg.CloseKey; reg.free; end; Вопрос: Как получить строку сообщения об ошибке Windows код которой получен функцией GetLastError? Ответ: Функция RTL SysErrorMessage(GetLastError). Пример: procedure TForm1.Button1Click(Sender: TObject); begin {Cause a Windows system error message to be logged} ShowMessage(IntToStr(lStrLen(nil))); ShowMessage(SysErrorMessage(GetLastError)); end; Вопрос: Как заставить Delphi выполнять еще более строгую проверка типов? Напрмер - я создаю пользовательский тип, унаследованный от double и могу передавать его любым функциям, принимающим параметр типа double. Как заставить компилятор проводить более строгую проверку типов и выдавать предупреждение в таких случаях? Ответ: См. ответ. Пример: type TStrongType = type Double; type TWeakType = Double; procedure AddWeakType(var d : TWeakType); begin d := d + 1; end; procedure AddStrongType(var d : TStrongType); begin d := d + 1; end; procedure AddDoubleType(var d : Double); begin d := d + 1; end; procedure TForm1.Button1Click(Sender: TObject); var d : Double; s : TStrongType; w : TWeakType; begin AddDoubleType(d); {compiles fine} AddDoubleType(w); {compiles fine} AddDoubleType(s); { Вопрос: Где в Delphi обьявленны VK_Key для A-Z и 0-9? Ответ: Они не обьявлены в Delphi поскольку они просто могуть быть заменены буквами. VK_0 до VK_9 то же что и ASCII '0' до '9' ($30 - $39), VK_A до VK_Z то же что и ASCII 'A' до 'Z' ($41 - $5A). Вопрос: Как изменить оконную процедуру для TForm? Ответ: Переопределите в подклассе TForm оконную процедуру WinProc класса. В примере оконная процедура переопределяется для того чтобы реагировать на сообщение WM_CANCELMODE, показывающее, что выполняется messagebox или какой-либо еще диалог. Пример: type TForm1 = class(TForm) Button1: TButton; procedure WndProc (var Message: TMessage); override; procedure Button1Click(Sender: TObject); private {Private declarations} public {Public declarations} end; var Form1: TForm1; implementation {$R *.DFM} procedure TForm1.WndProc (var Message: TMessage); begin if Message.Msg = WM_CANCELMODE then begin Form1.Caption := 'A dialog or message box has popped up'; end else inherited // Вопрос: Как узнать размеры TComboBox с показанным выпадающим списком до показа списка? Ответ: На событии FormShow пошлите сообщение CB_SHOWDROPDOWN в ComboBox дважды - один раз чтобы заставить список выпасть, второй - чтобы убрать его. Затем пошлите сообщение CB_GETDROPPEDCONTROLRECT, передав в качестве параметра адрес TRect. TRect будет содержать экранные кординаты прямоугольника описывающего ComboBox вместе с выпавшим списком. Затем Вы можете вызвать ScreenToClient чтобы преобразовать экранные кординаты в координаты клиентской области окна. Пример: var R : TRect; procedure TForm1.FormShow(Sender: TObject); var T : TPoint; begin SendMessage(ComboBox1.Handle, CB_SHOWDROPDOWN, 1, 0); SendMessage(ComboBox1.Handle, CB_SHOWDROPDOWN, 0, 0); SendMessage(ComboBox1.Handle, CB_GETDROPPEDCONTROLRECT, 0, LongInt(@r)); t := ScreenToClient(Point(r.Left, r.Top)); r.Left := t.x; r.Top := t.y; t := ScreenToClient(Point(r.Right, r.Bottom)); r.Right := t.x; r.Bottom := t.y; end; procedure TForm1.Button1Click(Sender: TObject); begin Form1.Canvas.Rectangle(r.Left, r.Top, r.Right, r.Bottom ); end; Вопрос: Я хочу создать в своей программе меню "а ля Дельфи 4". Как это сделать? Ответ: 1. Разместите на форме TControlBar. (закладка Additional) Установите Align = Client. 2. Разместите TToolBar (закладка Win32) внутри TControlBar. 3. Установите в True свойства Flat и ShowCaptions этого TToolBar. 4. Создайте на TToolBar столько TToolButtons сколько Вам нужно. (щелкнув по TToolBar правой кнопкой и выбрав NewButton) 5. Установите свойство Grouped = True для всех TToolButtons. Это позволит меню выпадать при перемещении курсора между главными пунктами меню (если меню уже показано). 6. Разместите на фоме TMainMenu и убедитесь, что оно *НЕ присоденено* как меню главной формы. (посмотрите свойство Menu формы). 7. Создайте все пункты меню (щелкнув по TMainMenu кнопкой и выбрав Menu Designer) 8. Для каждой TToolButton установите ее MenuItem равным соответсвующему пункту TMainMenu. Вопрос: Как добится того чтобы TMemo и TEdit имели работали не только в режиме вставки символов, но и в режиме замены? Ответ: Элементы управления Windows TEdit и TMemo не имеют режима замены. Однако этот режим можно эмулировать установив свойство SelLength edit'а или memo в 1 при обработке события KeyPress. Это заставит его перезаписывать символ в текущей позиции курсора. В примере этот способ используется для TMemo. Режим вставка/замена переключается клавишей "Insert". Пример: type TForm1 = class(TForm) Memo1: TMemo; procedure Memo1KeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); procedure Memo1KeyPress(Sender: TObject; var Key: Char); private {Private declarations} InsertOn : bool; public {Public declarations} end; var Form1: TForm1; implementation {$R *.DFM} procedure TForm1.Memo1KeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); begin if (Key = VK_INSERT) and (Shift = []) then InsertOn := not InsertOn; end; procedure TForm1.Memo1KeyPress(Sender: TObject; var Key: Char); begin if ((Memo1.SelLength = 0) and (not InsertOn)) then Memo1.SelLength := 1; end; Вопрос: Как отправить сообщение сразу всем элементам управления формы? Ответ: Можно использовать Screen.Forms[i].BroadCast(msg); где [i] - индекс той формы, которой Вы хотите переслать сообщение. BroadCast работает со всеми компонентами, потомками TWinControls и отправляет сообщение всем дочерним компонентам из массива Controls. Если один из дочерних компонентов обрабатывает это сообщение и устанавливает Msg.Result в ненулевое значение - дальнейшая рассылка сообщения останавливается. Вопрос: При попытке присвоить значение свойству "selected" ListBox'а вырабатывается exception "Index is out of bounds". В чем тут дело и как присвоить значение свойству selected? Ответ: Свойство "selected" компонента ТListBox может быть использованно только если свойство MultiSelect установленно в True. Если Вы работаете с ListBox'ом у которого MultiSelect=false то используйте свойство ItemIndex. Пример: procedure TForm1.Button1Click(Sender: TObject); begin ListBox1.Items.Add('1'); ListBox1.Items.Add('2'); {This will fail on a single selection ListBox} // ListBox1.Selected[1] := true; ListBox1.ItemIndex := 1; {This is ok} end; Вопрос: Как ограничить длинну текста, вводимого в TEdit, так чтобы ширина текста не превышала ширину TEdit'а? Ответ: В примере приведено два способа ограничить длинну текста в TEdit так чтобы она не превышала ширину клиентской области окна TEdit'а и не появлялась прокрутка текста. Первый способ устанавливает свойство TEdit'а MaxLength равным числу букв "W", которые поместятся в TEdit. "W" выбрана потому, что является, наверное, самой широкой буквой в любом шрифте. Этот метод сносно работает для шрифтов с фиксированной шириной букв, но для шрифтов с переменной шириной букв вряд ли сгодится. Второй способ перхватывает событие KeyPress TEdit'а и измеряет ширину уже введенного текста и ширину нового символа. Если ширина больше чем клиентская область TEdit'а новый символ отбрасывается и вызывается MessageBeep. Пример: procedure TForm1.FormCreate(Sender: TObject); var cRect : TRect; bm : TBitmap; s : string; begin Windows.GetClientRect(Edit1.Handle, cRect); bm := TBitmap.Create; bm.Width := cRect.Right; bm.Height := cRect.Bottom; bm.Canvas.Font := Edit1.Font; s := 'W'; while bm.Canvas.TextWidth(s) < CRect.Right do s := s + 'W'; if length(s) > 1 then begin Delete(s, 1, 1); Edit1.MaxLength := Length(s); end; end; {Другой вариант} procedure TForm1.Edit1KeyPress(Sender: TObject; var Key: Char); var cRect : TRect; bm : TBitmap; begin if ((Ord(Key) <> VK_TAB) and (Ord(Key) <> VK_RETURN) and (Ord(Key) <> VK_LEFT) and (Ord(Key) <> VK_BACK)) then begin Windows.GetClientRect(Edit1.Handle, cRect); bm := TBitmap.Create; bm.Width := cRect.Right; bm.Height := cRect.Bottom; bm.Canvas.Font := Edit1.Font; if bm.Canvas.TextWidth(Edit1.Text + Key) > CRect.Right then begin Key := #0; MessageBeep(-1); end; bm.Free; end; end; Вопрос: Как сохранить обьект TFont в реестре/ini/файле/таблице базы данных? Ответ: Нужно сохранять атрибуты шрифта (имя, размер и т.п.) а не сам обьект TFont. После считывания этой информации следует проверить существует ли такой шрифт, прежде чем его использовать. Чтобы не показаться голословным дополню ответ Borland'а своим примером сохранения/чтения шрифта в/из реестра Uses ... Registry; procedure SaveFontToRegistry(Font : TFont; SubKey : String); Var R : TRegistry; FontStyleInt : byte; FS : TFontStyles; begin R:=TRegistry.Create; try FS:=Font.Style; Move(FS,FontStyleInt,1); R.OpenKey(SubKey,True); R.WriteString('Font Name',Font.Name); R.WriteInteger('Color',Font.Color); R.WriteInteger('CharSet',Font.Charset); R.WriteInteger('Size',Font.Size); R.WriteInteger('Style',FontStyleInt); finally R.Free; end; end; function ReadFontFromRegistry(Font : TFont; SubKey : String) : boolean; Var R : TRegistry; FontStyleInt : byte; FS : TFontStyles; begin R:=TRegistry.Create; try result:=R.OpenKey(SubKey,false); if not result then exit; Font.Name:=R.ReadString('Font Name'); Font.Color:=R.ReadInteger('Color'); Font.Charset:=R.ReadInteger('CharSet'); Font.Size:=R.ReadInteger('Size'); FontStyleInt:=R.ReadInteger('Style'); Move(FontStyleInt,FS,1); Font.Style:=FS; finally R.Free; end; end; procedure TForm1.Button1Click(Sender: TObject); begin If FontDialog1.Execute then begin SaveFontToRegistry(FontDialog1.Font,'Delphi Kingdom\Fonts'); end; end; procedure TForm1.Button2Click(Sender: TObject); var NFont : TFont; begin NFont:=TFont.Create; if ReadFontFromRegistry(NFont,'Delphi Kingdom\Fonts') then begin //здесь добавить проверку - существует ли шрифт Label1.Font.Assign(NFont); NFont.Free; end; end; Вопрос: Как перемещать компонент мышкой во время работы программы "runtime"? Ответ: Перехватить событие OnMouseDown, запомнить x и y координты курсора мыши. Отслеживать движение мыши по событию OnMouseMove и перемещать компонент вслед за курсором мыши до тех пор пока не произойдет событие OnMouseUp. В примере показано перемещение компонента TButton. Перемещение начинается, когда пользователь "берет" TButton мышью, удерживая нажатой клавишу "Сontrol". Пример: type TForm1 = class(TForm) Button1: TButton; procedure Button1MouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); procedure Button1MouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer); procedure Button1MouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); private {Private declarations} public {Public declarations} MouseDownSpot : TPoint; Capturing : bool; end; var Form1: TForm1; implementation {$R *.DFM} procedure TForm1.Button1MouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); begin if ssCtrl in Shift then begin SetCapture(Button1.Handle); Capturing := true; MouseDownSpot.X := x; MouseDownSpot.Y := Y; end; end; procedure TForm1.Button1MouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer); begin if Capturing then begin Button1.Left := Button1.Left - (MouseDownSpot.x - x); Button1.Top := Button1.Top - (MouseDownSpot.y - y); end; end; procedure TForm1.Button1MouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); begin if Capturing then begin ReleaseCapture; Capturing := false; Button1.Left := Button1.Left - (MouseDownSpot.x - x); Button1.Top := Button1.Top - (MouseDownSpot.y - y); end; end; Вопрос: При попытке создать обьект класса TPrinter (TPrinter.Create) я получаю exception. Почему? Ответ: В создании обьекта класса TPrinter с использованием TPrinter.Create нет необходимости, так как обьект класса TPrinter (называемый Printer) автоматически создается при использовании модуля Printers. Пример: uses Printers; procedure TForm1.Button1Click(Sender: TObject); begin Printer.BeginDoc; Printer.Canvas.TextOut(100, 100, 'Hello World!'); Printer.EndDoc; end; Вопрос: Как перехватить события в неклиентской области формы, в заголовке окна, например? Ответ: Создайте обработчик одного из сообщений WM_NC (non client - не клиентских) (посмотрите WM_NC в Windows API help). Пример показывает как перехватить вижение мыши во всей неклиенстской области окна (рамка и заголовок). Пример: unit Unit1; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls; type TForm1 = class(TForm) private {Private declarations} procedure WMNCMOUSEMOVE(var Message: TMessage); message WM_NCMOUSEMOVE; public {Public declarations} end; var Form1: TForm1; implementation {$R *.DFM} procedure TForm1.WMNCMOUSEMOVE(var Message: TMessage); var s : string; begin case Message.wParam of HTERROR: s:= 'HTERROR'; HTTRANSPARENT: s:= 'HTTRANSPARENT'; HTNOWHERE: s:= 'HTNOWHERE'; HTCLIENT: s:= 'HTCLIENT'; HTCAPTION: s:= 'HTCAPTION'; HTSYSMENU: s:= 'HTSYSMENU'; HTSIZE: s:= 'HTSIZE'; HTMENU: s:= 'HTMENU'; HTHSCROLL: s:= 'HTHSCROLL'; HTVSCROLL: s:= 'HTVSCROLL'; HTMINBUTTON: s:= 'HTMINBUTTON'; HTMAXBUTTON: s:= 'HTMAXBUTTON'; HTLEFT: s:= 'HTLEFT'; HTRIGHT: s:= 'HTRIGHT'; HTTOP: s := 'HTTOP'; HTTOPLEFT: s:= 'HTTOPLEFT'; HTTOPRIGHT: s:= 'HTTOPRIGHT'; HTBOTTOM: s:= 'HTBOTTOM'; HTBOTTOMLEFT: s:= 'HTBOTTOMLEFT'; HTBOTTOMRIGHT: s:= 'HTBOTTOMRIGHT'; HTBORDER: s:= 'HTBORDER'; HTOBJECT: s:= 'HTOBJECT'; HTCLOSE: s:= 'HTCLOSE'; HTHELP: s:= 'HTHELP'; else s:= ''; end; Form1.Caption := s; Message.Result := 0; end; end. Вопрос: При попытке использовать метод TCanvas.StretchDraw чтобы нарисовать иконку увеличенной ее размер не изменяется. Что делать? Ответ: Иконки всегда рисуются размером принятым в системе по умолчанию. Чтобы показать увеличенный вид иконки скоприуйте ее на bitmap, а зате используйте метод TCanvas.StretchDraw. Пример: procedure TForm1.Button1Click(Sender: TObject); var TheBitmap : TBitmap; begin TheBitmap := TBitmap.Create; TheBitmap.Width := Application.Icon.Width; TheBitmap.Height := Application.Icon.Height; TheBitmap.Canvas.Draw(0, 0, Application.Icon); Form1.Canvas.StretchDraw(Rect(0,0,TheBitmap.Width * 3,TheBitmap.Height * 3), TheBitmap); TheBitmap.Free; end; Вопрос: Можно ли сделать так чтобы TStringGrid автоматически изменял ширину колонок, чтобы вместить самую длинную строчку в колонке? Ответ: См. пример. Пример: procedure AutoSizeGridColumn(Grid : TStringGrid; column : integer); var i : integer; temp : integer; max : integer; begin max := 0; for i := 0 to (Grid.RowCount - 1) do begin temp := Grid.Canvas.TextWidth(grid.cells[column, i]); if temp > max then max := temp; end; Grid.ColWidths[column] := Max + Grid.GridLineWidth + 3; end; procedure TForm1.Button1Click(Sender: TObject); begin AutoSizeGridColumn(StringGrid1, 1); end; Вопрос: TTimer работает не достаточно точно. Как получить более высокую точность? Ответ: Таймер Windows не был создан с целью получения сверхточного хронометра. :-( Другими словами, когда Вы устанавливаете таймер на срабатывания каждые 1000 миллисекунд, он может срабатывать через интервал несколько больший чем 1000 миллисекунд. Значения меньше 55 миллисекунд никогда не будут срабатывать вовремя в Windows, поскольку это минимальная точность таймера. Можно проверять системное время и сравнивать его со временем предыдущего события таймера чтобы повысить точность. Вопрос: Как поместить JPEG-картинку в exe-файл и потом загрузить ее? Ответ: 1) Создайте текстовый файл с расширением ".rc". Имя этого файла должно отличаться от имени файла-пректа или любого модуля проекта. Файл должен содержать строку вроде: MYJPEG JPEG C:\DownLoad\MY.JPG где: "MYJPEG" имя ресурса "JPEG" пользовательский тип ресурса "C:\DownLoad\MY.JPG" руть к JPEG файлу. Пусть например rc-файл называется "foo.rc" Запустите BRCC32.exe (Borland Resource CommandLine Compiler) - программа находится в каталоге Bin Delphi/C++ Builder'а - передав ей в качестве параметра полный путь к rc-файлу. В нашем примере: C:\DelphiPath\BIN\BRCC32.EXE C:\ProjectPath\FOO.RC Вы получите откомпилированный ресурс - файл с расширением ".res". (в нашем случает foo.res). Далее добавте ресурс к своему приложению. {Грузим ресурс} {$R FOO.RES} uses Jpeg; procedure LoadJPEGFromRes(TheJPEG : string; ThePicture : TPicture); var ResHandle : THandle; MemHandle : THandle; MemStream : TMemoryStream; ResPtr : PByte; ResSize : Longint; JPEGImage : TJPEGImage; begin ResHandle := FindResource(hInstance, PChar(TheJPEG), 'JPEG'); MemHandle := LoadResource(hInstance, ResHandle); ResPtr := LockResource(MemHandle); MemStream := TMemoryStream.Create; JPEGImage := TJPEGImage.Create; ResSize := SizeOfResource(hInstance, ResHandle); MemStream.SetSize(ResSize); MemStream.Write(ResPtr^, ResSize); FreeResource(MemHandle); MemStream.Seek(0, 0); JPEGImage.LoadFromStream(MemStream); ThePicture.Assign(JPEGImage); JPEGImage.Free; MemStream.Free; end; procedure TForm1.Button1Click(Sender: TObject); begin LoadJPEGFromRes('MYJPEG', Image1.Picture); end; Вопрос: Как перехватить сообщения прокрутки в TScrollBox? Ответ: Следующий пример перехватывает сообщения о прокрутке компонента TScrollBox и синхронизирует обе линейки прокрутки. Сообщения прокрутки перехватываются с помощью переопределения окнной процедуры (WinProc) ScrollBox'а. Пример: type {$IFDEF WIN32} WParameter = LongInt; {$ELSE} WParameter = Word; {$ENDIF} LParameter = LongInt; {Declare a variable to hold the window procedure we are replacing} var OldWindowProc : Pointer; function NewWindowProc(WindowHandle : hWnd; TheMessage : WParameter; ParamW : WParameter; ParamL : LParameter) : LongInt {$IFDEF WIN32} stdcall; {$ELSE} ; export; {$ENDIF} var TheRangeMin : integer; TheRangeMax : integer; TheRange : integer; begin if TheMessage = WM_VSCROLL then begin {Get the min and max range of the horizontal scroll box} GetScrollRange(WindowHandle, SB_HORZ, TheRangeMin, TheRangeMax); {Get the vertical scroll box position} TheRange := GetScrollPos(WindowHandle, SB_VERT); {Make sure we wont exceed the range} if TheRange < TheRangeMin then TheRange := TheRangeMin else if TheRange > TheRangeMax then TheRange := TheRangeMax; {Set the horizontal scroll bar} SetScrollPos(WindowHandle, SB_HORZ, TheRange, true); end; if TheMessage = WM_HSCROLL then begin {Get the min and max range of the horizontal scroll box} GetScrollRange(WindowHandle, SB_VERT, heRangeMin, TheRangeMax); {Get the horizontal scroll box position} TheRange := GetScrollPos(WindowHandle, SB_HORZ); {Make sure we wont exceed the range} if TheRange < TheRangeMin then TheRange := TheRangeMin else if TheRange > TheRangeMax then TheRange := TheRangeMax; {Set the vertical scroll bar} SetScrollPos(WindowHandle, SB_VERT, TheRange, true); end; {Call the old Window procedure to allow processing of the message.} NewWindowProc := CallWindowProc(OldWindowProc, WindowHandle, TheMessage, ParamW, ParamL); end; procedure TForm1.FormCreate(Sender: TObject); begin {Set the new window procedure for the control and remember the old window procedure.} OldWindowProc := Pointer(SetWindowLong(ScrollBox1.Handle, GWL_WNDPROC, LongInt(@NewWindowProc))); end; procedure TForm1.FormDestroy(Sender: TObject); begin {Set the window procedure back to the old window procedure.} SetWindowLong(ScrollBox1.Handle, GWL_WNDPROC, LongInt(OldWindowProc)); end; Вопрос: Как сделать прямоугольник для выделения части картинки для редактирования? Ответ: Самый простой способ - воспользоваться функцией Windows API DrawFocusRect. Функция DrawFocusRect использует операцию XOR при рисовании - таким образом вывод прямоугольника дважды с одними и теми же координатами стирает прямоугольник, и прямоугольник всегда будет виден, на фоне какого бы цвета он не выводился. Пример: type TForm1 = class(TForm) procedure FormMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); procedure FormMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer); procedure FormMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); private {Private declarations} Capturing : bool; Captured : bool; StartPlace : TPoint; EndPlace : TPoint; public {Public declarations} end; var Form1: TForm1; implementation {$R *.DFM} function MakeRect(Pt1 : TPoint; Pt2 : TPoint) : TRect; begin if pt1.x < pt2.x then begin Result.Left := pt1.x; Result.Right := pt2.x; end else begin Result.Left := pt2.x; Result.Right := pt1.x; end; if pt1.y < pt2.y then begin Result.Top := pt1.y; Result.Bottom := pt2.y; end else begin Result.Top := pt2.y; Result.Bottom := pt1.y; end; end; procedure TForm1.FormMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); begin if Captured then DrawFocusRect(Form1.Canvas.Handle,MakeRect(StartPlace,EndPlace)); StartPlace.x := X; StartPlace.y := Y; EndPlace.x := X; EndPlace.y := Y; DrawFocusRect(Form1.Canvas.Handle,MakeRect(StartPlace,EndPlace)); Capturing := true; Captured := true; end; procedure TForm1.FormMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer); begin if Capturing then begin DrawFocusRect(Form1.Canvas.Handle,MakeRect(StartPlace,EndPlace)); EndPlace.x := X; EndPlace.y := Y; DrawFocusRect(Form1.Canvas.Handle,MakeRect(StartPlace,EndPlace)); end; end; procedure TForm1.FormMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); begin Capturing := false; end; Вопрос: Можно ли использовать иконку как картинку на кнопке TSpeedButton? Ответ: Можно. См. пример. Пример: uses ShellApi; procedure TForm1.FormShow(Sender: TObject); var Icon: TIcon; begin Icon := TIcon.Create; Icon.Handle := ExtractIcon(0,'C:\WINDOWS\NOTEPAD.EXE',1); SpeedButton1.Glyph.Width := Icon.Width; SpeedButton1.Glyph.Height := Icon.Height; SpeedButton1.Glyph.Canvas.Draw(0, 0, Icon); Icon.Free; end; Вопрос: Как поместить прозрачную фоновую каринку на компонент CoolBar? Ответ: procedure TForm1.Button1Click(Sender: TObject); var Bm1 : TBitmap; Bm2 : TBitmap; begin Bm1 := TBitmap.Create; Bm2 := TBitmap.Create; Bm1.LoadFromFile('c:\download\test.bmp'); Bm2.Width := Bm1.Width; Bm2.Height := Bm1.Height; bm2.Canvas.Brush.Color := CoolBar1.Color; bm2.Canvas.BrushCopy(Rect(0, 0, bm2.Width, bm2.Height), Bm1, Rect(0, 0, Bm1.width, Bm1.Height), ClWhite); bm1.Free; CoolBar1.Bitmap.Assign(bm2); bm2.Free; end; Вопрос: Ползунок компонента TScrollBar все время мигает. Как это отключить? Ответ: Установите свойтсво ScrollBar.TabStop в False. Вопрос: Как программно перевести DBgrid в реим редактирования и установить курсор в окошке редактирования в требуемую позицию? Ответ: Переведите таблицу в режим редактирования, затем получите дескриптор (handle) окна редактирования и перешлите ей сообщение EM_SETSEL. В качестве параметров вы должны переслать начальную позицию курсора, и конечную позицию, определяющую конец выделения текста цветом. В приведенном примере курсор помещается во вторую позицию, текст внутри ячейки не выделяется. Пример: procedure TForm1.Button1Click(Sender: TObject); var h : THandle; begin Application.ProcessMessages; DbGrid1.SetFocus; DbGrid1.EditorMode := true; Application.ProcessMessages; h:= Windows.GetFocus; SendMessage(h, EM_SETSEL, 2, 2); end; Вопрос: Как поместить курсор в определенную позицию edit'а и подобных ему элементов управления? Ответ: Можно использовать методы Delphi SelStart() и SelectLength(). Пример: procedure TForm1.Button1Click(Sender: TObject); begin Edit1.SetFocus; {переводим курсор во вторую позицию} Edit1.SelStart := 2; {не выделяем никакого текста} Edit1.SelLength := 0; end; Вопрос: Как среагировать на минимизацию-максимизацию формы перед тем как произойдет изменение размера формы? Ответ: В примере перехватывается сообщение WM_SYSCOMMAND. Если это сообщение говорит о минимизации или максимизации формы - пищит динамик. Пример: type TForm1 = class(TForm) private {Private declarations} procedure WMSysCommand(var Msg: TWMSysCommand); message WM_SYSCOMMAND; public {Public declarations} end; var Form1: TForm1; implementation {$R *.DFM} procedure TForm1.WMSysCommand; begin if (Msg.CmdType = SC_MINIMIZE) or (Msg.CmdType = SC_MAXIMIZE) then MessageBeep(0) else inherited; end; Вопрос: Можно ли сделать так - одна форма показывает другую и остается позади нее, но фокус ввода не переходит к новой форме, а остается у старой? Ответ: В примере показывается не автосоздаваемая (non auto-created) форма, но фокус ввода ей не передается. Пример: uses Unit2; procedure TForm1.Button1Click(Sender: TObject); begin Form2 := TForm2.Create(Application); Form2.Visible := FALSE; ShowWindow(Form2.Handle, SW_SHOWNA); end; Вопрос: На некоторых laptop компьютерах может не быть флоппи дисковода. Можно ли удалять из списка TDriveComboBox диски которые отключены? Ответ: В примере TDriveComboBox не показывает дисководы, которые не готовы. (not ready). Учтите что на многих компьютерах будет ощутимая задержка при поверке plug&play флоппи дисковода. Пример: procedure TForm1.FormCreate(Sender: TObject); var i : integer; OldErrorMode : Word; OldDirectory : string; begin OldErrorMode := SetErrorMode(SEM_NOOPENFILEERRORBOX); GetDir(0, OldDirectory); i := 0; while i 0 then DriveComboBox1.Items.Delete(i) else inc(i); end; ChDir(OldDirectory); SetErrorMode(OldErrorMode); end; Вопрос: Как сообщить всем формам моего приложения (в том числе и не видимым в данный момент) об изминении каких-то глобальных значений? Ответ: Один из способов - создать пользовательское сообщение и использовать метод preform чтобы разослать его всем формам из массива Screen.Forms. Пример: {Code for Unit1} const UM_MyGlobalMessage = WM_USER + 1; type TForm1 = class(TForm) Label1: TLabel; Button1: TButton; procedure FormShow(Sender: TObject); procedure Button1Click(Sender: TObject); private {Private declarations} procedure UMMyGlobalMessage(var AMessage: TMessage); message UM_MyGlobalMessage; public {Public declarations} end; var Form1: TForm1; implementation {$R *.DFM} uses Unit2; procedure TForm1.FormShow(Sender: TObject); begin Form2.Show; end; procedure TForm1.UMMyGlobalMessage(var AMessage: TMessage); begin Label1.Left := AMessage.WParam; Label1.Top := AMessage.LParam; Form1.Caption := 'Got It!'; end; procedure TForm1.Button1Click(Sender: TObject); var f: integer; begin for f := 0 to Screen.FormCount - 1 do Screen.Forms[f].Perform(UM_MyGlobalMessage, 42, 42); end; {Code for Unit2} const UM_MyGlobalMessage = WM_USER + 1; type TForm2 = class(TForm) Label1: TLabel; private {Private declarations} procedure UMMyGlobalMessage(var AMessage: TMessage); message UM_MyGlobalMessage; public {Public declarations} end; var Form2: TForm2; implementation {$R *.DFM} procedure TForm2.UMMyGlobalMessage(var AMessage: TMessage); begin Label1.Left := AMessage.WParam; Label1.Top := AMessage.LParam; Form2.Caption := 'Got It!'; end; Вопрос: Как обновить список дисков компонента TDriveComboBox, учитывая, что могуд быть подключены/отключены сетевые диски и произведена "горячая замена" plug&play дисков? Ответ: Следующий пример вызывает защищенный (protected) метод класса TDriveComboBox BuildList() для регеирации списка дисков. (использовая так наз. "class cracer") Пример: type TNewDriveComboBox = class(TDriveComboBox) //это наш "class cracer" end; procedure TForm1.Button1Click(Sender: TObject); var Drive : char; begin Drive := DriveComboBox1.Drive; TNewDriveComboBox(DriveComboBox1).BuildList; //вызываем защищенный метод родительского класса DriveComboBox1.Drive := Drive; end; Вопрос: Как программно заставить выпасть меню? Ответ: В примере показано как показать меню и выбрать в нем какой-то пункт, эмулируя нажатие "быстрой кдавиши" пункта меню. Если у Вашего пункта меню нет "быстрой клавиши" Вы можете посылать комбинации VK_MENU, VK_LEFT, VK_DOWN, и VK_RETURN, чтобы программно "путешествовать" по меню. Пример: procedure TForm1.Button1Click(Sender: TObject); begin //Allow button to finish painting in response to the click Application.ProcessMessages; {Alt Key Down} keybd_Event(VK_MENU, 0, 0, 0); {F Key Down - Drops the menu down} keybd_Event(ord('F'), 0, 0, 0); {F Key Up} keybd_Event(ord('F'), 0, KEYEVENTF_KEYUP, 0); {Alt Key Up} keybd_Event(VK_MENU, 0, KEYEVENTF_KEYUP, 0); {F Key Down} keybd_Event(ord('S'), 0, 0, 0); {F Key Up} keybd_Event(ord('S'), 0, KEYEVENTF_KEYUP, 0); end; Вопрос: Как сделать клавишу-акселератор (keyboard shortcut) компонету у которого нет заголовка? Ответ: Возможный вариант - присвоить ссылку на этот компонент свойству FocusControl TLabel'а. В примере используется невидимый Label для создания "быстрой" клавиши (Alt+M) компонента Memo. Чтобы использовать пример, разместите на форме компонет TMemo, Label и несколько других компонентов, которые могут принимать фокус ввода. Запустите программу, перевидите фокус ввода куда-нибудь вне Memo и нажмите Alt+M - фокус ввода вернется в Memo. Пример: procedure TForm1.FormCreate(Sender: TObject); begin Label1.Visible := false; Label1.Caption := '&M'; Label1.FocusControl := Memo1; end; Вопрос: Можно ли как-то уменьшить мерцание при перерисовке компонента? Ответ: Если добавить флаг csOpaque (непрозрачный) к свойству ControlStyle компонента - то фон компонента перерисовываться не будет. Пример: constructor TMyControl.Create; begin inherited; ControlStyle := ControlStyle + [csOpaque]; end; Вопрос: Как запретить изменение размера моего компонента в design-time? Ответ: Поместите в конструктор компонента код, устанавливающий размеры по умолчанию. Переопределите метод SetBounds и проверяйте в нем "componentstate". Если компонет находится режиме "design-time" (csDesigning in ComponentState) просто передавайте значения ширины и высоты (width и heights) компонента по умолчанию (в нашем примере 50) методу класса-предка. Пример: procedure TVu.SetBounds(ALeft : integer; ATop : integer; AWidth : integer; AHeight : integer); begin if csdesigning in componentstate then begin AWidth := 50; AHeight := 50; inherited; //вызываем унаследованный от предка метод end; end; Вопрос: Можно ли уменьшить потребляемые компонентами TNotebook и TTabbedNotebook ресурсы? Ответ: Да. Можно уничтожать обьекты, расположенные не на текущей странице TNotebook или TTabbedNotebook. В примере вызывается защищенный (Protected) метод путем создания так называемый "class cracer'ов". type TMyTabbedNotebook = class(TTabbedNotebook); //это наш "class cracer" type TMyNotebook = class(TNotebook); procedure TForm1.TabbedNotebook1Change(Sender: TObject; NewTab: Integer; var AllowChange: Boolean); begin with TabbedNotebook1 do //вызываем защищенный метод родительского класса TMyTabbedNotebook(TWinControl(Pages.Objects[PageIndex])).DestroyHandle; end; procedure TForm1.TabSet1Change(Sender: TObject; NewTab: Integer; var AllowChange: Boolean); begin with Notebook1 do //вызываем защищенный метод родительского класса TMyNotebook(TWinControl(Pages.Objects[PageIndex])).DestroyHandle; NoteBook1.PageIndex := NewTab; AllowChange := true end; Вопрос: Функция keybd_event() принимает значения до 244 - как мне отправить нажатие клавиши с кодом #255 в элемент управления Windows? Ответ: Это может понадобится для иностранных языков или для специальных символов. (например, в русских шрифтах символ с кодом #255 - я прописное). Приведенный в примере метод, не стоит использовать в случае если символ может быть передан обычным способом (функцией keybd_event()). procedure TForm1.Button1Click(Sender: TObject); var KeyData : packed record RepeatCount : word; ScanCode : byte; Bits : byte; end; begin {Let the button repaint} Application.ProcessMessages; {Set the focus to the window} Edit1.SetFocus; {Send a right so the char is added to the end of the line} // SimulateKeyStroke(VK_RIGHT, 0); keybd_event(VK_RIGHT, 0,0,0); {Let the app get the message} Application.ProcessMessages; FillChar(KeyData, sizeof(KeyData), #0); KeyData.ScanCode := 255; KeyData.RepeatCount := 1; SendMessage(Edit1.Handle, WM_KEYDOWN, 255,LongInt(KeyData)); KeyData.Bits := KeyData.Bits or (1 shl 30); KeyData.Bits := KeyData.Bits or (1 shl 31); SendMessage(Edit1.Handle, WM_KEYUP, 255, LongInt(KeyData)); KeyData.Bits := KeyData.Bits and not (1 shl 30); KeyData.Bits := KeyData.Bits and not (1 shl 31); SendMessage(Edit1.Handle, WM_CHAR, 255, LongInt(KeyData)); Application.ProcessMessages; end; Вопрос: Некоторые компоненты не меняют курсор мыши до тех пор пока пользователь не сдвинет мышь. Как эмулировать движение мыши? Ответ: В примере мышка слегка "подталкивается" без участия пользователя. procedure TForm1.Button1Click(Sender: TObject); var pt : TPoint; begin Application.ProcessMessages; Screen.Cursor := CrHourglass; GetCursorPos(pt); SetCursorPos(pt.x + 1, pt.y + 1); Application.ProcessMessages; SetCursorPos(pt.x - 1, pt.y - 1); end; Вопрос: Как зарегистрировать расширение файла за своим приложением и контекстное меню, связанное с этим типом? Ответ: Пример регистрирует расширение файла(.myext) - файлы этого типа будут открываться приложением MyApp.Exe. Также регнстрируется одно действие (action) по умолчанию для файлов этого типа и два дополнительных пункта контекстного меню, связанного с этим типом файлов. Возможно, потребуется перезайти в систему чтобы изменения вступили в силу. Пример: uses Registry; procedure TForm1.Button1Click(Sender: TObject); var R : TRegIniFile; begin R := TRegIniFile.Create(''); with R do begin RootKey := HKEY_CLASSES_ROOT; WriteString('.myext','','MyExt'); WriteString('MyExt','','Some description of MyExt files'); WriteString('MyExt\DefaultIcon','','C:\MyApp.Exe,0'); WriteString('MyExt\Shell','','This_Is_Our_Default_Action'); WriteString('MyExt\Shell\First_Action', '','This is our first action'); WriteString('MyExt\Shell\First_Action\command','', 'C:\MyApp.Exe /LotsOfParamaters %1'); WriteString('MyExt\Shell\This_Is_Our_Default_Action','', 'This is our default action'); WriteString('MyExt\Shell\This_Is_Our_Default_Action\command', '','C:\MyApp.Exe %1'); WriteString('MyExt\Shell\Second_Action', '','This is our second action'); WriteString('MyExt\Shell\Second_Action\command', '','C:\MyApp.Exe /TonsOfParameters %1'); Free; end; end;
© 1999 Inprise Corp. Last Modified Friday, 06-Aug-99 11:12:04 PST. Translated & Adapted by 19-Sep-1999
Директива компилятора - $INCLUDE
В своей статье я писал про проблему, возникающую в Object Pascal в связи с только явным подключением заголовочных файлов. Благодаря одному человеку, подсказавшему решение, для меня эта проблема в основом снята. Хочу поделиться решением. Существует такая директива компилятора - {$INCLUDE filename} и её более короткий аналог {$I filename}. Раньше я недооценивал её значение, т. к. в чужих программах с помощью неё к коду программы подключались либо файлы с процедурами, либо списки ассемблерных команд. Выяснилось, что с помощью этой директивы можно подключать и ссылки на другие файлы программы. Поясню на примере.
В IDE Delphi 5 при создании нового проекта в интерфейсной секции автоматически формируется список uses такого вида: uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs;
Начинающие ещё не усматривают никакого подвоха в этом (как не замечал этого и я, когда только осваивал программирование), но постепенно с ростом программ список uses, помимо ссылок на стандартные файлы среды, начинает пополняться десятками собственных и грозит распухнуть до гигинтских размеров. Частично её можно решить, если весь код собрать в нескольких файлах, но это неудобно. Выход один - можно создать отдельный файл - назовём его, например, vcl.pas. После этого модернизируем его так:
| //------------------------------------------------------------------ // Файл: vcl.pas // Описание: Список ссылок на стандартные модули VCL //------------------------------------------------------------------ Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs | Замечу, здесь нет ошибки! Именно так, без всех зарезервированных слов языка, должен выглядеть этот файл. После Dialogs точка с запятой отсутствует. Теперь в модуле Unit1.pas удаляем все ссылки и пишем:
| uses Activex, // для наглядности {$i vcl}; | Т. к. расширение по умолчанию - *.pas, его можно не указывать. Что же произошло? Директивой {$i} мы указали компилятору подставить список ссылок в текст модуля Unit1.pas. Получилось
| uses Activex, Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs; | Если бы поставили запятую ранее, то получилось бы ,; - что недопустимо. Можно поставить точку с запятой в конце списка ссылок в файле vcl.pas, но тогда нельзя ставить этот символ после директивы. Просто уясните себе, что директива {$INCLUDE} подставляет в исходную программу блок строк из указанного файла, и код необходимо согласовать. Также необходимо помнить, что данный файл (vcl.pas) не должен быть подключен к проекту, другими словами, в файле проекта ссылка вроде vcl in 'vcl.pas'; должна отсутствовать - иначе возникнет ошибка наподобие [Error] vcl.pas(5): 'UNIT' expected but identifier 'Windows' found.
Данная директива не решает всех проблем, это ведь не аналог директивы #include из языка С. Например, С позволяет организовать видимость модулей на нескольких уровнях - например, если модуль B включает модуль С, то модуль А, подключив модуль В, получит в своё распоряжение также и данные из модуля С. В нашем случае это недоступно, однако часто это и не требуется. А вот сократить список uses в каждом файле проекта - это может быть полезно.
Диспетчер кучи для объектов одного размера
й Парунов, дата публикации 23 декабря 02 |
Модуль содержит класс psnFixSzMemMgr, реализующий диспетчер кучи для объектов одного размера.
Динамическое размещение объектов (не только экземпляров классов, а вообще) в куче имеет неоспоримые преимущества: гибкость, простота... Но у такого подхода есть недостаток, проистекающий от всеядности стандартного диспетчера кучи языка (Delphi), который и выбирает, откуда "отщипнуть" кусочек памяти. Уж очень много самых разных блоков выделяется-освобождается в куче, и она превращается в швейцарский сыр. Отсюда вытекают две неприятности. Во-первых, когда выделяется блок памяти, диспетчер вынужден запомнить его размер и прочие параметры в пропадающем зря 4-байтном (чаще всего) заголовке (для освобождения). Во-вторых, уж очень медленен этот процесс - надо перестроить дерево... в общем, тактов 200 на одно выделение/освобождение на процессоре Пентиум 3 обеспечено. Все эти недостатки проявляются особенно сильно при работе с маленькими динамическими переменными, которые часто так заманчиво применять для различных списков, деревьев, графов... И самое обидное, что вся эта возня - впустую: так как размер переменных нам-то известен, нет необходимости ни в мудрёном отслеживании блоков (можно обойтись простым списком неиспользуемых), ни тем более в запоминании размера каждого выделенного блока. Эти соображения ведут к применению массивов, с которыми нередко достигается наибольшая скорость, но есть и свои сложности: сложность (простите за каламбур) и негибкость. К тому же частое применение индексов в массиве всё же медленнее использования указателей. Данный класс реализует упрощенный диспетчер кучи для объектов одного размера (назовём его экземпляр кучей), который можно применять вместо стандартного, получая с маленькими объектами существенно большую скорость, а часто и экономию памяти, за счёт реализации старого татаро-монгольского принципа: "меньше чем по десять тысяч мы даже в гости не ходим" ((c) Александр Борянский, Сергей Козлов). Его применение в основном подобно стандартному:
| type psnFixSzMemMgr = class private FLastCommit, { указатель на последний выделенный массив, в начале которого - указатель на предпоследний...} FEmptyBlock : Pointer; {указатель на последний пустой блок из списка, в начале которого - указатель на предпоследний...} FBlockSize, FCommitSize, FFillCycles: Integer; public constructor Create( const BlockSize: Integer; {Размер выделяемых блоков памяти. Реально округляется в большую сторону до величины, кратной 4.} const CommitBlocks: Integer = 127 {Количество блоков в выделяемых массивах. Должно быть больше 1 (иначе зачем огород городить?), а для скорости желательно не меньше 31. Реально увеличивается до ближайшего сверху нечётного значения (алгоритм требует это для высокой скорости).} ); function FixNew: Pointer; {Выделяет блок памяти размера BlockSize и возвращает ссылку на эту память. Выполнено в виде функции во избежание обязательного преобразования типов параметра, передаваемого по ссылке.} procedure FixDsp(const Ptr: Pointer); {Освобождает блок памяти, выделенный ранее в данном экземпляре класса, указуемый параметром Ptr.} destructor Destroy; override; end; implementation constructor psnFixSzMemMgr.Create(const BlockSize, CommitBlocks: Integer); begin FBlockSize:= (BlockSize + 3) and $7FFFFFFC; FCommitSize:= (CommitBlocks and $7FFFFFFE + 1) * FBlockSize + 4; FFillCycles:= CommitBlocks shr 1 - 1; {заполняем в один присест два блока, и цикл оформим от нуля: for I:= 0 to FFillCycles, быстрее будет} end; function psnFixSzMemMgr.FixNew: Pointer; var P, P2: Pointer; I, DBlockSize: Integer; begin if not Assigned (FEmptyBlock) then begin {выделим массив} GetMem(P, FCommitSize); Pointer(P^):= FLastCommit; FLastCommit:= P; Inc(PChar(P), 4); Pointer(P^):= Nil; P2:= Pointer(Integer(P) + FBlockSize); DBlockSize:= FBlockSize shl 1; for I:= 0 to FFillCycles do begin {надо сделать список пустых блоков} Pointer(P2^):= P; Inc(PChar(P), DBlockSize); Pointer(P^):= P2; Inc(PChar(P2), DBlockSize); end; FEmptyBlock:= P; end; Result:= FEmptyBlock; FEmptyBlock:= Pointer(Result^); end; procedure psnFixSzMemMgr.FixDsp(const Ptr: Pointer); begin Pointer(Ptr^):= FEmptyBlock; FEmptyBlock:= Ptr; end; destructor psnFixSzMemMgr.Destroy; var P: Pointer; begin while Assigned(FLastCommit) do begin P:= FLastCommit; FLastCommit:= Pointer(P^); FreeMem(P, FCommitSize); end; inherited; end; |
Дополнение
й Жолоб (Донецк) 01 августа 2002г. Я до сих пор работаю с Delphi 4, поскольку моего домашнего компьютера как раз хватает для этой версии. Иногда я встречаю интересные проекты, предназначенные для Delphi 5, в которых DFM-файлы имеют текстовый формат. Однако Delphi 4, в отличие от Delphi 5, еще не имеет встроенного распознавателя форматов. Поэтому приходится либо переходить в Delphi 5 (я могу сделать это на работе), либо создавать форму в текстовом режиме (тогда получаются накладки с событиями). Есть, правда утилита convert.exe для преобразования файлов, но она не дает возможности просмотреть содержание DFM файла в текстовом виде. Вот я и решил написать собственную программу для этой цели. Предлагаю ее всем желающим для свободного использования. Приношу благодарность разработчикам RX Library за возможность использования кода из модуля StrUtils. В "прицепе" - полный исходный код программы.
Проверка текстового DFM-файла проводится согласно алгоритму, использованному Markus Stephan Скачать исходные коды проекта: (4.8K)
(начало) Эта статья о том, что в компоненте ListView существенно нехватает одного свойства и о все той же программе для редактирования файлов с убранными ошибками и более грамотным интерфейсом.
Дополнительное выравнивание пропорциональных шрифтов
символа является фиксированной величиной. То есть, ширина задана создателем шрифта для каждого конкретного символа и не зависит от его положения в тексте.
Это неизбежно порождает эффект неравномерности при выводе текста на экран, которое не замечает большинство рядовых пользователей, но хорошо известный дизайнерам. Введите, например, в Word-е следующий текст: "AVALLOOOOOLTLTLTL"
Обратите внимание, что видимое расстояние между буквами A и V заметно больше, чем между соседними O. С точки зрения компьютера все в порядке - грубо говоря, Windows считает межсимвольным расстоянием разницу между самой правой точкой предыдущего символа и самой левой текущего. Но с точки зрения дизайна - это помарка, которую стандартными средствами исправить невозможно или очень сложно (вручную регулировать межсимвольный интервал в Word-e). К слову сказать, Adobe Photoshop пытается бороться с этим явлением (опция "Auto Kerning"), но не всегда у него получается то, что надо. Например, для Arial приведенный выше текст будет выглядеть хорошо, для Times - не очень и даже "очень не". | |
В прикладных задачах неровности шрифта можно исправить хотя бы частично, если рассчитывать и запоминать расстояния между каждыми парами символов. Предлагаемый невизуальный компонент позволяет это делать для символов из одного шрифта, запоминая межсимвольные расстояния при инициализации. При настройке объекта на конкретный шрифт, а точнее, канву, задается минимальное межсимвольное расстояние и строка, представляющая собой набор символов, для которых будет производиться дополнительное выравнивание. После того, как настройка произведена, печать текста производится с помощью процедуры, аналогичной TextOut. Примечание: компонент написан под библиотеку KOL (1.55), но может быть легко портирован и под VCL, так как разницы в их работе в данном случае немного. Пример работы компонента:
Две верхние строчки напечатаны с дополнительны выравниванием, две нижние - стандартными средствами. Очевидно, что видимые расстояния между символами стали выглядеть гораздо лучше. | Объекты и процедуры модуля FineFont | TFineFont- объект, в котором хранятся данные о межсимвольных расстояниях. Он же производит расчет расстояний и печать выравненного текста. Объект статический SetMinDistance- процедура установки межсимвольного расстояния. Может и должна вызываться перед инициализацией шрифта. AssignFont - настройка компонента на готовую канву. Шрифт канвы должен быть заранее проинициализирован. Внимание - шрифт канвы не должен быть растровым!
Надо заметить, что настройка компонента на новый шрифт происходит довольно медленно, несмотря на все оптимизации. Для символьного набора из 52 символов (английские заглавные + строчные буквы) инициализация идет 5 секунд на PII-233. Также очень сильно влияет и размер символов. Для ускорения работы следует как можно сильнее сжать символьный набор, исключить символы c вертикальными линиями по бокам - O, M, N, W и так далее.
Но есть и другой способ: AssignFontEx - настройка компонента с помощью сохраненных заранее параметров. Естественно, следует следить, чтобы настройки канвы в момент инициализации компонента совпадали с теми, которые вы сохранили когда-то с помощью процедуры SaveSettings. SaveSettings - сохраняет данные о символьном наборе и межсимвольных расстояниях в файл на диске. TextOut - печатает текст на заданной при вызове AssignFont канве. Аналог соответствующей функции TCanvas. Отличие в том, что эта функция не очищает фон перед печатью. В передаваемой строке могут содержаться символы, не входящие в символьный набор, заданный при вызове AssignFont. Они будут выводиться стандартным способом. CloseFont - освобождение занятой памяти. Процедура должна быть вызвана перед следующим вызовом AssignFont.
P.S. Что касается развития компонента. Можно подумать о том, чтобы при настройке отсеивать несколько крайних точек символа, поскольку засечки на буквах в шрифтах типа Times "мешаются под ногами" и увеличивают видимое расстояние между символами.
Скачать пример: (35K) © Михаил Рудаков aka Miek, 11/2002
Доступ к реестру Win9x/ME без WinAPI
Думаю, каждый пользователь Windows хоть раз сталкивался с необходимостью чтения данных из неактивных файлов реестра. Существует, как минимум, 2 варианта решения данной задачи: воспользоваться стандартной утилитой regedit, которая может экспортировать и импортировать данные из файлов реестра. Второй вариант: использовать очень добротную и аккуратно написанную программу regview (для MSDOS), с отличным пользовательским интерфейсом ( - там же описание формата файлов реестра Win9x на русском).
К сожалению, для работы в пакетном режиме подходит лишь regedit. При этом, скорость оставляет желать лучшего...
Хочется поделиться с Вами, уважаемые, небольшим компонентом TRawRegistry для доступа к информации неактивных и активных файлов реестра формата Win9x/ME. Сразу оговорюсь: я не программист, и не собираюсь таковым быть, поэтому качество реализации оставляет желать лучшего (за неимением гербовой пишут на почтовой =)
TRawRegistry - реализует все методы и свойства класса TRegistry, необходимые для чтения ключей и значений параметров из реестра. На данный момент существуют, минимум, две основные проблемы: работа с бинарными данными и общая скорость доступа к файлам. Из-за использования динамических массивов (что легко исправимо), компилируется, начиная с D4.
В качестве примера прилагаю программу, использующую данный класс. Это аналог утилиты regedit, но без возможности записи какой бы то ни было информации в реестр.
Скачать архив (18K) Искренне Ваш. Илья Маляренко
Еще один комментарий к статье по поводу wsprintf
Предполагаемая история появления статьи "Использование функции wsprintf".
Будущий ю, это в данном случае не так важно, лишь бы она поддерживала переменное число параметров), пытается использовать в Delphi - а она не работает. Нет бы подумать, разобраться, почему в windows.pas функция объявлена так странно? Неужели в Borland при переводе заголовочных файлов допустили такую явную ошибку? Но нет, сразу объявляется главный враг - это конечно Delphi и принятый в ней способ передачи параметров (кстати наиболее надежный, представьте что будет если процедура ожидает всего два параметра, а вы передадите три) и все дальнейшее обсуждение сводится к опусканию Delphi в контексте "как в C все просто, как в Delphi сложно и некрасиво" (уточняю, это мое личное впечатление от данной заметки и от некоторых статей обсуждаемого е же параметры, которые нужно подставить в исходную строку? Если подумать, то единственный разумный ответ: они уже должны быть в стеке. Теперь осталось еще совсем немного подумать и написать такую простенькую процедуру-обертку: procedure cdecl_(Output: PChar; Format: PChar; args: array of integer); begin wsprintf(Output, Format) end; А теперь сравните данную процедуру (всего одна строчка) с теми монстрами, которые приведены в обсуждении статьи. И кто сказал что программа на Delphi не может быть красивой? Проверяем: var i: integer; mes: PChar; begin GetMem(mes, 255); i := 2; cdecl_(mes, PChar('%d+%d=%d'), [i, i, i + i]); ShowMessage(mes); FreeMem(mes) end;
Работает. Что неудивительно (достаточно посмотреть получившийся ассемблерный код во встроенном отладчике). Следовательно, единственный вывод, который можно сделать - недостаточное знакомство автора заметки с темой обсуждения. Не верите? - откройте статью "Open array parameters" во встроенной справке Delphi (5.0) и прочитайте: "When you pass an array as an open array value parameter, the compiler creates a local copy of the array within the routine's stack frame".
Еще раз о нечетком сравнении строк
По мотивам обсуждения статьи Второй вариант поиска : (359 K)
Отличия от первого, по моим субъективным наблюдениям, в следующем: 1.Лучше находит похожие слова лежащие в одной плоскости Соха - Сноха - совпадение 88 при том что в первом алгоритме составило 66 при длине фразы = 3 2.Хуже находит похожие (даже идентичные), но перевернутые слова Тихий Дон - Дон Тихий - в первом способе совпадение 79 во втором 55 - очень существено. Так что первый способ я рекомендовал для сравнения, например, полей двух баз данных. Второй способ, по моему убеждению, лучше использовать в поиске по словарю или в тех местах, где надо найти фразу.
Вообще-то, существуют еще, кроме этих, алгоритмы поиска. Я бы выделил SoundEx для сравнения, но у него есть свои недостатки — он языкозависим, но отлично подходит для сравнения английских фраз. Если вас это заинтересует то могу прислать в оригинале (написан он на C), но перевести в Pascal для людей которых это заинтересует, не составит труда. И, напоследок, предлагаю вам архив с примерами алгоритмов анализа строк. К сожалению страницы указанные в архиве, как начальные, где можно найти информацию, не работают - поэтому высылаю слепок с сайта. Скачать (185 К) На данных страницах лежит очень много алгоритмов касающихся анализа строк, приведу список причем очень хорошо документированных и математически обоснованных. 3. ОБЗОР АЛГОРИТМОВ 3. 1 Сопоставления строк 3. 2 Расстояния между строками 3. 2.1 Обобщенные задачи 3. 3 Нечеткое сопоставление строк 3. 3.1 Специальные устройства 3. 4 Максимальная повторяющаяся подстрока 4. АЛГОРИТМЫ 4. 1 Поиск образцов 4. 1.1 Наивный подход 4. 1.2 Кнут-Моррис-Пратт 4. 1.3 Бойер-Мур 4. 1.4 Бойер-Мур-Хорспул 4. 1.5 Сандей: Быстрый поиск, Максимальный сдвиг, Оптимальное несовпадение 4. 1.6 Хьюм и Сандей. Улучшенные алгоритмы Бойера-Мура и Наименьшая цена 4. 1.7 Харрисон 4. 1.8 Карп-Рабин 4. 2 Расстояние между строками и самая длинная общая подпоследовательность 4. 2.1 Вагнер-Фишер 4. 2.2 Хиршберг 4. 2.3 Хант-Шиманский 4. 2.4 Машек-Патерсон 4. 2.5 Укконен 4. 2.6 Самая тяжелая общая подпоследовательность 4. 3 НЕЧЕТКОЕ СОПОСТАВЛЕНИЕ СТРОК 4. 3.1 k несовпадений - Ландау-Вишкин 4. 3.2 k различий - Ландау-Вишкин 4. 4 Самая длинная повторяющаяся подстрока 4. 4.1 Наивный подход 4. 4.2 Суффиксные деревья Кузан Дмитрий
Flexible Frame - механизм добавления
й Перовский, дата публикации 02 января 2003г. |
Часто встречаются задачи, в которых все объекты могут отличаться друг от друга по структуре и алгоритмам. Описывать для каждого экземпляра отдельный тип неэффективно. Алгоритмические особенности отдельного экземпляра могут быть реализованы при помощи обработчиков событий. А как быть с полями? Для решения этой задачи предлагается механизм Flexible Frame (гибкий каркас). Что же стоит за таким парадоксальным названием. Просто у объекта, кроме стандартных полей, имеется список дополнительных именованных атрибутов различного типа. В прилагаемых модулях список реализован на основе TStrings. Естественно, для использования в качестве атрибутов, все элементарные типы данных должны быть "упакованы" в соответствующие объекты. Модуль uFFbase содержит описание следующих классов: TFF- класс Flexible Frame список именованных атрибутов различных типов; TFFI-базовый абстрактный класс элементов списка(Flexible Frame Item); TFFITypeForm-диалог определения названия и типа для нового атрибута. Модуль uFF предоставляет простейшие типы элементов списка: TFFIinteger - целое число; TFFReal - вещественное число; TFFIstring - строка; TFFIFileName - имя файла.
Модуль uFFform описывает FormFF - диалог для редактирования значений атрибутов простейших типов. Более сложные типы могут иметь собственные формы для редактирования, как это сделано в ObjectInspector-е. Модуль uFFDic - пример расширения стандартного набора типов. TFFSlovar - класс атрибутов с перечислимым значением, хранит код строки в словаре и код словаря. Описан список для регистрации словарей. TFFDicForm - диалог для редактирования значений. Скачать (10K)
FloatSpinEdit. Компонент для ввода целых и дробных чисел
В своих приложениях часто сталкиваюсь с необходимостью обеспечить удобный ввод дробных чисел. Для этого разработал компонент TFloatSpinEdit. Более чем уверен что подобных компонент разработано немало, но в силу лени и прочего, не искал оного в Internet, а написал своё.
Компонент FloatSpinEdit предназначен для ввода чисел целых и дробных чисел. Вводить число можно как непосредственно с клавиатуры в поле ввода, так и увеличивая/уменьшая его значение при помощи компонента типа TUpDown или клавиш Up/Down. Кроме этого осуществляется контроль допустимого диапазона вводимого числа и корректности ввода. Существует так же возможность отображения суффикса (например "А/м"), после числа.
Компонент представляет собой контейнер (TWinControl), содержащий два компонента FEdit(TFloatEdit - потомок TEdit) и FUpDown(TUpDown)." В компоненте введены следующие новые свойства, доступные как на стадии разработки, так и на стадии выполнения: UpDownPosition - определяет положение компонента FUpDown относительно, компонента FEdit (слева/справа), значение по умолчанию справа; Precision(0..15) - определяет отображаемое количество значащих цифр, введённого числа Значение по умолчанию равно 2; Sufix(string) - строка длинною не более 20 символов, определяющая суффикс (например "см") выводимый после введённого числа через пробел. По умолчанию - пустая строка; Min(Extended) - определяет минимально допустимое значение вводимого числа. По умолчанию равно 100; Max(Extended) - определяет максимально допустимое значение вводимого числа. По умолчанию равно -100; Step(Extended) - определяет шаг изменения значения вводимого числа, при изменении его значения посредством клавиш Up/Down или компонента UpDown. По умолчанию равно 0.25; NumberValue(Extended) - определяет значение введённого числа, если введеноn некорректное число, свойство принимает значение DefNumberValue; DefNumberValue(Extended) - определяет значение числа, при вводе некорректного значения. По умолчанию равно 0; ArrowKeys(Boolean) - определяет можно ли использовать клавиши Up/Down для изменения значения вводимого числа. По умолчанию True; CheckOnExit(Boolean) - определяет, будет ли контролироваться значение введённого числа, при потере компонентом фокуса. Контроль допустимого диапазона введённого значения осуществляется, следующим образом. Если введено некорректное значение (например "0..2."), то значение числа будет равным свойству DefNumberValue, если значение больше Max, то оно устанавливается равным Max, если значене менее Min, то то оно устанавливается равным Min. Контролируемое значение числа можно получить, прочитав свойство NumberValue или обратившись к методу DefineValue. В определенных случаях будет так же проконтролировано символьное представление числа в свойстве Text, после чего свойство Text будет отформатировано в соответствии со свойствами Precision и Sufix: изменение значения вводимого числа клавишами Up/Down или посредством компонента FUpDown; при потере компонентом фокуса (событие OnExit), если задан флаг CheckOnExit; Более подробное описание в прилагаемом файле FloatSpinEdit.txt.
Скачать (35 К)
Функции для работы с модулем
Все операции с формулами обрабатывает объект класса TDataEditor. У этого класса есть ряд методов для регистрации новых функций, типов, превода формул в сценарии и выполнения этих сценариев. Объект класса TDataEditor может хранить в себе одну формулу и один сценарий. Если Вы не собираетесь создавать новые функции, то можно просмотреть только описание функций преобразования строки в сценарий, думаю этого будет вполне достаточно, в противном случае я советую особое внимание уделить описанию методов регистрации новых функций и событию OnIntFunction. function RegisterIntFunction(const FunctionName: string; RequireValue1, RequireValue2: Boolean): Integer; Регистрирует математическую функцию с именем FunctionName. Параметры RequireValue1 и RequireValue2 означают, требуются ли этой функции параметры стоящие перед ней или после нее соответственно. Возвращает идентификатор зарегистрированной функции. Если Вы зарегистрировали новую функцию, то на Вас ложится ответственность за проведение расчета этой функции. Расчет функции придется делать каждый раз при расчете сценария. Этот расчет будет происходить в пределах события OnIntFunction (он описывается ниже), в котором Вам будет передан индекс Вашей новой функции и параметры, если необходимость их наличия была указана в функции, которую мы сейчас обсуждаем (RequireValue1 и RequireValue2). function UnRegisterIntFunction; Удаляет ранее зарегистрированную функцию по ее имени или ее идентификатору. function RegisterBoolFunction(const FunctionName: string; RequireValue1, RequireValue2: Boolean): Integer; По смыслу тоже что и функция RegisterIntFunction. function RegisterType(const TypeName: string; TypeID: Integer): Integer; Регистрирует тип с именем TypeName и присваивает ему идентификатор TypeID. Возвращает идентификатор зарегистрированного типа. function UnRegisterType: Удаляет ранее зарегистрированный тип по имени или по его идентификатору function StringToIntScript(const S: string; out Script: TScript; OpenedBracket: Char = '('; ClosedBracket: Char = ')'); Переводит строку S в сценарий Script. В параметрах OpenedBracket и ClosedBracket содержится символы начала и конца вложенной формулы function StringToBoolScript(const S: string; out Script: TScript; OpenedBracket: Char = '('; ClosedBracket: Char = ')'); По смыслу тоже, что и функция StringToIntScript. function ExecuteIntScript(P: Pointer): Double; Выполняет математический сценарий с адресом P и возвращает результат сценария function ExecuteInt: Double; Выполняет математический сценарий, содержащийся в свойстве Script. function ExecuteBoolScript(P: Pointer): Boolean: По смыслу то же, что и функция ExecuteIntScript. function ExecuteBool: Boolean; По смыслу то же, что и функция ExecuteInt. property Script: TScript; содержит сценарий property Formula: string; содержит формулу property Accuracy: TRoundToRange; Точность вычисления операций. Проще говоря, это второй параметр функции RoundTo - см. справку Delphi property OnIntFunction: TIntFunctionEvent; TIntFunctionEvent = function(FunctionID: Integer; TypeID: Integer; var Value1: Double; Value2, Value3: Double): Boolean; Эта событие должно обрабатываться в том случае, если зарегистрированы новые функции. Параметр FunctionID возвращает идентификатор функции, которая должна быть вычислена. Параметр TypeID указывает идентификатор типа (который, например, мог быть создан функцией RegisterType). Это событие будет возникать каждый раз при вычислении каждой функции в сценарии, даже если это стандартная функция. Если Вы хотите передать управление подпрограмме, которая обрабатывает стандартные функции в сценарии, то результат функции должен быть истина. Таким образом можно перекрыть стандартные функции, т.е. изменить их метематику. Результат выполнения функции нужно поместить в параметр Value1. Параметры Value2 и Value3 возвращают параметры функции, но только в том случае, если они требуются (это указывается при создании новой функции, см. RegisterIntFunction и RegisterBoolFunction). property OnBoolFunction: TBoolFunctionEvent; TBoolFunctionEvent = function(FunctionID: Integer; TypeID: Integer; var Value1: Boolean; Value2, Value3: Double): Boolean of object; По смыслу то же, что и событие OnIntFunction.
Функции для работы со строками
Несколько функций для работы со строками. Они довольно простые, но может кому-нибудь пригодятся.
| Разбивка строки в список и слияние списка строк |
1. Разбивка строки на подстроки с учетом заданного символа(строки) разделителя Str - исходная строка, R - символ(строка) разделитель, в результате получается список TStrings найденных строк. function StrToArrays(str, r: string; out Temp: TStrings): Boolean; var j: integer; begin IF temp <> Nil then Begin temp.Clear; while str <> '' do Begin j := Pos(r,str); if j=0 then j := Length(str) + 1; temp.Add(Copy(Str,1,j-1)); Delete(Str,1,j+length(r)-1); End; Result:=True; End else Result:=False; end;
2. Слияние списка строк в одну строку с вставкой символа(строки)-разделителя function ArrayToStr(str: TStrings; r: string): string; var i: integer; begin Result:=''; IF str = nil Then Exit; for i:= 0 to Str.Count-1 do Result := Result+Str.Strings[i]+r; end;
Дополнителльно по этой же теме Cмотрите реализацию функций TStrings.GetCommaText и TStrings.SetTextStr в модуле Classes Коллективное творчество нескольких авторов
Функция копирования части строки
При работе со строками часто возникает необходимость копировать кусок строки от одного символа (или нескольких) до другого (других). Каждый раз использовать copy или delete нерационально, поэтому я написал небольшую функцию:
| function GetBetween(first,second,line:string):string; var posF,posS,i:integer; st:string; index:boolean; begin st:=''; posF:=pos(first,line)+length(first);//начало копирования posS:=pos(second,line);//конец копирования index:=true; i:=1; while (i<=length(line))and(index) do begin if (i>=posF)and(i | Есть правда одно ограничение: если в строке встречается несколько одинаковых кусков и такой кусок выбран в роли first или second, то результат не всегда будет корректным.
Функция посылает окну строку синхронно через WM_COPYDATA
Функция посылает окну строку (с дополнительным любым числом) синхронно через WM_COPYDATA. Можно и другому приложению. function SendIPCString(TargetWnd, SourceWnd: THandle; dwData: integer; const S: string): integer; var CD: TCopyDataStruct; begin CD.dwData := dwData; CD.cbData := Length(S); if CD.cbData = 0 then CD.lpData := NIL else CD.lpData := @S[1]; Result := SendMessage(TargetWnd, WM_COPYDATA, SourceWnd, integer(@CD)); end;
Алексей Еремеев
Функция приблизительного/нечеткого сравнения строк
Недавно в поисках информации по интеллектуальным алгоритмам сравнения я нашел такой алгоритм — алгоритм сравнения (совпадения) двух строк, Так как он был написан на VBA, я под свои нужды переписал его на Delphi Уважаемые жители Королевства, я думаю данная функция пригодится тем, кто часто пишет функции поиска, особенно когда поиск приблизителен. То есть, например, в БД забито "Иванав Иван" - с ошибкой при наборе, а ищется "Иванов". Так вот, данный алгоритм может вам найти "Иванав" при вводе "Иванов",а также при "Иван Иванов" - даже наоборот с определенной степенью релевантности при сравнении. А используя сравнение в процентном отношении, вы можете производить поиск по неточным данным с более-менее степенью похожести. Еще раз повторяю, алгоритм не мой, я только его портировал на Delphi. А метод был предложен Владимиром Кива, за что ему огромное спасибо. | |
Скачать проект (356 K) Функция нечеткого сравнения строк БЕЗ УЧЕТА РЕГИСТРА
| //------------------------------------------------------------------------------ //MaxMatching - максимальная длина подстроки (достаточно 3-4) //strInputMatching - сравниваемая строка //strInputStandart - строка-образец // Сравнивание без учета регистра // if IndistinctMatching(4, "поисковая строка", "оригинальная строка - эталон") > 40 then ... Type TRetCount = packed record lngSubRows : Word; lngCountLike : Word; end; //------------------------------------------------------------------------------ function Matching(StrInputA: WideString; StrInputB: WideString; lngLen: Integer) : TRetCount; Var TempRet : TRetCount; PosStrB : Integer; PosStrA : Integer; StrA : WideString; StrB : WideString; StrTempA : WideString; StrTempB : WideString; begin StrA := String(StrInputA); StrB := String(StrInputB); For PosStrA:= 1 To Length(strA) - lngLen + 1 do begin StrTempA:= System.Copy(strA, PosStrA, lngLen); PosStrB:= 1; For PosStrB:= 1 To Length(strB) - lngLen + 1 do begin StrTempB:= System.Copy(strB, PosStrB, lngLen); If SysUtils.AnsiCompareText(StrTempA,StrTempB) = 0 Then begin Inc(TempRet.lngCountLike); break; end; end; Inc(TempRet.lngSubRows); end; // PosStrA Matching.lngCountLike:= TempRet.lngCountLike; Matching.lngSubRows := TempRet.lngSubRows; end; { function } //------------------------------------------------------------------------------ function IndistinctMatching(MaxMatching : Integer; strInputMatching: WideString; strInputStandart: WideString): Integer; Var gret : TRetCount; tret : TRetCount; lngCurLen: Integer ; //текущая длина подстроки begin //если не передан какой-либо параметр, то выход If (MaxMatching = 0) Or (Length(strInputMatching) = 0) Or (Length(strInputStandart) = 0) Then begin IndistinctMatching:= 0; exit; end; gret.lngCountLike:= 0; gret.lngSubRows := 0; // Цикл прохода по длине сравниваемой фразы For lngCurLen:= 1 To MaxMatching do begin //Сравниваем строку A со строкой B tret:= Matching(strInputMatching, strInputStandart, lngCurLen); gret.lngCountLike := gret.lngCountLike + tret.lngCountLike; gret.lngSubRows := gret.lngSubRows + tret.lngSubRows; //Сравниваем строку B со строкой A tret:= Matching(strInputStandart, strInputMatching, lngCurLen); gret.lngCountLike := gret.lngCountLike + tret.lngCountLike; gret.lngSubRows := gret.lngSubRows + tret.lngSubRows; end; If gret.lngSubRows = 0 Then begin IndistinctMatching:= 0; exit; end; IndistinctMatching:= Trunc((gret.lngCountLike / gret.lngSubRows) * 100); end; | Кузан Дмитрий
Функция скрытия пиктограмм с рабочего стола
Заметил в софтовых архивах, что появляються shareware программы с функцией скрытия пиктограмм с рабочего стола и эта возможность в некоторых декларируется как одна из самых "крутых". Стало обидно и смешно одновременно, поэтому набросал примерчик для тех кому интересно. Чтобы скрыть пиктограммы, необходимо найти окно SysListView32, что делает код приведенный ниже. procedure TFrmProgman.FormCreate(Sender: TObject); begin FHandle := GetWindow(GetWindow(FindWindow('progman', nil), GW_CHILD), GW_CHILD); end; А здесь мы элементарно скрываем это окно или показываем. procedure TFrmProgman.btnHideClick(Sender: TObject); begin if TButton(Sender).Name = 'btnHide' then ShowWindow(FHandle, SW_HIDE) else ShowWindow(FHandle, SW_SHOW); end; При этом все функции рабочего стола активны - видна картинка, работает контекстное меню. Если же вы хотите и это убрать, тогда получите дескриптор окна progman.
(3K) — Пример для Дельфи 6, но код можно запустить под любой версией.
Исходники
Скачать модуль Скачать программу тестирования скорости Скачать практический пример
Использование буфера записей BDE
Все идет к тому, что BDE в ближайшее время окончательно сдаст позиции компонентам прямого доступа к данным (IBX, dbExpress). Но все наработанное с использованием BDE сразу не перепишешь и не выбросишь. Компоненты прямого доступа существенно расширяют возможности разработчика. Недавно понадобилось напрямую работать с буфером записей запроса (TQuery), если бы можно было использовать IBQuery проблем бы с этим не возникло, но буфер записей BDE закрыт и просто до него не достучаться. Задача стояла следующая: в БД (Interbase) при работе с достаточно большой таблицей появилась необходимость при навигации в ReadOnly DBGrid и нажатию короткой клавиши отмечать записи для отложенной печати (поле SOST := 1).
Данная задача решается несколькими способами: Перевести Query в режим редактирования установить поле в необходимое значение и вызвать метод Query.Post; C использованием другого Query выполнить Update записи, затем переоткрыть Query. C использованием другого Query выполнить Update записи, затем в буфере записей выставить значение нужного поля. Первый метод не подходит по понятным соображениям, к тому же в нашем случае Query не редактируемый (RequestLive = false). Второй слишком долгий и ведет к увеличению сетевого трафика. Третий метод возможно реализовать только с использованием IBX или ClientDataSet, что в этом конкретном случае не приемлемо. Поэтому для решения задачи третьим методом пришлось искать где BDE хранит полученные от IB сервера данные, вот что из этого получилось: unit Unit1; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, Db, DBTables, Grids, DBGrids, BDE, Menus; type TForm1 = class(TForm) DataSource: TDataSource; Query: TQuery; DBGrid: TDBGrid; Database: TDatabase; SetFldQ: TQuery; PopupMenu: TPopupMenu; Sost1: TMenuItem; Sost0: TMenuItem; procedure FormCreate(Sender: TObject); procedure Sost1Click(Sender: TObject); procedure Sost0Click(Sender: TObject); private { Private declarations } public { Public declarations } procedure SetSost(AValue: Integer); end; var Form1: TForm1; implementation {$R *.DFM} function GetBDERecBuff(ACursor: TQuery): Pointer;{cursor} Var P, P1: Pointer; CurNo, RecNo, RecSize: Integer; begin //Вызов этого метода синхронизирует положение курсора //DataSet и BDE ACursor.UpdateCursorPos; P := ACursor.Handle; Inc(PChar(P), $1E); P := Pointer(P^); Inc(PChar(P), $7E); P := Pointer(P^); Inc(PChar(P), $14); P := Pointer(P^); Inc(PChar(P), $36); P := Pointer(P^); // Получаем внутренний BDE-шный номер текущей записи P1 := P; Inc(PChar(P1), $A); Inc(PChar(P1), $2); RecNo := Integer(P1^) - 1; Inc(PChar(P), $4); P := Pointer(P^); // Получаем внутренний BDE-шный номер курсора P1 := P; Inc(PChar(P1), $11F); P1 := Pointer(P1^); CurNo := Word(P1^); // Получаем размер записи P1 := P; Inc(PChar(P1), $113); RecSize := Word(P1^); // Получаем указатель на массив где хранятся указатели на // буфера всех BDE курсоров Inc(PChar(P), $4); P := Pointer(P^); Inc(PChar(P), $68); P := Pointer(P^); // Выбираем из массива нужный нам указатель Inc(PChar(P), 4*(CurNo - 1)); P := Pointer(P^); // Получаем указатель на текущую запись Inc(PChar(P), RecNo * RecSize); Result := P; end; procedure PutFldToBDEBuf(ACursor: TQuery; AField: TField; pValue: Pointer); Var P: Pointer; begin // Получаем указатель на текущую запись P := GetBDERecBuff(ACursor); //складываем нужное значение в буфер BDE Check(DbiPutField(ACursor.Handle, AField.FieldNo, P, pValue)); //Вызов Resync для пересчета Calc-полей и немедленного отображений изменении на экране ACursor.Resync([]); end; procedure TForm1.FormCreate(Sender: TObject); begin Database.Open; Query.DataBaseName := Database.DatabaseName; SetFldQ.DataBaseName := Database.DatabaseName; DBGrid.PopupMenu := PopupMenu; Sost1.ShortCut := TextToShortCut('Ctrl+A'); Sost0.ShortCut := TextToShortCut('Ctrl+S'); Query.SQL.Text := 'SELECT * FROM AKODIF ORDER BY CODE'; Query.Open; SetFldQ.SQL.Text := 'UPDATE AKODIF SET SOST = :SOST WHERE CODE = :CODE'; SetFldQ.Prepare; end; procedure TForm1.SetSost(AValue: Integer); begin SetFldQ.ParamByName('SOST').AsInteger := AValue; SetFldQ.ParamByName('CODE').AsInteger := Query.FieldByName('CODE').AsInteger; SetFldQ.ExecSQL; PutFldToBDEBuf(Query, Query.FieldByName('SOST'), @AValue); end; procedure TForm1.Sost1Click(Sender: TObject); begin SetSost(1); end; procedure TForm1.Sost0Click(Sender: TObject); begin SetSost(0); end; end. Все описанное выше работает в Delphi 3, Delphi 4, Delphi 5. С BDE 5.01, idapi32.dll от 12.11.1999 размер 589 312. С другими версиями BDE скорее всего работать не будет!
Все, вышеописанное есть некий частный результат и интересует эта тема.
Использование функции wsprintf()
Данная заметка возможно, будет интересна для тех, кто программирует на Object Pascal с использованием только API-функций Windows.
Часто необходимо вывести (к примеру, даже в окошко с MB_OK) какое-то значение, будь то код ошибки типа HRESULT или результат работы какой-либо функции. Простейшее решение - использовать IntToStr() из модуля sysutils.pas. Однако в этом случае размер минимальной программы на Delphi увеличится с 16 до 41 кб, что лично меня очень неудовлетворяет. Хочется получить реальное преимущество в размерах, раз уж не используется VCL. Есть и другое объяснение - хотя мы и живём в эпоху быстрых процессоров и ёмких HDD, следить за качеством кода программ и его размерами обязывает культура программирования. В конце-концов это просто неразумно - ради одной функции тащить весь код из sysutils.pas и sysconst.pas. Складывается глупейшая ситуация - ради преобразования чисел в строку приходится мириться с тем, что к готовому exe-модулю подключаются многочисленные resourcestring и хлам в виде кучи ненужного кода. Для некоторых преобразований можно использовать API-функцию wsprintf() из модуля windows.pas. Она позволяет произвести форматированную запись в буфер последовательности символов и значений аргументов. Вот как она описана: function wsprintf(Output: PChar; Format: PChar): Integer; stdcall; А вот так она описана в файле winuser.h: WINUSERAPI int WINAPIV wsprintfA(LPSTR, LPCSTR, ...); Как видно, осутствует третий параметр. Он подразумевает передачу значения произвольного типа - например, char* или int (при желании его можно даже опустить). Причина, по которой функция документирована неправильно - ограничение, накладываемое языком Object Pascal - он не позволяет передать в параметр функции значение произвольного типа (хотя, например, SizeOf() позволяет, но реализована она на системном уровне).
Я всё-таки нашёл способ приспособить wsprintf() для своих нужд. Выход - описать её самостоятельно в своей программе так: function wsprintf( lpOut: LPSTR; lpFmt: LPCTSTR; p: Pointer ): Integer; stdcall; external 'user32.dll' name 'wsprintfA'; Теперь для вывода в буфер достаточно написать так: var szMessage: PChar; GetMem( szMessage, 256 ); // Определяем размер строки в памяти wsprintf( szMessage, 'Number %d', Pointer(899034)); MessageBox( 0, szMessage, 'Сообщение', MB_OK ); Предварительно надо выделить память для буфера, иначе форматирование не произойдёт.
Полную документацию по функции wsprintf() можно найти в справке Delphi. Добавлю, что для корректности можно приводить передаваемые значения не к типу Pointer, а к Pinteger - для типа Integer, PUINT - для целых беззнаковых и т. д. Скачать demo-проект (0.67 K)
Смотрите также :
Использование команды RDTSC процессора Pentium для работы с малыми временными интервалам
| Раздел Сокровищница | нашел интересное использование команды RDTSC процессора Pentium для работы с малыми временными интервалами. Я думаю, что эта функция может найти широкое применение (в таймерах, управлении внешними устройствами, научных исследованиях).
Пример прилагается (4K) Этот счетчик увеличивается на 1 на каждом такте CPU. Он стартует при включении компьютера или при нажатии кнопки RESET. Обычно функцию RDTSC используют при определении тактовой частоты процессора. Применяя программные ухищрения можно добиться измерения очень малых временных величин в реальном масштабе времени или применять для калибровки таймеров (предварительно определив при помощи этой же функции тактовую частоту процессора). Готовые примеры определения тактовой частоты при помощи функции RDTSC есть в интернете, например, на сайте Мастера Delphi" : function RDTSC: comp; var TimeStamp: record case byte of 1: (Whole: comp); 2: (Lo, Hi: Longint); end; begin asm db $0F; db $31; {$ifdef Cpu386} mov [TimeStamp.Lo], eax mov [TimeStamp.Hi], edx {$else} db D32 mov word ptr TimeStamp.Lo, AX db D32 mov word ptr TimeStamp.Hi, DX {$endif} end; Result := TimeStamp.Whole; end; |
Использование модуля
Заводим переменную типа TFormula: TFormula=record CS:CodeSeg; //массив с кодом DS:DataSeg; //массив с переменными и константами proc:tproc; //вызываемая функция end; var formula1:tformula; Вызываем процедуру компиляции кода, в которой указываем нашу formula1, список имен используемых в ней переменных, и, конечно, входную строку. Для вычисления значения функции в formula1.DS записываем значения переменных в том порядке, в котором их имена фигурировали в списке (при этом важно изменять только первые 0..число переменных-1 элементы DS, т.к. в последующих элементах хранятся значения констант из входной строки), а затем вызываем formula1.proc, которая и возвратит искомое значение. Информация более конкретного характера содержится в самом модуле.
Использование SetRange
Метод procedure SetRange(const StartValues, EditValues: array of const); показывает не только записи, индексные поля которых лежат в диапазоне [StartValues..EndValues].
Пример: Пусть в наборе данных Table1 показываются все записи. Включим в структуру записинабора данных два поля: "Номер группы" и "Наименование товара". Пусть итекущий индекс построен по полю "Номер группы".
Напишем такой обработчик события: CheckBox1.Click:
|
|
| procedure TForm1.CheckBox1Click(Sender: TObject); var GrNumTmp: Integer; begin If CheckBox1.Checked then begin GrNumTmp := StrToInt(Edit1.Text); With Table1 do begin CancelRange; SetRange([GrMunTmp],[GrNumTmp]); end; end else Table1.CancelRange; end; |
В отфильтрованном наборе данных показываются только те записи, индексное поле текущего индекса у которых (в нашем случае "Номер группы") имеет значение, лежащее в заданном диапазоне. В данном случае диапазон определяется переменной GrNumTmp. Поэтому для GrNumTmp = 3 будут показаны записи, принадлежащие к группе 3.
Если бы мы захотели, чтобы в наборе данных фильтровались записи из нескольких групп, то нам следовало бы добавить в форму второй компонент Edit2, в котором вводился бы номер конечной группы, в то время как в Edit1 вводился бы номер начльной группы:
|
|
| procedure TForm1.CheckBox1Click(Sender: TObject); var GrNumTmp1, GrNumTmp2: Integer; begin If CheckBox.Checked then begin GrNumTmp1 := StrToInt(Edit1.Text); GrNumTmp2 := StrToInt(Edit2.Text); With Table1 do begin CancelRange; SetRange([GrNumTmp1],[GrNumTmp2]); end; end else Table1.CancelRange; end; |
Александр Мефодьев [29.01.2000] (Специально для "Королевства")
ICQ: 56666220
Использованные идеи и алгоритмы
Алгоритм Эрли проверки корректности входной строки. Упрощенный вариант алгоритма Дейкстры перевода в обратную польскую запись на основе стека с приоритетами. Способ формирования кода в памяти из программы Сергея Втюрина. Методы вычисления различных математических функций из открытых исходников модуля Math.
Скачать проект (25K) Смотрите также :
Используемые инструменты
Для копирования баз данных можно использовать различные инструменты. Я использую для этих целей специально разработанную программу копирования баз данных, под названием "Репликатор". Её можно скачать по адресу Что же эта программа умеет?
Она умеет выполнять скрипты, копировать таблицы в нужной последовательности, отключать триггера, ключи, ограничения. А так же обходит ошибки вынося их текст в Лог и не обрывая соединение. А это нам и нужно для восстановления и перегенерации баз данных.
Изменение в ходе выполнения
Сразу скажу, что эта статья - маленькая рекомендация тем, кто хочет реализовать возможность работы TWebBrowser в своей программе с настройками Proxy , которые отличаются от стандартных. В один прекрасный день мне понадобилось в программе периодически менять Proxy и при этом пользоваться всем, что предоставляет IE. Лучший и единственный выбор - TwebBrowser. При близком знакомстве с ним стало понятно, что через Proxy он работать не может (вернее может, но берет настройки из "Свойств обозревателя"). Перспектива постоянно менять настройки реестра меня не прельщала . И как назло ни в одной крупной конференции не было даже упоминания о возможности настройки Proxy в ходе выполнения программы кроме изменения реестра (может плохо искал). Перерыв Fido-архивы и конференции Инета накаткнулся на win-функцию UrlMkSetSessionOption. Вот к чему привели мои изыскания : .... uses ... urlmon, wininet ... .... var PIInfo : PInternetProxyInfo; ... New (PIInfo) ; // Изменение настроек ПРОКСИ PIInfo^.dwAccessType := INTERNET_OPEN_TYPE_PROXY ; // Тип доступа в интернет - через Proxy сервер PIInfo^.lpszProxy := PChar('some.proxy:someport'); // указать прокси напр. 195.43.67.33:8080 PIInfo^.lpszProxyBypass := PChar(''); // Список адресов, доступ к которым возможен минуя Proxy сервер UrlMkSetSessionOption(INTERNET_OPTION_PROXY, piinfo, SizeOf(Internet_Proxy_Info),0); .... Dispose (PIInfo) ; .... Вызывать функцию UrlMkSetSessionOption можно из любого места программы, причем любое количество раз и с разными настройками. После вызова функции TWebBrowser будет работать через указанный прокси. Еще раз повторюсь, настройки касаются только текущей сессии (программы на момент выполнения ), общие настройки Windows не изменяются.
Андрей Попков Дополнительно: INTERNET_PROXY_INFO Structure Contains information that is supplied with the INTERNET_OPTION_PROXY value to get or set proxy information on a handle obtained from a call to the InternetOpen function. Syntax typedef struct { DWORD dwAccessType; LPCTSTR lpszProxy; LPCTSTR lpszProxyBypass; } INTERNET_PROXY_INFO, * LPINTERNET_PROXY_INFO; Members dwAccessType Unsigned long integer value that contains the access type. This can be one of the following values: INTERNET_OPEN_TYPE_DIRECT Internet accessed through a direct connection. INTERNET_OPEN_TYPE_PRECONFIG Applies only when setting proxy information. INTERNET_OPEN_TYPE_PROXY Internet accessed using a proxy. lpszProxy Address of a string value that contains the proxy server list. lpszProxyBypass Address of a string value that contains the proxy bypass list.
Это Unit1.pas
unit Unit1; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, Buttons,StrEx,Math; type TForm1 = class(TForm) Edit1: TEdit; BitBtn1: TBitBtn; Label1: TLabel; Memo1: TMemo; Button1: TButton; Edit2: TEdit; Label2: TLabel; Button2: TButton; procedure BitBtn1Click(Sender: TObject); procedure Button1Click(Sender: TObject); procedure Edit1Change(Sender: TObject); procedure FormCreate(Sender: TObject); procedure Button2Click(Sender: TObject); private { Private declarations } public { Public declarations } end; TProc=procedure; var Form1: TForm1; A:array of real; CS:array of Byte; DS:array of Real; Res,X,Y:real; proc:TProc; function preCalc(Ex:String):real; function Prepare(Ex:String):real; function SecindBracket(Ex:String;first:integer):Integer; implementation {$R *.DFM} // это про скобки... это просто и не заслуживает большого внимания. function SecindBracket(Ex:String;first:integer):Integer; var i,BrQ:integer; begin Result:=0; case Ex[first] of '(': begin i:=first+1; BrQ:=0; while (i<=length(Ex)) do begin if (BrQ=0) and (Ex[i]=')') then begin Result:=i;exit;end; if Ex[i]='(' then Inc(BrQ) else if Ex[i]=')' then Dec(BrQ); i:=i+1; end; end; ')': begin i:=first-1; BrQ:=0; while (i>0) do begin if (BrQ=0) and (Ex[i]='(') then begin Result:=i;exit;end; if Ex[i]='(' then Inc(BrQ) else if Ex[i]=')' then Dec(BrQ); i:=i-1; end; end; end; end; // а вот тут мы собственно и формируем процедуру function Prepare(Ex:String):real; begin SetLength(Ds,1); // вот это будет заголовок SetLength(CS,6); cs[0]:=$8b; cs[1]:=$05; cs[2]:=(integer(@ds) and $000000FF) shr 0; cs[3]:=(integer(@ds) and $0000FF00) shr 8; cs[4]:=(integer(@ds) and $00FF0000) shr 16; cs[5]:=(integer(@ds) and $FF000000) shr 24; // вот это - вычисление X:=1; //догадайтесь зачем :) preCalc(Ex); // а вот это - завершение SetLength(CS,high(CS)+7); cs[high(CS)-5]:=$DD; cs[high(CS)-4]:=$1D; cs[high(CS)-3]:=(integer(@res) and $000000FF) shr 0; cs[high(CS)-2]:=(integer(@res) and $0000FF00) shr 8; cs[high(CS)-1]:=(integer(@res) and $00FF0000) shr 16; cs[high(CS)-0]:=(integer(@res) and $FF000000) shr 24; SetLength(CS,high(CS)+2); // ну и не забудем про RET cs[high(CS)]:=$C3;// ret proc:=pointer(cs); end; // будем формировать код рассчета. function preCalc(Ex:String):real; var Sc,i,j:integer; s,s1:String; A,B:real; const Op: array [0..3] of char =('+','-','/','*'); begin s:=''; // да всегда инициализируйте переменные ваши for i:=1 to length(Ex) do if ex[i]<>' ' then s:=s+ex[i]; // чтобы под ногами не путались :) while SecindBracket(s,Length(s))=1 do s:=copy(s,2,Length(s)-2);// скобки if s='' then begin Result:=0; ShowMessage('Error !'); exit; end; val(s,Result,i); // это число ? а какое ? if i=0 then begin // ага это число. так и запишем Form1.Memo1.Lines.Add('fld '+FloatToStr(result)); SetLength(Ds,high(ds)+2); Ds[high(ds)]:=Result; SetLength(CS,high(CS)+4); cs[high(Cs)]:=high(ds)*8; cs[high(Cs)-1]:=$40; cs[high(Cs)-2]:=$DD; exit; end; if (s='x') or (s='X') then begin // опа, да это же Икс ! Form1.Memo1.Lines.Add('fld X'); SetLength(CS,high(CS)+7); cs[high(CS)-5]:=$DD; cs[high(CS)-4]:=$05; cs[high(CS)-3]:=(integer(@x) and $000000FF) shr 0; cs[high(CS)-2]:=(integer(@x) and $0000FF00) shr 8; cs[high(CS)-1]:=(integer(@x) and $00FF0000) shr 16; cs[high(CS)-0]:=(integer(@x) and $FF000000) shr 24; end; // это все еще выражение :( ох не кончились наши мучения i:=-1; j:=0; while j<=1 do Begin i:=length(s); Sc:=0; while i>0 do begin // ну скобки надо обойти if s[i]=')' then Inc(Sc); if s[i]='(' then Dec(Sc); if Sc<>0 then begin dec(i); continue; end; if (s[i]=Op[j*2]) then begin j:=j*2+10; break; end; if (s[i]=Op[j*2+1]) then begin j:=j*2+11; break; end; dec(i); end; inc(j); End; //('+','-','/','*'); // а вот и рекурсия - все что справа и слева от меня пусть обработает ... // ой да это же я:) Ну а я так уж и быть сформирую код операции в середине :) case j of 11: begin preCalc(copy(s,1,i-1) ); preCalc(copy(s,i+1,length(s)-i) ); Form1.Memo1.Lines.Add('FAddp St(1),st'); // cs //fAddP st(1),st // [DE C1] SetLength(CS,high(CS)+3); cs[high(Cs)]:=$C1; // вот такой код сформируем cs[high(Cs)-1]:=$DE; end; // далее - аналогично для каждой операции 12: begin preCalc(copy(s,1,i-1) ); preCalc(copy(s,i+1,length(s)-i) ); Form1.Memo1.Lines.Add('FSubP St(1),st'); //fSubP st(1),st // [DE E9] SetLength(CS,high(CS)+3); cs[high(Cs)]:=$E9; cs[high(Cs)-1]:=$DE; end; 13: begin try preCalc(copy(s,1,i-1) ); preCalc(copy(s,i+1,length(s)-i) ); Form1.Memo1.Lines.Add('fdivP st(1),st'); //fDivP st(1),st // [DE F9] SetLength(CS,high(CS)+3); cs[high(Cs)]:=$F9; cs[high(Cs)-1]:=$DE; except ShowMessage('Division by zero !... '); preCalc(copy(s,1,i-1) ); preCalc(copy(s,i+1,length(s)-i) ); exit; end; end; 14: begin preCalc(copy(s,1,i-1) ); preCalc(copy(s,i+1,length(s)-i) ); Form1.Memo1.Lines.Add('FMulp St(1),st'); //fMulP st(1),st // [DE C9] SetLength(CS,high(CS)+3); cs[high(Cs)]:=$C9; cs[high(Cs)-1]:=$DE; end; end; end; // Вычисляй procedure TForm1.BitBtn1Click(Sender: TObject); begin x:=StrToFloat(Edit2.text); if (@proc<>nil) then proc; // Вычисляй Label1.caption:=FloatToStr( res ); end; // это всякие сервисные функции procedure TForm1.Button1Click(Sender: TObject); begin Memo1.Clear; Prepare(Edit1.text); BitBtn1.Enabled:=true; end; procedure TForm1.Edit1Change(Sender: TObject); begin BitBtn1.Enabled:=false; end; procedure TForm1.FormCreate(Sender: TObject); begin Edit1.OnChange(self); end; // а это для того чтобы посмотреть какой за быстрый получился код procedure TForm1.Button2Click(Sender: TObject); //Speed test var t:TDateTime; i:integer; const N=$5000000; //количество повторений begin if @proc=nil then exit; t:=now; for i:=0 to N do begin x:=i; proc; x:=res; end; t:=now-t; Memo1.lines.add('work time for '+inttostr(N)+' repeats ='+TimeToStr(t)+' sec'); Memo1.lines.add('='+FloatToStr(t)+ ' days' ); end; end.
Как появляются иконки в трее.
| Раздел Сокровищница | ан Минич, дата публикации 09 июля | Иконку в трей помещают с помощью Shell_NotifyIconW. Интересено посмотреть на этот процесс с другой точки зрения.
Цитата с сайта delphi.mastak.ru: Shell_NotifyIconW просто ищет окно с классом "Shell_TrayWnd" и посылает в него сообщение WM_COPYDATA. в качестве данных выступает простая структура TNIDMessage. возвращаясь к топику: если создать свое окно с классом "Shell_TrayWnd" и обрабатывать входящие сообщения WM_COPYDATA, то можно написать полный аналог system tray! ... (с) paul_shmakov ...чем и займемся.
В первую очередь немаловажное замечание: сообщение посылается только одному окну, то есть наше приложение должно грузится первым. Разные там explorer'ы и другие подобные будут мешать. Шаг первый:
Создаем окно "Shell_TrayWnd"
| procedure TForm1.CreateParams(var Params: TCreateParams); begin inherited CreateParams(Params); Params.WinClassName := 'Shell_TrayWnd'; end; |
Все. Окно класса "Shell_TrayWnd" имеем. Шаг второй:
Ловим WM_COPYDATA
| procedure TFORM1.WMCOPYDATA(var Msg: Tmessage); var pcd: PCopyDataStruct; NID: PNotifyIconData; begin pcd := PCOPYDATASTRUCT(msg.lParam); if pcd^.dwData = 1 then begin NID := pointer(integer(pcd.lpData)+8); case integer(pointer(integer(pcd.lpData)+4)^) of NIM_ADD: Msg.Result := NewTrayIcon(NID); // добавить иконку NIM_DELETE: Msg.Result := DeleteTrayIcon(NID); // удалить иконку NIM_MODIFY: Msg.Result := ModifyTrayIcon(NID); // изменить иконку (или подсказку) end; exit; end; end; |
Обратите внимание на Msg.Result. Желательно чтобы NewTrayIcon, DeleteTrayIcon, ModifyTrayIcon возвращали Integer(True) или Integer(False) в зависимости от помещения/удаления иконки. Некоторые приложения не проверяют этот результат, но если начнут проверять - то причины "глючного" поведения иконки того же AVP Monitor можно искать долго и безуспешно. Шаг третий:
Поймали, и че с ним теперь делать? А мы имеем очень интересную структуру - NID.cbSize - размер записи, в принципе не интересен; NID.Wnd - хендл окна (владельца иконки); NID.uID - идентификатор иконки (если их в приложении несколько), для данной задачи нужен для отсылки обратного сообщения; NID.uFlags - определяет, какие поля используются в сообщении. Параметр может быть любой комбинацией из флагов (0 - uCallbackMessage, 2 - hIcon, 4 - czTip); NID.uCallbackMessage - номер сообщения, которое посылается окну, определяемому полем NID.Wnd (владельцу). lParam отсылаемого сообщения дожен равняться NID.uID, а wParam сообщение от мыши. Пример: PostMessage(NID.Wnd, NID.uCallBack, NID.uID, MOUSE_EVENT) где MOUSE_EVENT может принимать значения WM_LBUTTONDOWN, WM_LBUTTONUP, WM_LBUTTONDBLCLK и подобные для других кнопок мыши. NID.hIcon - хендл иконки, которую собственно и предполагается отображать; NID.szTip - строка, оканчивающаяся нулевым символом, содержит подсказку, которая должна выводится при наведении курсора на иконку.
В случаях ошибки нужно информировать приложения про необходимость поместить иконки обратно. Для этого послужат такие действия:
| procedure TForm1.FormCreate(Sender: TObject); var WM_TASKBARCREATED: UINT; begin WM_TASKBARCREATED := RegisterWindowMessage('TaskbarCreated'); PostMessage(HWND_BROADCAST, WM_TASKBARCREATED, 0, 0); end; |
Не все приложения реагируют на такое сообщение.
Скачать демо проект (9.8K)
И несколько слов о демо проекте.
ВНИМАНИЕ!!! Следуйте данным инструкциям только в том случае, если Вы ясно понимаете смысл действий!!!
Повторюсь: Shell_NotifyIconW сообщение посылает только одному окну. Поэтому чтобы увидеть результаты работы демопроекта, загружать его надо без или вместо explorer'а.
Первый вариант (для Win9x): Пример: файл %windir%\system.ini изменить следующим образом:
Найти строчку: shell=explorer.exe Заменить на (предполагается что демопроект находится в C:\Demotray\ ) : ;shell=explorer.exe shell=c:\demotray\demotray.exe Перегрузите Windows Для возврата explorer'a раскомментируйте первую строчку, закомментируйте или удалите вторую.
Второй вариант: Лично я использую Far для выгрузки Explorer.exe
1) Загружаем IDE Delphi и демопроект 2) Загружаем Far, F11->Process list-> выбираем EXPLORER.EXE->F8->OK 3) Отлаживаем проект 4) Для появления Explorer'a просто запустите его.
Используйте эти инструкции на свой страх и риск. Прочитайте их дважды. Внимательно изучите исходники. Трижды.
Инструкции по закрытию EXPLORER.EXE действительны для Win9x.
Если у Вас NT - разберитесь сами. Если не сможете разобратся - то за такие проекты Вам браться рановато.
Гревные ругательства "А у меня после ... ничего не работает!" не принимаются.
Благодарности: Paul Shmakov - реверсинг Shell_NotifyIcon, моральная поддержка. Стив Тейксейра & Ксавье Пачеко - литература. Особая благодарность обоим использованым алфавитам.
Богдан Минич
Смотрите также: Добавить "иконку" приложения в область SysTray.
Как создать свое окно предварительного просмотра QuickReport отчетов?
Внятного и простого ответа куцая документация на компонент TQRPreview не дает. И, хотя ответ на этот вопрос действительно очень прост, мне все же пришлось потратить некоторое время на его поиски, результаты которых я и привожу.
Создайте пустую форму и поместите на нее объекты TQRPreview, TToolBar и TImageList. Свойство Align QRPreview установите в alClient. Добавьте на компонент TToolBar необходимые вам кнопки. В TImageList с помощью ImageList Editor поместите иконки для кнопок ToolBar`а (я использовал иконки от стандартного QuickReport окна предпросмотра выдранные в помощью Resource Explorer) и в свойство ToolBar.Images поместите имя созданного компонента TImageList.
Затем переходим к написанию обработчиков нажатий на кнопки. Основные используемые свойства : QRPreview.PageNumber - номер текущей просматриваемой страницы, QRPreview.Zoom - масштаб просмотра отчета и QRPreview.QRPrinter - объект принтера. Ниже показаны назначения кнопок и обработчики для них : * Печать отчета QRPreview.QRPrinter.Print; * Настройка принтера QRPreview.QRPrinter.PrintSetup; * Переход к первой странице QRPreview.PageNumber:=1; * Переход к предыдущей странице QRPreview.PageNumber:=QRPreview.PageNumber-1; * Переход к следующей странице QRPreview.PageNumber:=QRPreview.PageNumber+1; * Переход к последней странице QRPreview.PageNumber:=QRPreview.QRPrinter.PageCount; * Масштабирование отчета на 100% QRPreview.Zoom:=100; * Масштабирование отчета по ширине страницы QRPreview.ZoomToWidth; * Масштабирование отчета по целой странице QRPreview.ZoomToFit; Процедуры сохранения, загрузки отчета и подобные можно найти в исходниках QuickReport`а в файле qrprev.pas (почти все вышеперечисленные обработчики также взяты из этого файла).
Последнее что требуется сделать - написать такой обработчик события OnPreview просматриваемого отчета : frmPreview.QRPreview.QRPrinter:=TQRPrinter(Sender); //frmPreview - форма frmPreview.Show; // окна предпросмотра Видно, что окно предпросмотра не содержит ссылок на конкретный компонент TQuickRep и очевидно что его можно использовать в различных отчетах и программах. Разумеется всплывающие подсказки на кнопках и прочие красивости вставляются по вкусу.
В обработчик закрытия окна нужно записать следующие строки : QRPreview.QRPrinter.ShowingPreview:=false; QRPreview.QRPrinter.Free;
PS. Замечания и исправления принимаются по адресу shadowj@yandex.ru
Алексей Трунтов Специально для
Как все это работает:
Компилятор он и есть компилятор. Сначала выражение надо скомпилировать. Делается это с помощью функции function Prepare(Ex:String):real; которая вызывает function preCalc(Ex:String):real;формирующую код, вычисляющий заданное выражение. Как можно догадаться, Ex - это строка, содержащая математическое выражение. Функция preCalc рекурсивна и распознавая полученную математику, попутно формируя исполняемый код. Она имеет мало проверок на корректность и нет нужды вводить туда мусор и радоваться, когда увидите что все повисло. Помните правило GIGO (Garbage in Garbage Out). Не надо также ставить 0 под знак деления. Но это уже не моя ошибка :)))
ВНИМАНИЕ: ограничение на глубина рекурсии: полученый код не должен помещать в стек более 8 значений.Снятие этого ограничения опять же лишь вопрос практической реализации. Для понятности формируемый код представляется в ближайшем Memo. Функция возвращает: а фиг его знает что она возвращает :) лучше не обращайте внимания :) Скомпилировали? Теперь можно и запускать: При компиляции мы сформировали процедуру с красноречивым названием: proc:TProc; где type TProc=procedure; пример запуска можно найти в procedure TForm1.BitBtn1Click(Sender: TObject); Также встречаются процедуры и функции: function SecindBracket(Ex:String;first:integer):Integer; вот уж и не помню, отчего появилось такое красивое название (скорее всего от очепятки), но все это призвано обработать скобки в выражении , procedure TForm1.BitBtn1Click(Sender: TObject); // Вычисляйзапускает вычисление, а также procedure TForm1.Button2Click(Sender: TObject); //Speed testдля того чтобы посмотреть какой за быстрый получился код.
К сему прилагается слегка комментированный исходный код. Вряд ли кому нужны комментарии типа: I:=0; // обнуляем счетчик а по структуре программы там комментариев хватает. Ну вот и все... Буду рад если вам это пригодиться. Если какие пожелания - пишите. Конструктивная критика - пишите. Неконструктивная критика - тоже пишите - у меня файлы удаляются без помещения в корзину.
Как выставить приоритет любому процессу
В качестве параметров необходимо передать _имя процесса_ (то, которое в диспетчере задач) и приоритет. Не забудьте также подключить модуль TLHelp32.
| procedure SetPriority(Name: String; Priority: Integer); var Handler: THandle; Data: TProcessEntry32; Finded: boolean; Res: boolean; ProcessID : DWORD; ProcessHandle : THandle; ThreadHandle : THandle; function ReturnName: String; var I : byte; Names: string; begin names:=''; i:=0; while data.szExeFile[i] <> '' do begin names:=names+data.szExeFile[i]; inc(i); end; ReturnName:=names; end; procedure TryIt; begin if AnsiUpperCase(ReturnName)=AnsiUpperCase(Name) then begin ProcessHandle := OpenProcess(PROCESS_SET_INFORMATION, true, data.th32ProcessID); Finded:=true; if ProcessHandle=0 then begin RaiseLastWin32Error; exit; end; case Priority of 0: Res:=true; 1: Res:=SetPriorityClass(ProcessHandle, IDLE_PRIORITY_CLASS); 2: Res:=SetPriorityClass(ProcessHandle, NORMAL_PRIORITY_CLASS); 3: Res:=SetPriorityClass(ProcessHandle, HIGH_PRIORITY_CLASS); 4: Res:=SetPriorityClass(ProcessHandle, REALTIME_PRIORITY_CLASS); end; if not Res then RaiseLastWin32Error; end; end; begin Finded:=false; Handler:= CreateToolHelp32SnapShot(TH32CS_SNAPALL,0); if process32first(handler,data) then begin TryIt; while process32next(handler,data) do TryIt end; if not Finded then ShowMessage('Cannot find'); end; |
Как заставить работать ActionList в формах, импортируемых из DLL
При использовании форм, импортируемых из DLL, вы столкнетесь с проблемой что ActionList работать не будет до тех пор, пока вы не активизируете его обновление самостоятельно. А следовательно, обновление всех компонентов управления работать не будет.
Класс для чтения/записи потока с преобразованием информации "на лету".
й Перовский, дата публикации 18 ноября 2003г. |
Мне пришлось столкнуться с проблемой записи информации в шифрованный файл. Причем в прототипе использовалась запись иерархии объектов в FileStream при помощи функций Load и Store. Чтобы не переделывать объекты и методы сохранения/восстановления, я создал класс TСryptoStream наследник TStream, предназначенный для блочного шифрования информации. Понятно, что сам метод шифрования может быть произвольным. Более того, это может быть совсем не шифрование, а, например, перекодировка, подсчет статистики и т.д. Работа с TCryptoStream аналогична работе с TFileStream - при создании объекта указывается режим записи или чтения. Для преобразования блоков используется внешняя функция. | const csmOpenRead =0; {либо чтение, либо запись} csmOpenWrite =1; {только последовательный доступ} type Tcsm = csmOpenRead .. csmOpenWrite;{csm- CryptoStreamMode - режим работы} {Функция блочного преобразования должна быть этого типа} TTransform=procedure ( Buffer:pointer; Count: Longint); {Описание класса TCryptoStream} TCryptoStream=class (TStream) private fMode:Tcsm; {либо чтение, либо запись} FS:TStream; {обрабатываемый поток} FTransform:TTransform;{Функция блочного преобразования} Data:pointer; {Указатель на блок для преобразования} L:integer; {Размер блока преобразования} Ost:integer; {Текущее свободное место в блоке} public constructor Create(S:TStream;al:integer; aTransform: TTransform;Mode: Tcsm); destructor destroy;override; function Read(var Buffer; Count: Longint): Longint;override; function Write(const Buffer; Count: Longint): Longint; override; end; | В прилагаемых файлах, кроме собственно модуля uCryptoStream, содержится проект, демонстрирующий использование TСryptoStream для шифрования потока простейшим методом (XOR с заданным значением). Скачать (4.5K)
Класс для реализации списка Variant'ов на основе TCollection
Класс реализует коллекцию элементов типа Variant, которые могут интерпретироваться как Integer, String или Currency. Динамический список этих элементов может быть именованным, где каждому элементу присваивается имя. Это условие по умолчанию не обрабатывается, так что с этим классом можно работать просто как с динамическим списком величин типа Variant. Довольно удобно. Можно искать в списке по значению (IndexOF), по имени (GetValueFromName), удалять из списка. Функция JoinList возвращает строку из символьного представления всех элементов списка разделенных заданным сепаратором.
Скачать файл (2K) unit ListUtils; interface Uses Classes , SysUtils; Type TListsItem = class(TCollectionItem) Private FValue : Variant; FName : String; Protected Function GetAsInteger : LongInt; Procedure SetAsInteger(AValue : LongInt ); Function GetAsString : String; Procedure SetAsString(AValue : String ); Function GetAsCurrency : Currency; Procedure SetAsCurrency(AValue : Currency ); Public procedure AssignTo( Dest: TPersistent ); override; property Value : Variant read FValue write FValue; property Name : String read FName write FName; property AsInteger : LongInt read GetAsInteger write SetAsInteger; property AsString : String read GetAsString write SetAsString; property AsCurrency : Currency read GetAsCurrency write SetAsCurrency; End; TCollectionListItemClass = class (TListsItem); TLists = class (TCollection) private function GetListItem(Index : Integer) : TListsItem; Public Constructor Create(ItemClass: TCollectionItemClass); Function AddItem( Value : Variant; AName : String ='' ) : TListsItem; Procedure FillFromArray(ArValue : array of Variant); Procedure FillFromNamedArray(ArValue , ArName : array of Variant ); Function IndexOf( Value : Variant ) : Integer; Function JoinList( Separator : String = ',') : String; Function GetFromName(AName : String ) : TListsItem; Function GetValueFromName(AName : String; DefaultValue : Variant ) : Variant; Procedure DeleteFromValue( Value : Variant; All : Boolean = FALSE); Procedure DeleteFromName(AName : String ); Property AnItems[Index : Integer] : TListsItem read GetListItem; default; End; implementation //---------------------------------------------------------------------------------------- // TLists //---------------------------------------------------------------------------------------- Constructor TLists.Create(ItemClass: TCollectionItemClass); Begin Inherited Create(ItemClass); End; //---------------------------------------------------------------------------------------- function TLists.GetListItem(Index : Integer) : TListsItem; Begin Result:=TListsItem(Items[Index]); End; //---------------------------------------------------------------------------------------- function TLists.AddItem(Value : Variant; AName : String = '') : TListsItem; Begin Result:=TListsItem(Self.Add); Result.FValue:=Value; Result.FName:=AName; End; //---------------------------------------------------------------------------------------- function TLists.IndexOf(Value : Variant): Integer; begin Result := 0; while (Result < Count) and ( AnItems[Result].Value <> Value) do Inc(Result); IF Result = Count then Result := -1; end; //---------------------------------------------------------------------------------------- Function TLists.JoinList( Separator : String = ',') : String; Var i : Integer; Begin Result:=''; IF Count > 0 Then Begin For i:=0 To Count-1 Do Result:= Result + AnItems[i].AsString + Separator; Result:=Copy(Result , 1 , Length(Result)-1 ); End; End; //---------------------------------------------------------------------------------------- Procedure TLists.DeleteFromValue( Value : Variant; All : Boolean = FALSE); Var i : Integer; Begin i:=IndexOf(Value); IF i >= 0 Then Delete(i); End; //---------------------------------------------------------------------------------------- Procedure TLists.DeleteFromName(AName : String ); Var i : Integer; AItem : TListsItem; Begin AItem:=GetFromName(AName); IF AItem <> nil Then Delete(AItem.Index); End; //---------------------------------------------------------------------------------------- Function TLists.GetFromName(AName : String ) : TListsItem; Var i : Integer; Begin Result:=nil; For i:=0 To Count-1 Do IF CompareText(AnItems[i].FName , AName) = 0 Then Begin Result:=AnItems[i]; Exit; End; End; //---------------------------------------------------------------------------------------- Function TLists.GetValueFromName(AName : String; DefaultValue : Variant ) : Variant; Begin Result:=DefaultValue; IF GetFromName(AName) <> nil Then Result:= GetFromName(AName).Value; End; //---------------------------------------------------------------------------------------- Procedure TLists.FillFromArray(ArValue : array of Variant); Var i : Integer; Begin Clear; For i:=Low(ArValue) TO High(ArValue) Do AddItem(ArValue[i]); End; //---------------------------------------------------------------------------------------- Procedure TLists.FillFromNamedArray(ArValue , ArName : array of Variant ); Var i , No : Integer; Begin FillFromArray(ArValue); No:=High(ArName); IF No > High(ArValue) Then No:=High(ArValue); For i:=Low(ArName) TO No Do AnItems[i].FName:=ArName[i] ; End; //---------------------------------------------------------------------------------------- //**************************************************************************************** //---------------------------------------------------------------------------------------- // TListItem //---------------------------------------------------------------------------------------- procedure TListsItem.AssignTo( Dest: TPersistent ); Begin IF Dest Is TListsItem Then Begin TListsItem(Dest).FValue:=FValue; TListsItem(Dest).FName:=FName; End Else inherited; End; //---------------------------------------------------------------------------------------- Function TListsItem.GetAsInteger : LongInt; Begin if TVarData(FValue).VType <> varNull then Result := TVarData(FValue).vInteger else Result := 0; End; //---------------------------------------------------------------------------------------- Procedure TListsItem.SetAsInteger(AValue : LongInt ); Begin FValue:=AValue; End; //---------------------------------------------------------------------------------------- Function TListsItem.GetAsString : String; Begin Result:=VarToStr(FValue); End; //---------------------------------------------------------------------------------------- Procedure TListsItem.SetAsString(AValue : String ); Begin FValue:=AValue; End; //---------------------------------------------------------------------------------------- Function TListsItem.GetAsCurrency : Currency; Begin if TVarData(FValue).VType <> varNull then Result := TVarData(FValue).vCurrency else Result := 0; End; //---------------------------------------------------------------------------------------- Procedure TListsItem.SetAsCurrency(AValue : Currency ); Begin FValue:=AValue; End; //---------------------------------------------------------------------------------------- end.
Класс TRySharedSream.
unit RySharedStream; interface uses SysUtils, Windows, Classes, RySharedMem; {$IFDEF VER120} {$DEFINE D5} {$ENDIF} {$IFDEF VER130} {$DEFINE D5} {$ENDIF} {$IFDEF VER140} {$DEFINE D6} {$ENDIF} type { TRyPageList } TRyPageList = class(TList) protected function Get(Index: Integer): TRySharedMem; procedure (Index: Integer; Item: TRySharedMem); public property Items[Index: Integer]: TRySharedMem read Get write Put; default; end; { TRySharedStream } TRySharedStream = class(TStream) { Для совместимости с TStream } private FSize : Longint; { Реальный размер записанных данных } FPosition : Longint; FPages : TRyPageList; protected function NewPage: TRySharedMem; public constructor Create; destructor Destroy; override; function Read(var Buffer; Count: Longint): Longint; override; function Write(const Buffer; Count: Longint): Longint; override; function Seek(Offset: Longint; Origin: Word): Longint; override; procedure (NewSize: Longint); override; procedure (Stream: TStream); procedure (const FileName: string); procedure (Stream: TStream); procedure (const FileName: string); public end; implementation uses RyActiveX; {resourcestring CouldNotMapViewOfFile = 'Could not map view of file.';} { TRySharedStream } { * Класс TRySharedStream можно рассматривать как альтернативу временным файлам (т.е. как замену TFileStream). Преимущество : а. Данные никто не сможет просмотреть. б. Страницы, зарезервированные под данные, автомотически освобождаются после уничтожения создавшего ее TRySharedStream'а. * Класс TRySharedStream можно рассматривать как альтернативу TMemoryStream. Преимущество : а. Не надо опасаться нехватки памяти при большом объеме записываемых данных. [случай когда физически нехватает места на диске здесь не рассматривается]. Известные проблемы: На данный момент таких не выявлено. Но есть одно НО. Я не знаю как поведет себя TRySharedStream в результате нехватки места а. на диске б. в файле подкачки (т.е. в системе с ограниченным размером файла подкачки). } const PageSize = 1024000; { размер страницы } constructor TRySharedStream.Create; begin FPosition := 0; { Позиция "курсора" } FSize := 0; { Размер данных } FPages := TRyPageList.Create; FPages.Add(NewPage); end; destructor TRySharedStream.Destroy; begin with FPages do while Count > 0 do begin Items[Count - 1].Free; Delete(Count-1); end; FPages.Free; inherited; end; function TRySharedStream.NewPage: TRySharedMem; begin Result := TRySharedMem.Create(RyActiveX.GUIDToString(RyActiveX.GetGUID), 0, PageSize) { |} {Я знаю что можно не именовать страницу __|} {но оказалось не всегда Win98 правильно создает новую} {неименнованную страницу. а другого способа получения} {уникальной строки я не знаю. } {если у кого-то будут идеи по этому поводу - милости просим.} end; function TRySharedStream.Read(var Buffer; Count: Longint): Longint; var FPos, BPos, FPageNo: Integer; ACount, FCount : Longint; Buf: PChar; begin Buf := @Buffer; ACount := 0; if Count > 0 then begin FCount := FSize - FPosition; {максимальное кол-во, которое можно прочитать} if FCount > 0 then begin if FCount > Count then FCount := Count; {если нам нужно прочитать меньше чем можем} ACount := FCount; {запоминаем сколько надо} FPageNo := FPosition div PageSize; {т.к. у нас многостраничный stream, то находим с какой страницы начать читать} BPos := 0; FPos := FPosition - (PageSize * FPageNo); {с какой позиции на странице читаем} while FCount > 0 do begin if FCount > (PageSize - FPos) then Count := PageSize - FPos else Count := FCount; {определяем сколько можно прочитать со страницы} Move(PChar(FPages.Items[FPageNo].Memory)[FPos], Buf[BPos], Count); {считаваем инфо. в буффер} Inc(FPageNo); {переходим на следующую страницу} Dec(FCount, Count); Inc(BPos, Count); FPos := 0; end; Inc(FPosition, ACount); end end; Result := ACount; end; function TRySharedStream.Write(const Buffer; Count: Longint): Longint; var FPos, BPos, FPageNo: Integer; ASize, ACount, FCount : Longint; Buf: PChar; begin { Функция аналогичная TStream.Write(). Все пояснения по работе с ней см. в help'e. } Buf := @Buffer; if Count > 0 then begin ASize := FPosition + Count; {определяем сколько места нужно для данных} if FSize < ASize then Size := ASize; {если больше чем было, то увеличиваем размер стрима} FCount := Count; {запоминаем сколько надо записать} FPageNo := FPosition div PageSize; {определяем с какой страницы начинаем писать} BPos := 0; FPos := FPosition - (PageSize * FPageNo); {вычисляем позицию на странице} while FCount > 0 do {пока все не напишем ни куда не уходим} begin if FCount > (PageSize - FPos) then ACount := PageSize - FPos else ACount := FCount; Move(Buf[BPos], PChar(FPages.Items[FPageNo].Memory)[FPos], ACount); {пишем сколько влезает до конца страницы} Inc(FPageNo); {переходим на следующую страницу} Dec(FCount, ACount); {уменьшаем кол-во незаписанных на кол-во записанных} Inc(BPos, ACount); FPos := 0; end; FPosition := ASize; end; Result := Count; end; function TRySharedStream.Seek(Offset: Longint; Origin: Word): Longint; begin { Функция аналогичная TStream.Seek(). Все пояснения по работе с ней см. в help'e. } case Origin of soFromBeginning : FPosition := Offset; soFromCurrent : Inc(FPosition, Offset); soFromEnd : FPosition := FSize - Offset; end; if FPosition > FSize then FPosition := FSize else if FPosition < 0 then FPosition := 0; Result := FPosition; end; procedure TRySharedStream.SetSize(NewSize: Longint); var Sz: Longint; begin { Функция аналогичная TStream.SetSize(). Все пояснения по работе с ней см. в help'e. } inherited SetSize(NewSize); if NewSize > (PageSize * FPages.Count) then { Если размер необходимый для записи данных больше размера выделенного под наш stream, то мы должны увеличить размер stream'a} begin { ...но FileMapping не поддерживает изменения размеров "страницы", что не очень удобно, поэтому приходится выкручиваться. } Sz := NewSize div (PageSize * FPages.Count); { думаем сколько нужно досоздать страниц под данные } while Sz > 0 do {создаем страницы} begin FPages.Add(NewPage); Dec(Sz); end; end; FSize := NewSize; { Запоминаем размер данных } if FPosition > FSize then FPosition := FSize; end; procedure TRySharedStream.LoadFromFile(const FileName: string); var Stream: TFileStream; begin Stream := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite); try LoadFromStream(Stream) finally Stream.Free end end; procedure TRySharedStream.LoadFromStream(Stream: TStream); begin CopyFrom(Stream, 0); end; procedure TRySharedStream.SaveToFile(const FileName: string); var Stream: TFileStream; begin Stream := TFileStream.Create(FileName, fmCreate); try SaveToStream(Stream) finally Stream.Free end end; procedure TRySharedStream.SaveToStream(Stream: TStream); begin Stream.CopyFrom(Self, 0); end; { TRyPageList } function TRyPageList.Get(Index: Integer): TRySharedMem; begin Result := TRySharedMem(inherited Get(Index)) end; procedure TRyPageList.Put(Index: Integer; Item: TRySharedMem); begin inherited Put(Index, Item) end; end.
Алексей Румянцев Специально для Прилагается демонстрационный пример использования TRySharedStream : (13 K)
Класс TRyTimer.
Описание: Обертка для стандартного Windows'таймера. Аналог TTimer. Отличия от TTimer'а: не тянет за сабой TComponent, uses Forms, Application
Написано на Delphi5. Тестировалось на Win98. WinXP. В случае обнаружения ошибки или несовместимости с другими версиями Delphi и Windows, просьба сообщить автору.
Скачать Демо_Архив (4 K) 22.04.02
Алексей Румянцев . Специально для
Класс TSharedSream.
unit SharedStream; interface uses SysUtils, Windows, Classes, Consts; type { TSharedStream } TSharedStream = class(TStream) { Для совместимости с TStream } private FMemory : Pointer; { Указатель на данные } FSize : Longint; { Реальный размер записанных данных } FPageSize : Longint; { Размер выделенной "страницы" под данные } FPosition : Longint; { Текущая позиция "курсора" на "странице" } protected public constructor Create; destructor Destroy; override; function Read(var Buffer; Count: Longint): Longint; override; function Write(const Buffer; Count: Integer): Longint; override; function Seek(Offset: Longint; Origin: Word): Longint; override; procedure SetSize(NewSize: Longint); override; procedure LoadFromStream(Stream: TStream); procedure LoadFromFile(const FileName: string); procedure SaveToStream(Stream: TStream); procedure SaveToFile(const FileName: string); public property Memory: Pointer read FMemory; end; const SwapHandle = $FFFFFFFF; { Handle файла подкачки } implementation resourcestring CouldNotMapViewOfFile = 'Could not map view of file.'; { TSharedStream } { * TSharedStream работает правильно только с файлом подкачки, с обычным файлом проще и надежнее работать TFileStream'ом. * Для тех кто знаком с File Mapping Functions'ами : Класс TSharedStream не может использоваться для синхронизации(разделения) данных среди различных процессов(программ/приложений). [пояснения в конструкторе] * Класс TSharedStream можно рассматривать как альтернативу временным файлам (т.е. как замену TFileStream). Преимущество : а. Данные никто не сможет просмотреть. б. Страница, зарезервированная под данные, автомотически освобождается после уничтожения создавшего ее TSharedStream'а. * Класс TSharedStream можно рассматривать как альтернативу TMemoryStream. Преимущество : а. Не надо опасаться нехватки памяти при большом объеме записываемых данных. [случай когда физически нехватает места на диске здесь не рассматривается]. Известные проблемы: На данный момент таких не выявлено. Но есть одно НО. Я не знаю как поведет себя TSharedStream в результате нехватки места а. на диске б. в файле подкачки (т.е. в системе с ограниченным размером файла подкачки). } constructor TSharedStream.Create; const Sz = 1024000; { Первоначальный размер страницы }{ взят с потолка } var SHandle : THandle; begin FPosition := 0; { Позиция "курсора" } FSize := 0; { Размер данных } FPageSize := Sz; { Выделенная область под данные } { Создаем дескриптор объекта отображения данных. //эта формулировка взята из книги Проще сказать - создаем страницу под данные. //разрешите, я здесь и далее //буду употреблять более протые //информационные вставки. Все подробности по CreateFileMapping в Help'e. } SHandle := CreateFileMapping( SwapHandle, nil, PAGE_READWRITE, 0, Sz, nil ); { Создаем "страницу"___| | | |} { Handle файла подкачки ______| | |} { Задаем размер "страницы"[Sz]. Не может быть = нулю______________| |} { Имя "страницы" должно быть нулевым[nil]_____________________________| иначе Вам в последствии не удастся изменить размер "страницы". (Подробнее см. в TSharedStream.SetSize). * Для тех кто знаком с File Mapping Functions'ами : раз страница осталась неименованной, то Вам не удастся использовать ее для синхронизации(разделения) данных среди различных процессов(программ/приложений). [остальных недолжно волновать это отступление] } if SHandle = 0 then raise Exception.Create(CouldNotMapViewOfFile); { ошибка - неудалось создать объект отображения[т.е. "страница" не создана и указатель на нее = 0]. Это может быть: Если Вы что-либо изменяли в конструкторе - a. Из-за ошибки в параметрах, передоваемых функции CreateFileMapping б. Если Sz FMemory := MapViewOfFile(SHandle, FILE_MAP_WRITE, 0, 0, Sz); { Получаем указатель на данные } if FMemory = nil then raise Exception.Create(CouldNotMapViewOfFile); { Виндус наверно может взбрыкнуться и вернуть nil, но я таких ситуаций не встречал. естественно если на предыдущих дейсвиях не возникало ошибок и если переданы корректные параметры для функции MapViewOfFile() } CloseHandle(SHandle); end; destructor TSharedStream.Destroy; begin UnmapViewOfFile(FMemory); { закрываем страницу. если у Вас не фиксированный размер файла подкачки, то через пару минут вы должны увидеть уменьшение его размера. } inherited Destroy; end; function TSharedStream.Read(var Buffer; Count: Longint): Longint; begin { Функция аналогичная TStream.Read(). Все пояснения по работе с ней см. в help'e. } if Count > 0 then begin Result := FSize - FPosition; if Result > 0 then begin if Result > Count then Result := Count; Move((PChar(FMemory) + FPosition)^, Buffer, Result); Inc(FPosition, Result); end end else Result := 0 end; function TSharedStream.Write(const Buffer; Count: Integer): Longint; var I : Integer; begin { Функция аналогичная TStream.Write(). Все пояснения по работе с ней см. в help'e. } if Count > 0 then begin I := FPosition + Count; if FSize < I then Size := I; System.Move(Buffer, (PChar(FMemory) + FPosition)^, Count); FPosition := I; Result := Count; end else Result := 0 end; function TSharedStream.Seek(Offset: Integer; Origin: Word): Longint; begin { Функция аналогичная TStream.Seek(). Все пояснения по работе с ней см. в help'e. } case Origin of soFromBeginning : FPosition := Offset; soFromCurrent : Inc(FPosition, Offset); soFromEnd : FPosition := FSize - Offset; end; if FPosition > FSize then FPosition := FSize else if FPosition < 0 then FPosition := 0; Result := FPosition; end; procedure TSharedStream.SetSize(NewSize: Integer); const Sz = 1024000; var NewSz : Integer; SHandle : THandle; SMemory : Pointer; begin { Функция аналогичная TStream.SetSize(). Все пояснения по работе с ней см. в help'e. } inherited SetSize(NewSize); if NewSize > FPageSize then { Если размер необходимый для записи данных больше размера выделенного под "страницу", то мы должны увеличить размер "страницы", но... } begin { ...но FileMapping не поддерживает изменения размеров "страницы", что не очень удобно, поэтому приходится выкручиваться. } NewSz := NewSize + Sz; { задаем размер страницы + 1Meтр[чтобы уменьшить работу со страницами]. } { Создаем новую страницу }{ возможные ошибки создания страницы описаны в конструкторе TSharedStream. } SHandle := CreateFileMapping( SwapHandle, nil, PAGE_READWRITE, 0, NewSz, nil ); if SHandle = 0 then raise Exception.Create(CouldNotMapViewOfFile); SMemory := MapViewOfFile(SHandle, FILE_MAP_WRITE, 0, 0, NewSz); if SMemory = nil then raise Exception.Create(CouldNotMapViewOfFile); CloseHandle(SHandle); Move(FMemory^, SMemory^, FSize); { Перемещаем данные из старой "страницы" в новую } UnmapViewOfFile(FMemory); { Закрываем старую "страницу" } FMemory := SMemory; FPageSize := NewSz; { Запоминаем размер "страницы" } end; FSize := NewSize; { Запоминаем размер данных } if FPosition > FSize then FPosition := FSize; end; procedure TSharedStream.LoadFromFile(const FileName: string); var Stream: TFileStream; begin Stream := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite); try LoadFromStream(Stream) finally Stream.Free end end; procedure TSharedStream.LoadFromStream(Stream: TStream); var Count: Longint; begin Stream.Position := 0; Count := Stream.Size; SetSize(Count); if Count > 0 then Stream.Read(FMemory^, Count); end; procedure TSharedStream.SaveToFile(const FileName: string); var Stream: TFileStream; begin Stream := TFileStream.Create(FileName, fmCreate); try SaveToStream(Stream) finally Stream.Free end end; procedure TSharedStream.SaveToStream(Stream: TStream); begin Stream.Write(FMemory^, FSize); end; end.
Алексей Румянцев Специально для Прилагается демонстрационный пример использования TSharedStream : (6.2K)
Комментарий к статье по поводу wsprintf
Сама по себе статья вызывает мало интереса, кроме того, что поднята интересная проблема - вызов с-шной функции с переменным числом параметров. В ответах с использованием массивов вообще, IMHO, ошибка - на стек попадет адрес массива, а в с это совсем не то. Но решение проблемы существует, правда надо ручками повозиться со стеком. Приводимая ниже функция на скорую руку переделывается из работающей в реальном проекте похожего буфера с-паскаль, но там функция в dll имеет тип вызова cdecl и другие обязательные параметры, в связи с чем возможны "опечатки" /*------------------------------------------------------------------*/ // Пишем функцию-переходник, маскируя с-шные "..." паскалевским // array of const function sprintf(out, fmt: Pchar; args: array of const): Integer; var I: Integer; BufPtr: Pchar; S: String; buf: array[0..1024] of char; begin BufPtr := buf; // Формируем буффер параметров. Можно, конечно, и прямо на стеке, // но головной боли слишком много - проще так for I:=low(Par) to High(Par) do case Par[I].VType of vtInteger : // Здесь все просто - 4 байта на стек begin Integer(Pointer(BufPtr)^) := Par[I].VInteger; Inc(BufPtr,4); end; vtExtended: // Здесь хуже - слова надо местами поменять :-(( begin Integer(Pointer(BufPtr)^) := Integer(Pointer(Pchar(Par[I].VExtended)+4)^); Inc(BufPtr,4); Integer(Pointer(BufPtr)^) := Integer(Pointer(Par[I].VExtended)^); Inc(BufPtr,4); end; vtPChar : // Здесь тоже все хорошо - 4 байта begin Pointer(Pointer(BufPtr)^) := Par[I].VPchar; Inc(BufPtr,4); end; vtString, vtAnsiString : // А здесь во избежание чудес надо // копию строки снять begin if Par[I].VType = vtString then S := Par[I].VString^ else S:= string(Par[I].VAnsiString); Pointer(Pointer(BufPtr)^ := StrPCopy(StrAlloc(Length(S)+1), S); Inc(BufPtr,4); end; end; // Поддержку других типов доделывать самостоятельно, // вооружившись толковым пособием по с и ассемблеру I := (BufPtr - buf) div 4; // Сколько раз на стек слово положить asm push dword ptr [out] push dword ptr [fmt] mov ecx, dword ptr [i] mov eax, dword ptr [buf] // stdcall - параметры в прямом // порядке @@1: push dword ptr [eax] add eax, 4 loop @@1 call [wsprintf] mov dword ptr [Result], eax // Сохранить результат mov eax, dword ptr [i] // Привести в порядок стек shl eax, 2 add eax, 8 add esp, eax end; // Почистить строки for I:=low(Par) to High(Par) do case Par[I].VType of vtInteger : Inc(BufPtr,4); vtExtended: Inc(BufPtr,8); vtPChar : Inc(BufPtr,4); vtString, vtAnsiString : begin StrDispose(PChar(PPointer(BufPtr)^)); Inc(BufPtr,4); end; end; end; /*-----------------------------------------------------------------*/ В таком виде методика уже имеет смысл. Изменения при типах вызова cdecl / pascal понятны.
С уважением Владимир Переплетчик. Смотрите также :
Компилятор синтаксических выражений
| Раздел Сокровищница | й Втюрин aka Nemo, дата публикации 01 августа 2002г. |
Компонент для выгрузки набора данных в дерево
TDBSTreeView1.0 — компонент предназначен для выгрузки набора данных в дерево. Оганичение: Таблица должна иметь вид такой(то есть там должны присутствовать такие поля(названия не имеет значения)): 1 ID - integer(глобальный идатификатор) 2 IDPARENT - integer (ссылка на родительскую запись в этой таблице). Самые верхние узлы имеют в IDPARENT = 0.... Например, такая таблица IDNode IDparent Name 1 0 Первый узел 2 0 Второй узел 3 1 Первый ребенок первого узла 4 1 Второй ребенок первого узла итд Вот, в общем-то, и все ограничение... Наследовалось от TTreeView, поэтому имеет все его свойства. Добавлены свойства DataSource - наверное, не надо объяснять, зачем это надо :). DataField - значения данного поля будут отображаться в узлах дерева. IDNode - Название поля глобального идатификатора. IDParentNode - Название поля ссылки на родительскую запись в этой таблице. ViewField - Зарезервирована для дальнейшего развития. Да, собственно, для чего это делалось: ВАЖНО! Имеет пока одну только функцию LoadDBSTreeView(Root: string); - грузит дерево из НД. И самое главное при "прогулке" по дереву переводит НД на запись соответствующую узлу (просто именно из-за этой функции и писалось) Ах да ещё!...Никаких проверок на корректность присваивания DataSource пока нет, так как писалась для себя. За это, чур, не ругать.. А если найдете ошибки(не граматические естейственно:)..) отпишите плз... а то исправите у себя, а я буду потом десять лет это отлавливать ок?:)
Александров Дмитрий AlexDBases Скачать : (9,6 К)
| Для данного материала нет комментариев. |
Компонент градиентной раскраски областей
Как можно увидеть при внимательном рассмотрении, раскраска псевдообъемных фигур производится линейно. Для более правильного воспроизведения интенсивности цвета необходимо ввести тригонометрическую функцию преобразования интенсивности, но это сильно замедлит воспроизведение фигуры. Для ускорения воспроизведения фигур в RunTime можно добавить в компонент преобразование рисунка в Bitmap после окончания режима разработки и далее воспроизводить его как картинку.
Компонент реализует градиентную заливку для конуса, цилиндра и сферы. В Design-time настраиваются все основные свойства псевдообъемной фигуры: тип фигуры и ее размер, расположение (кроме сферы), цвет заливки, смещения вершины конуса для получения скошенных и усеченных конусов. Для смены точки освещения и скошенных цилиндров допишете сами, это не сложно.
Проект, демонстрирующий градиентную заливку : Исходные коды + exe (156 K) Исходные коды (6 K)
Компонент "Линия"
| Раздел Сокровищница | стa 2001 г. |
Компонент предназначен для вычерчивания линий на мнемосхемах и других целей, где количество ломаных линий, созданых одним компонентом, не должно превышать 255. Инструмент - Delphi 5.1.
Введение даже списка (TList), не говоря уже о коллекции, заметно замедляло отрисовку, поэтому был выбран статический массив записей линий.
Компонент позволяет изменять тощину, стиль и цвет как в режиме разработки, так и в динамике. Для редактирования используется стандартный редактор компонентов, запускаемый нажатием правой кнопкой мыши. Редактирование нужно начинать с первой команды выпадающего меню (Edit Lines), а заканчивать - со второй (Exit from Editing). Редактирование заключается в добавлении линий (Add Line) и узлов (Add node), и удалении их (Remove Line и Remove Node). Можно также менять цвет (Line Color) и стиль линии (LineStyle). Ввиду ограничений, накладываемых операционными системами Windows95 и 98, стили меняются только для линий с толщиной, равной 1. Для Windows NT и 2000 таких ограничений нет.
Для изменения координат узла нужно выбрать линию путем нажатия левой кнопки мыши над требуемым узлом или концом линии, и, удерживая ее, перетащить на нужное место. Выделенная линия обозначается черными квадратиками.
Для большего удобства в выпадающем меню редактора указывается общее количество созданых линий, номер выбранной линии и узлов в ней.
К сожалению, компонент имеет существенный недостаток - отсутствие блокировки манипулирования другими компонентами, находящимися на форме, до выхода из режима редактирования линий. убоко благодарен за любые советы по преодолению указанного недостатка.
убокую благодарность Сергею Губенко и Юрию Зотову за ценные советы по построению компонента. Компонент могут использовать все без всяких ограничений, но со ссылкой на автора.
Скачать исходные коды (7K)
Компонент MathParser
нов, дата публикации 21 января 2002г |
Компонент MathParser разбирает математические выражения и вычисляет их. Математическое выражение может состоять из чисел (целых и действительных), переменных (любая последовательность букв и цифр начинающаяся с буквы), действий арифметики (плюс, минус, умножить, разделить, возвести в степень ), функций (любая последовательность букв и цифр начинающаяся с буквы и заканчивающаяся круглыми скобками) и скобки для задания приоритетов. Переменные и функции чувствительны к регистру.
Свойства Expression - тип String, математическое выражение, которое нужно вычислить. Например x^2+sin(exp(x))-b+2 Variables - тип TStrings, представляет набор переменных и их значений, разделенных знаком =. Например x=2 b=2 Методы Execute - возвращает значение выражения, при данных значениях переменных. Возвращаемое значение имеет тип Real. Исключения EUntrueSequence - недопустима последовательность символов, например x(3); EUnknownSymbol - недопустимый символ, например @; EUndeclaredIdentifier- неизвестный идентификатор; EUnknownFunction - неизвестная функция; Допустимые символы: + - плюс; - - минус; * - умножение; / - дделение; ^ - возведение в степень; ( ) -скобки; 1..9 - числа; . или ,- разделитель дробной части; Функции sin - синус; cos-косинус; tan-тангенс; exp-экспонента; ln - логарифм натуральный; sqrt - корень; arctan - арктангенс; Скачать исходные коды: (6К)
Компонент NXDBGrid, позволяющий отображать Dataset в транспонированном виде (столбцы в строках).
Создание копонента было вызвано тем, что пришлось отображать объекты со множеством свойств, либо константных, либо изменяемых одновременно. Стандартный ValueEditor не подходил по нескольким причинам: невозможно отобразить сразу несколько колонок со значениями. неизвестно, насколько хорошо будет передаваться между СОМ-объектами структуры, используемые для хранения значений. нет контроля типов дополнительная морока при состыковке с таблицами БД В конце концов выбор пал на TDBGrid. За основу мы взяли TCustomDBGrid и TCustomGrid. Класс отнаследован от TCustomGrid, методы TCustomDBGrid были вставлены простым копированием и дополнены функциональностью.
NXDBGrid дополнен возможностью редактирования Даты\времени (т.к. мы отнаследователись от TCustomGrid то мы не смогли вставить стандартный редактор от MS как Inplace, поэтому пришлось написать самим ;-) ) Не смогли добавить дефолтный редактор компонента для DBGrid (по двойному щелчку мыши). Не смогли реализовать добавление редакторов и валидаторов (Хотели использовать паттерн State (состояние) при изменении текущего столбца), но то ли мы не со всем разобрались, то ли ребята из Борланда замутили ;-)(у некоторых функций забыли поставить Virtual, некоторые объявлены в приватной области). Добавлены проверки типов данных при редактировании. Скриншоты :
Использование компонента состоит в изменении свойства Transformed Технология использования компонента у нас простая. Создаем виртуальный рекордсет, запихиваем в него данные и выполняем присвоение ADODataset.Recordset = наш рекордсет
Скачать компонент (93K) и и основных моментов - Александр Ткаченко, реализация - Денис Полеонов материал предоставлен специально для
Компонент, позволяющий отображать формулы
| Раздел Сокровищница | в А.В., дата публикации 18 октября 2002г. | Предлагаю Вашему вниманию компонент позволяющий отображать формулы (наследник от TCustomLabel). Возможности - вывод греческих символов - вывод специальных математических символов (в пределах фонта Symbol) - использование верхних и нижних индексов, но не одновременно. - выравнивание выводимой формулы по вертикали и горизонтали - смена начертания внутри формулы
Для задания формулы используется текст свойства Caption. Формула описывается в текстовом режиме. Зарезервированные символы , '\', '^','_', '}','{'.
Для вывода зарезервированных символов необходимо использовать их совместно с символом \. Например для вывода пробела ипользуется \, правая фигурная скобка\}. Символы {} зарезервированы для дальнейшнго расширения. Символы греческого алфавита и спецсимволы: \Delta, \Downarrow, \Gamma, \Lambda, \LeftArrow, \Leftrightarrow, \Omega, \RightArrow, \Phi, \Pi, \Psi, \Sigma, \Theta, \Uparrow, \Upsilon, \Xi, \alpha, \angle, \approx, \beta, \bullet, \cap, \cdot, \chi, \cong, \delta, \diamond, \div, \downarrow, \epsilon, \equiv, \eta, \gamma, \ge, \gets, \in, \infinity, \iota, \kappa, \lambda, \le, \mu, \ne, \notin, \nu, \omega, \oplus, \oslash, \otimes, \partial, \perp, \phi, \pi, \pm, \psi, \rho, \sigma, \subset, \subseteq, \supset, \tau, \theta, \times, \to, \uncup, \uparrow, \upsilon, \varepsilon, \varphi, \varpi, \varsigma, \vee, \wedge, \xi, \zeta. Для задания верхних и нижних идексов используются символы ^ и _ соответственно. Для смены начертания символов \it -- италика (курсив) \bl -- bold (жирный) \ul -- underline (подчеркнутый) \st -- strike (перечеркнутый) \rm -- отмена смены начертания
Недостатки: невозможность использовать струтурные скобки {} работа только на одной базовой линии (нельзя использовать \frac) и т.д. Да и нельзя реализовать TeX в 20-30 строках кода. Примеры: S=\pi R^2 -- площадь круга С_2 H_5 OH -- OН и есть \Delta \phi = 0 уравнение Пуассона
Скачать (5 K)
Компонент SystemTray
| Раздел Сокровищница | рбань С.В., дата публикации 25 сентября 2002г. | Компонент отличается от всех найденных мной аналогов. В нем не реализована только анимация (ну не нужна она мне...), зато он (компонент) САМ взаимодействует с формой и приложением. Это значит, что вы кладете компонент на форму, настраиваете настраиваете его (иконка, хинт, события мыши, всплывающее меню), задаете видимость формы, видимость приложения на панели задач и видимость иконки в трее. Все. Запускаете приложение.
Если вы в DesignTime задали невидимую форму, невидимое приложение и видимый значек - ваше приложение появится ТОЛЬКО в трее. Если вы и значек спрятали - тогда вообще ничего не видно :-) Спрятались :-)) | | |
В архиве компонент и специальный пример — (236 K)
Компонент TADOUpdateSQL
| Раздел Сокровищница | ркуша Алексей, дата публикации 14 мая 2002г. |
Здесь представлены работающие компоненты обновления данных, полученных запросом через TADOQuery, аналогичные компонентам BDE TQuery,TUpdateSQL Компоненты TADOUpdateQuery, TADOUpdateSQL выполняют в точности те же функции что и компоненты BDE TQuery,TUpdateSQL. Это может способствовать быстрому переводу программ с BDE на ADO. Компоненты работающие (в исходных текстах есть комментарии), но до полной совместимости необходимы доработки, например: отсутствуют события onUpdateRecord, onUpdateError. Предлагаю всем подключится и довести дело до конца. type TADOUpdateQuery = class; TADOUpdateSQL = class; // Для правильной работы (логика) нежелательно изменять запрашиваемые поля TADOUpdateQuery = class (TADOQuery) private DelRecords: TADOQuery; FUpdateObject: TADOUpdateSQL; procedure SetUpdateObject(Value: TADOUpdateSQL); procedure ClearBuffer; // физическое удаление записей из буфера удаленных procedure InitBuffer; // создание датасета в которые помещаюися удаленные записи procedure FillBuffer; // перенос записи в буфер удаленных procedure ApplyDelUpdates; protected procedure InternalDelete; override; public constructor Create (AOwner: TComponent); override; destructor Destroy; override; procedure ApplyUpdates; // после успешного выполнения буфер удаленных записей будет пуст и необходим CommitUpdates // так как статусы "тронутых" записей не изменены //(пример вставка записи: будет столько сколько раз // был вызван ApplyUpdates. Неправильно это :-(, кто об этом знает procedure CancelUpdates; // сброс внутренних флагов ADO (вставленных, измененных) и сброс удаленных procedure CommitUpdates; // сброс внутренних флагов ADO (вставленных, измененных) published property UpdateObject: TADOUpdateSQL read FUpdateObject write SetUpdateObject; end; TADOUpdateSQL = class(TComponent) private FDataSet: TADOUpdateQuery; FQueries: array[TUpdateKind] of TADOQuery; FSQLText: array[TUpdateKind] of TStrings; function GetQuery(UpdateKind: TUpdateKind): TADOQuery; function GetSQLIndex(Index: Integer): TStrings; procedure SetSQL(UpdateKind: TUpdateKind; Value: TStrings); procedure SetSQLIndex(Index: Integer; Value: TStrings); protected function GetSQL(UpdateKind: TUpdateKind): TStrings; virtual; function GetDataSet: TADOUpdateQuery; virtual; procedure SetDataSet(ADataSet: TADOUpdateQuery); virtual; procedure SQLChanged(Sender: TObject); public constructor Create(AOwner: TComponent); override; destructor Destroy; override; procedure Apply(UpdateKind: TUpdateKind); virtual; // не изменяет статусов записей // при прямом вызове сначала SetParams, не изменяет статусов записей procedure ExecSQL(UpdateKind: TUpdateKind); procedure SetParams(UpdateKind: TUpdateKind); // заполнение параметров property DataSet: TADOUpdateQuery read GetDataSet write SetDataSet; property Query[UpdateKind: TUpdateKind]: TADOQuery read GetQuery; property SQL[UpdateKind: TUpdateKind]: TStrings read GetSQL write SetSQL; published property ModifySQL: TStrings index 0 read GetSQLIndex write SetSQLIndex; property InsertSQL: TStrings index 1 read GetSQLIndex write SetSQLIndex; property DeleteSQL: TStrings index 2 read GetSQLIndex write SetSQLIndex; end; Скачать (5.7 K)
Компонент TComboBox с пошаговым поиском в списке
Мне понадобился компонент TComboBox с пошаговым поиском в списке. Несколько модифицированный стандартный TComboBox компонент с возможностью инкрементального поиска нашел на . Для Borland C++ Builder. Переписал его на Delphi, может кому пригодится. Всю критику по коду приму по мылу. unit ComboBoxInc; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls; type TComboBoxInc = class(TComboBox) private FTagString:AnsiString; FIncSearch:boolean; Findex:longint; Findex_prev:longint; FText_prev:string; FSelStart_prev:longint; protected procedure KeyUp(var Key:word; Shift:TShiftState);override; procedure KeyDown(var Key:word; Shift:TShiftState);override; public constructor Create (Owner:TComponent);override; published property IncSearch:boolean read FIncSearch write FIncSearch default true; property TagString:AnsiString read FTagString write FTagString; end; procedure Register; implementation procedure Register; begin RegisterComponents('Samples', [TComboBoxInc]); end; procedure TComboBoxInc.KeyUp(var Key : word; Shift:TShiftState ); var start :integer; s:string; begin if (Key = 13) then begin start := 0; Findex_prev:=-1; Findex := SendMessage(Handle, CB_FINDSTRING, Findex-1, LongInt(PChar(Text))); FText_prev:=''; if (Findex <> -1) then SendMessage(Handle, CB_SETCURSEL, Findex, 0); SelStart := start; SelLength := GetTextLen()-start; Findex:=-1; inherited; end else begin if (FIncSearch) then begin if (key=8) then SelStart:=FSelStart_prev; start := SelStart; if (key <> 8) then Findex_prev:=Findex; Findex := SendMessage(Handle, CB_FINDSTRING, Findex-1,LongInt(PChar(Text))); FText_prev:=Text; if ((Findex <> -1)and not((Key = VK_DELETE))) then SendMessage(Handle, CB_SETCURSEL, Findex, 0); SelStart := start; SelLength := GetTextLen()-start; end; end; end; procedure TComboBoxInc.KeyDown(var Key : word; Shift:TShiftState ); begin if (Key=8) then begin SetLength(FText_prev,length(FText_prev)-1); Findex:=Findex_prev; Text:=FText_prev; end else FSelStart_prev:=SelStart; end; constructor TComboBoxInc.Create (Owner:TComponent); begin FIncSearch := true; FTagString := ''; inherited; end; end.
Компонент TOraCommentsToFL — загрузка русских наименований колонок из ERwin в DisplayLabels
| Раздел Сокровищница | й Седов , дата публикации 29 мая 2002г. |
Хочу поделиться идеей, которая, может и приходила уже кому-нибудь в голову, но я пока не встречал таких решений... Для тех, кто проектирует базы данных для Oracle в ERwin. Предлагаю решение для загрузки русских наименований колонок из ERwin в DisplayLabels датасета. Компонент, конечно, сырой, но я пользуюсь, и очень поиогает. Ненавижу вбивать один текст по 10 раз. Что нужно сделать: 1. Создать в ErWin скрипт на уровне модели следующего содержания: %ForEachTable() { %ForEachColumn() { COMMENT ON COLUMN %TableName.%ColName IS '%AttName'; }} 2. Сгенерировать базу. 3. В дата-модуле или на форме разместить компонент TOraCommentsToFL. 4.Установить его свойство OraDataSet. 5. Установить свойство LoadFieldLabels в True, после чего названия полей будут загружены из базы Oracle. 6. После этого компонент можно удалить с формы! Примечание: Требуются установленные компоненты ODAC.
Скачать (3K)
Компоненты для облегчения работы с типовыми операциями поиска, выбора и фильтрации данных
Набор компонент писался для облегчения работы с типовыми операциями поиска, выбора и фильтрации данных.
TAkLstDataпредназначена для вывода и выбора данных из набора данных в виде списка. Имеется возможность фильтрации этого списка. Множественный выбор. TAkTreeDataпредназначена для вывода и выбора данных из набора данных. Данные представлены в виде дерева. Существует возможность множественного выбора. TAkFilterDataпозволяет в диалоге задать условия отбора данных используемые в TQuery в разделе WHERE. TEditDataFormпозволяет получать данные (строка, дата, число) А-ля редактор свойств параметра ключа в реестре. TAkFindGridпозволяет искать данные в DBGrid. Компоненты оттестированы для Delphi 5 и необходимо наличие библиотеки RxLib. Имеется краткая помощь и демо.
Скачать компоненты: (107K)
Компоненты для работы с графикой.
| Раздел Сокровищница | Юрий , 16 апреля 2001г. |
Несколько слов о сути моего материала. Прежде всего, он предназначена для программистов Delphi, работающих с графикой. Представленные компоненты имеют такие возможности, как: работа с прозрачностью быстрая и гибкая замена необходимых цветов улучшенный компонент TDrawGrid работа с массивами картинок (в т.ч. быстрое восстановление картинки, после того как она была изменена, быстрое сохранение и загрузка картинок, различные процедуры для их обработки (поддерживаются два формата: bmp и jpg) некоторые полезные дополнения, например такие как движущийся текст на неоднородном фоне - полезно для таких случаев, как создание диалогового окна About
А теперь рассмотрим вышеперечисленные пункты немного подробнее. Но для начала, несколько слов о принципах работы этих компонентов. Прежде всего они построены на основе массива объектов типа TBitmap. В 32 битных изображениях цвет кодируется 3 байтами - по одному на синий, зеленый и голубой, плюс добавляется один байт на альфа канал. В этот альфа канал записывается информация о том, следует ли при вставке одной картинки в другую использовать соответствующий пиксель (или игнорировать его). То есть по идее он может принимать два значения: 0 и 1. В этих компонентах использован несколько иной принцип использования альфа канала. Туда записывается число, которое представляет собой коэффициент, используемый при расчете двух пикселей двух различных картинок. То есть, если, скажем альфа канал равен 127, то результирующий пиксель будет являться средним арифметическим двух других пикселей. Значение альфа канала, равное 0 соответствует полной непрозрачности, а равное равное 255 - полной прозрачности вставляемой картинки. Процедура SetAlphaChannel устанавливает заданное значение альфа канала для всех пикселей картинки.
Также в компонент TGraph добавлено несколько функция для замены цветов. Все эти процедуры работают только с 24 битными рисунками. procedure ChangeColor(ChangeColorProc: TChangeColorProc; Bitmap: TBitmap; Color: TColor); TChangeColorProc = procedure(var R, G, B: Byte); Наверное, самая простая процедура. Процедура TChangeColorProc вызывается каждый раз при замене очередного пикселя. procedure ChangeColorEx(Bitmap: TBitmap; OldColor, NewColor: TColor; ChangingType: TChangingType); Замена цветом NewColor происходит, когда значение заменяемого пикселя оказывается между значениями OldColor и NewColor если ChangingType = ctEqual, если ChangingType = ctNotEqual, то замена цвета происходит когда значение заменяемого пикселя оказывается вне этого диапазона. Во всех последующих процедурах этот параметр имеет тот же самый смысл, поэтому я не буду описывать его действие. procedure ChangeColorRange(Bitmap: TBitmap; IntensityRecLo, IntensityRecHi, NewIntensityRec: TIntensityRec; ChangingType: TChangingType); TIntensityType = (itRed, itGreen, itBlue); TIntensityRec = array[TIntensityType] of Byte; Как видно выше, тип TIntensityRec представляет из себя не что иное, как 24 битный пиксель. Действие этой процедуры аналогично процедуре ChangeColorEx, за исключением того, что эта немного более гибкая, что ли. procedure ChangeColorRange(Bitmap: TBitmap; IntensityType: TIntensityType; IntensityLo, IntensityHi: Byte; NewIntensityRec: TIntensityRec; ChangingType: TChangingType); Самая быстрая процедура этого типа. Производит замену пикселя по одной из его составляющий: синему, зеленому или красному цвету. Соответственно IntensityLo и IntensityHi это диапазон одного из составляющих цвета пикселей, по границам которого производится замена пикселем NewIntensityRec.
Вот, собственно все процедуры созданные для замены цвета. Вместе они представляют из себя довольно гибкое средство для работы с графикой. Еще одно достоинство - они работают достаточно быстро. Это касается также и других процедур, например на компьютере Pentium Celeron 400 расчет прозрачности двух картинок (обе были (конечно же) 32 битные, размером 1024 на 768) занимал на более 500 миллисекунд.
Следующее, что мы рассмотрим, это компонент TGraphGrid, созданный на основе компонента TDrawGrid. Этот компонент способен отображать картинки с текстом. Чтобы картинки отображались в компоненте в порядке из загрузки, следует установить свойство DefaultOrder в True. Вообще, это не лучший способ для отображения картинки. В компоненте TDrawGrid есть свойство: RefArray: TIntArray TIntArray = array of Integer; Оно представляет из себя массив ссылок. Поясню на примере. Пусть у на прорисовывается ячейка к координатами Col = 2, Row = 1, где в компоненте TDrawGrid 10 столбцов. В таком случае индекс ячейки (ее порядковый номер) будет равен 12 (нумерация столбцов и рядов начинается с нуля, с нуля также начинается нумерация индекса ячейки). При этом RefArray[12] = 1. В таком случае в ячейке Col = 2, Row = 1 будет прорисовываться картинку с индексом 1. При отсутствии ссылки не другую ячейку RefArray[N] = -1. В таком случае, при условии DefaultOrder = False в ячейке Col = 2, Row = 1 прорисовывалось бы именно картинка с индексом 12. Для более удобного доступа к массиву ссылок есть свойство: property Reference[Col, Row: Integer]: TGridCoord; TGridCoord = record Col, Row: Integer; end; Для задания переменной типа есть функция: function GridCoord(Col, Row: Integer): TGridCoord; Рассмотрим теперь событие, возникающее при прорисовки картинки в ячейке: TDrawPictureEvent = procedure(Sender: TObject; Rect: TRect; Index, Col, Row: Integer; var X, Y: Integer; Picture: TBitmap; var Background: TBitmap; var Continue: Boolean) of object; Параметры X и Y представляют собой координаты левого верхнего угла картинки (по умолчанию равны 0), которые можно изменять, Picture: собственно, сама картинка, Background картинка, по умолчанию равная nil. Если присвоить этому параметру значение, то картинка Background будет представлять собой фон ячейки. Также, если картинка Background по размерам меньше, ячейки, то она автоматически будет размножена. Также в компоненте TGraphGrid есть ряд свойств, которые, я думаю, объяснять не нужно, так как они интуитивно понятны (например свойство Scale - для автоматического масштабирования картинки и т.д.)
(153 K)
И, наконец, последний пункт - дополнение в виде движущегося текста. В компоненте TGraph также еще есть одна функция Appearance, но о ней я пока рассказывать не буду. procedure TGraph.AnimatedText(Canvas: TCanvas; Application: TApplication; Source: TBitmap; List: TStrings; Alignment: TAlignment; X, Y, Decrement: Integer; Delay: LongWord); Свойство Canvas представляет собой объект типа TCanvas на котором и будет осуществляться прорисовка. Application служит для вызова функции ProcessMessages. Source представляет собой фоновую картинку, Alignment - способ выравнивания текста, X, Y, координаты, по ним определяется границы движения текста, Decrement величина, определяющая через сколько пикселей текст будет перерисован. Delay - соответственно, задержка текста в миллисекундах.
(180 K)
Заключение
Если Вам удалось прочитать всю это до конца, то я уверен, Вы сможете найти практическое применение для моих компонентов, а скачать их можно здесь — (11 K)
Копирование на практике
Произведём копирование данных на практике. Для этого создадим новый проект под названием Test. Вносим название баз данных. Примечание: Новая база данных (куда мы копируем) должна быть совершенно пустой. У меня её размер составляет 230 Кб. Т.е. в ней нет ничего, кроме системных таблиц InterBase и т.п.. Переходим на таблицу названий копируемых таблиц и заполняем в той последовательности, в которой хотим копировать. За последовательность отвечает столбец Сорт. Примечание: Последовательность важна, если мы при копировании информации не блокируем триггера и ограничения по вторичным ключам. Там где поле Название заполняем название таблицы. Можно указать Русское название - поле чисто для информации на русском языке. Если Вы применяете генераторы для ключевого поля, то необходимо заполнить поля Первичный, Название генератора, Название процедуры генератора. Тогда программа автоматически будет создавать генератор, устанавливать его значение по максимальному значению указанного Вами поля, а так же создавать хранимую процедуру для выборки значения генератора. Так как мы копируем информацию в полностью пустую базу данных, то необходимо указать скрипты генерации базы данных. Для этого щёлкаем на закладке Файлы скриптов и указываем заполняем таблицу. Если указана галочка в поле П, то это означает, что скрипт будет выполнен перед копированием информации в базе данных. Обычно, это скрипт генерируемый системами проектирования баз данных (например ErWin). Если указана галочка И, то это означает, что скрипт будет выполняться.
Примечание: В качестве раздалителя в скриптах используется ^. Вот и завершена предварительная работа перед копированием базы данных. Теперь можно перейти на закладку Проекты, отметить галочки отключений (если нужно и нажать на Перекачать информацию. (В моём случае даже не нужно отключение триггеров и ограничений). Появится окно с информацией о процессе копирования информации и выполнения скриптов.
Это процесс длительный. Программа не забирает всё процессорное время и позволяет в это время Вам работать над другими задачам. При копировании таблиц показывается номер копируемой строки. После завершения копирования показывается общее количество ошибок, предупреждений, ошибок в скрипте. Вот мы и сгенерировали полностью работоспособную базу данных. И обошлось практически без потерь информации. Рудюк Сергей
Лицензионное соглашение.
Лицензионное соглашение написано в каждом сооветствующем юните, здесь же написано некоторое пояснение : TRySharedMem и TRySharedStream - это, по большому счету, базируются на результате(ах) работы FileMappingFunctions, но немалое значение здесь имеет и человеческий фактор: как вы распорядитесь объектами отображения, какой файл вы отобразите и что, как и сколько вы туда запишите никто не может знать, а файловая область, как вы знаете, это не шутка. Поэтому программный код дается вам бесплатно, по принципу "as is". асны с лицензионным соглашением или с некоторыми пунктами - вы не должны использовать данный програмный код в ваших проектах.
ListBox с расшифровкой длинных строк
В некоторых программах я встречал очень удобный дополнительный интерфейс стандартного списка ListBox: при наведении мышки на строчку, которая по ширине полностью не помещалась в контроле, рядом всплывало поясняющее окошко содержащее эту строчку целиком. Это очень удобный интерфейс; если пользователю хочется уточнить, что же в точности написано в скрытой строке списка, то ему не надо расширять/сужать форму, дергать Spliter'ы и так далее, достаточно просто подвести мышку к интересующей его строке. Когда мне понадобилось снабдить таким интерфейсом ListBox в одном из моих проектов, я решил, что для решения этой задачи можно воспользоваться каким-нибудь сторонним компонентом. Но вот беда, на Torry такого компонента я не нашел. Поиск по другим сайтам выявил несколько похожих компонент, но все они были какими-то недоделанными, самопальными... Видимо, подумал я, этот компонент легче написать самому; благо тут нет ничего сложного. Так я и сделал. Получились два довольно приличных компонента TTipListBox и TTipCheckListBox, которые и предлагаются вашему вниманию. С интересом выслушаю все соображения по улучшению функциональности компонента и упрощению его кода. Собственно, я и выкладываю-то эти компоненты ради обсуждения их общественностью. :)
Исходники компонентов и демонстрационный проект (Delphi 5) (7K)
Специально для Смотрите по теме:
Матрицы в Delphi
Уважаемые сограждане. В ответ на вопросы Круглого Стола, в основном, от собратьев студентов, публикую алгоритмы матричного исчисления. В них нет ничего сложного, все базируется на функциях стандартного Borland Pascal еще версии 7.0. Я понимаю, что уровень подготовки наших преподавателей весьма отстает не то, что от нынешних технологий, но даже и от весьма более ранних, но все-таки попробую помочь собратьям "по-несчастью".... :o)))
Итак, в приведен исходный текст весьма простенькой библиотеки Matrix.pas... Перечень функций этой библиотеки: type MatrixPtr = ^MatrixRec; MatrixRec = record MatrixRow : byte; MatrixCol : byte; MatrixArray : pointer; end; MatrixElement = real; (* Функция возвращает целочисленную степень *) function IntPower(X,n : integer) : integer; (* Функция создает квадратную матрицу *) function CreateSquareMatrix(Size : byte) : MatrixPtr; (* Функция создает прямоугольную матрицу *) function CreateMatrix(Row,Col : byte) : MatrixPtr; (* Функция дублирует матрицу *) function CloneMatrix(MPtr : MatrixPtr) : MatrixPtr; (* Функция удаляет матрицу и возвращает TRUE в случае удачи *) function DeleteMatrix(var MPtr : MatrixPtr) : boolean; (* Функция заполняет матрицу указанным числом *) function FillMatrix(MPtr : MatrixPtr;Value : MatrixElement) : boolean; (* Функция удаляет матрицу MPtr1 и присваивает ей значение MPtr2 *) function AssignMatrix(var MPtr1 : MatrixPtr;MPtr2 : MatrixPtr) : MatrixPtr; (* Функция отображает матрицу на консоль *) function DisplayMatrix(MPtr : MatrixPtr;_Int,_Frac : byte) : boolean; (* Функция возвращает TRUE, если матрица 1x1 *) function IsSingleMatrix(MPtr : MatrixPtr) : boolean; (* Функция возвращает TRUE, если матрица квадратная *) function IsSquareMatrix(MPtr : MatrixPtr) : boolean; (* Функция возвращает количество строк матрицы *) function GetMatrixRow(MPtr : MatrixPtr) : byte; (* Функция возвращает количество столбцов матрицы *) function GetMatrixCol(MPtr : MatrixPtr) : byte; (* Процедура устанавливает элемент матрицы *) procedure SetMatrixElement(MPtr : MatrixPtr;Row,Col : byte;Value : MatrixElement); (* Функция возвращает элемент матрицы *) function GetMatrixElement(MPtr : MatrixPtr;Row,Col : byte) : MatrixElement; (* Функция исключает векторы из матрицы *) function ExcludeVectorFromMatrix(MPtr : MatrixPtr;Row,Col : byte) : MatrixPtr; (* Функция заменяет строку(столбец) матрицы вектором *) function SetVectorIntoMatrix(MPtr,VPtr : MatrixPtr;_Pos : byte) : MatrixPtr; (* Функция возвращает детерминант матрицы *) function DetMatrix(MPtr : MatrixPtr) : MatrixElement; (* Функция возвращает детерминант треугольной матрицы *) function DetTriangularMatrix(MPtr : MatrixPtr) : MatrixElement; (* Функция возвращает алгебраическое дополнение элемента матрицы *) function AppendixElement(MPtr : MatrixPtr;Row,Col : byte) : MatrixElement; (* Функция создает матрицу алгебраических дополнений элементов матрицы *) function CreateAppendixMatrix(MPtr : MatrixPtr) : MatrixPtr; (* Функция транспонирует матрицу *) function TransponeMatrix(MPtr : MatrixPtr) : MatrixPtr; (* Функция возвращает обратную матрицу *) function ReverseMatrix(MPtr : MatrixPtr) : MatrixPtr; (* Функция умножает матрицу на число *) function MultipleMatrixOnNumber(MPtr : MatrixPtr;Number : MatrixElement) : MatrixPtr; (* Функция умножает матрицу на матрицу *) function MultipleMatrixOnMatrix(MPtr1,MPtr2 : MatrixPtr) : MatrixPtr; (* Функция суммирует две матрицы *) function AddMatrixOnMatrix(MPtr1,MPtr2 : MatrixPtr) : MatrixPtr; (* Функция вычитает из первой матрицы вторую *) function SubMatrixOnMatrix(MPtr1,MPtr2 : MatrixPtr) : MatrixPtr; (* Функция решает систему методом Гаусса и возвращает LU-матрицы *) (* Результат функции - вектор-столбец решений *) function GausseMethodMatrix(MPtr,VPtr : MatrixPtr;var LPtr,UPtr,BPtr : MatrixPtr) : MatrixPtr;
Мне кажется, что интерфейсное описание весьма простое, но если возникнут какие-либо вопросы - пишите на E-mail - постараюсь ответить на все Ваши вопросы. Может быть, азы матричного исчисления я опишу в виде отдельной статьи по причине множества поступивших вопросов, хотя в этой матричной математике нет ничего сложного :o) Следует отметить, что теория матриц дает в Ваши руки весьма мощный инструмент по анализу данных весьма различного характера, в чем я неоднократно убеждался на практике.
Важные, на мой взгляд, замечания. НЕ СТЕСНЯЙТЕСЬ использовать подход, использующий стандартный тип Pascal - record - в объектах мало чего хорошего в межкомпиляторном взаимодействии. Да и, кстати, использование типа record до сих пор является самым быстрым способом математических расчетов, в отличиие от ООП. Частенько простое 2+2=4 дает существенный выигрыш по времени выполнения, по сравнению с объектным подходом, а если математических вычислений в Вашей программе великое множество....
P.S. Касательно уровня подготовки наших институтских кадров - эта библиотека сдавалась в качестве лабораторного задания аж в трех уральских университетах (кроме скромной персоны студента (ну, это и понятно ;o), и столь простые принципы работы доказывались чуть ли не перед комиссией от кафедры... ;o)))
С уважением, Специально для
Скачать библиотеку (4 K)
Меню на основе панели инструментов
Наверное многие видели меню, которое используется в MS Office или в самой среде Delphi: главные пункты выглядят как flat-кнопки -- плоские, но при перемещении над ними мыши как бы вспухающие. Кроме того, меню оформлено как панель инструментов и может пристыковываться к окну в любом месте.
Я предлагаю вариант реализации такого меню стандартными средствами VCL.
Сразу замечу, что чтобы двигать меню произвольным образом нужно возиться дополнительно ? TToolBar такой возможности не предоставляет.
Само меню оформляется как объект класса TToolBar, главные пункты меню ? стандартные для панели инструментов кнопки TToolButton, выпадающие подменю ? объекты TPopupMenu.
Итак, по пунктам. 1. Создаётся обычная панель инструментов MenuBar: TToolBar У неё устанавливаются следующие свойства: Color = clMenu //цвет как у меню AutoSize = TRUE // нельзя. 2. Создаются кнопки-главные элементы меню, класс TToolButton. У каждой кнопки устанавливаются свойства: AllowAllUp = TRUE AutoSize = TRUE Grouped = TRUE Caption = "Название пункта меню" 3. Для каждой кнопки создаётся своё подменю ? объект класса TPopupMenu В каждом из подменю задаётся соответствующий список пунктов. У кнопок на панели MenuBar свойство DropdownMenu заполняется ссылкой на соответствующий объект TPopupMenu.
Всё? Нет. К сожалению установленный в системе шрифт нельзя задать в design-режиме, а потому: 4. Когда-нибудь, до использования меню (например в обработчике события формы OnCreate) должен исполниться следующий код: var nc: TNonClientMetrics; s: TFontStyles; . . . . begin . . . . //читаем системные настройки в структуру nc nc.cbSize := sizeof(nc); SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof(nc), @nc, 0); with MenuBar.Font do begin Charset := nc.lfMenuFont.lfCharSet; //устанавливаем charset Height := nc.lfMenuFont.lfHeight; //высоту шрифта Name := nc.lfMenuFont.lfFaceName; //гарнитуру шрифта //далее определяем набор стилей s := []; if nc.lfMenuFont.lfWeight >= FW_BOLD then s := s + [fsBold]; if nc.lfMenuFont.lfItalic <> 0 then s := s + [fsItalic]; if nc.lfMenuFont.lfUnderline <> 0 then s := s + [fsUnderline]; if nc.lfMenuFont.lfStrikeOut <> 0 then s := s + [fsStrikeOut]; Style := s; end; 5. Есть ещё один существенный недостаток. Созданное описанным образом меню не считается таковым с точки зрения Windows. Самое явное последствие ? оно не подсвечивается по нажатию клавши Alt. Исправить данный недостаток удалось написанием обработчика сообщения WM_SYSCOMMAND, которое вызывается, в частности, для выделения пункта меню по горячей клавише.
Заведите новый метод в private-секции формы с меню (имя метода и параметра роли не играет): procedure SysCommand(var M: TWMSysCommand); message WM_SYSCOMMAND; Реализация метода такова (код основан на исходниках TToolBar): procedure TMain.SysCommand(var M: TWMSysCommand); begin //проверяется, что это команда - menu accelerator key, что дополнительной //кнопки не нажато (только Alt), что никто не захватил (capture) мышь if (M.CmdType and $FFF0 = SC_KEYMENU) and (M.Key = 0) and (GetCapture() = 0) then begin MenuBar.TrackMenu(nil); //аргумент это кнопка, подменю которой вывалится; nil-никакой, такова //стандартная реакция; если хотите, чтобы подменю первой кнопки //сразу развернулось напишите MenuBar.TrackMenu(MenuBar.Buttons[0]); //можно и просто указать компонент-кнопку: MenuBar.TrackMenu(mb_Options); end else inherited; //условие не выполнили - обрабатываем по умолчанию end; Здесь правда возникает другая проблема -- становится недоступным через клавиатуру системное меню (которое с Переместить/Закрыть и пр.), но это уже не самое страшное. Вот теперь всё! Можете запускать программу и смотреть что получилось.
Несколько замечаний. Описанное меню, по сравнению со стандартным имеет и дополнительные преимущества. Ведь созданные popup-менюшки можно использовать и отдельно, именно в качестве popup. Есть у Вас к примеру popup с операциями над каким-то объектом. Вы настраиваете этот объект так, чтобы по правому клику выскакивало меню и записываете соответствующий элемент в главное меню. Когда объект выделяется, кнопке в главном меню ставится Visible := TRUE, когда теряет фокус: Visible := FALSE. Вот и программа сразу солидней стала :) Можно вместо Visible использовать Enabled. Не забывайте и про событие OnPopup у класса TPopupMenu - это хорошее место для динамического скрытия или запрещения отдельных пунктов, в зависимости от состояния программы. Можно ещё создать обычное TMainMenu (но не ссылаться на него в свойстве Menu у главной формы) и кнопкам в панели задавать не DropdownMenu, MenuItem и ссылаться не на отдельные popup-меню, а на пункты главного меню. Единственное преимущество такого способа это то, что меню создаётся реальное, системное. Пятый пункт, с обработкой WM_SYSCOMMAND в данном случае становится бессмысленным. Тем не менее этот вариант мне понравился меньше, поскольку первый более гибок. Смешать оба варианта не удалось -- с выпадением подменю проблемы начались. Так что рекомендую использовать TPopupMenu, как описано. Примерчик, иллюстрирующий всё здесь написанное, а так же содержащий копию текста, который Вы сейчас читаете, содержится в архиве (10 K)
P.S. Уже после публикации статьи ко мне поступила просьба поместить ссылку на ресурс, который содержит интересную реализацию toolbar-меню:
По сути, это pas-файл, описывающий компонент - наследник TToolBar, в который добавлены дополнительные возможности. А именно: (а) необходимые свойства TToolBar (см. выше) устанавливаются сразу в нужные значения; (б) добавлено свойство Menu: TMainMenu, позволяющее автоматически создать toolbar-меню, на основе существующего главного меню.
Основным недостатком можно считать отсутствие синхронизации между обычным главным меню и создаваемым toolbar-меню. Плюс, учтите всё сказанное во втором замечании.
Достоинство в том, что это законченный компонент, которому достаточно задать свойство Menu и не мучаться больше -- всё остальное оформление он выполнит сам.
Методы фильтрации
Помимо описываемых ниже методов, присущих только TTable, наюоры данных имеют также общие свойства, методы и события для фильтрации - Filter, Filtered, OnFilteredRecord, FindFirst, FindLast, FindNext, FindPrior.
Для фильтрации записей TTable имеет следующие методы: > SetRangeStart - устанавливает нижнюю границу фильтра; > EditRangeEnd - утанавливает верхнюю границу фильтра; > ApplyRange - осуществляет фильтрацию записей в TTable; > SetRange - имеет тот же эффект, что и последовательное выполнение методов SetRangeStart, EditRangeEnd и ApplyRange. В качестве параметров используются масивы констант, каждый из которых содержит значения ключевых полей.
Фильтрация методами ApplyRange, SetRange должно проводиться по ключевым полям.По умолчанию берется текущий индекс, определяемый свойством TTable.IndexName или TTable.IndexFieldNames. Если значения этих свойств не установлены, по умолчанию используется главный индекс. Поэтому, если нужно использовать индекс, отличный от главного, необходимо явно переустановить значение свойства TTable.IndexName (имя текущего индекса) или TTable.IndexFieldNames (список полей текущего индекса).
Многострочный Hint
| Раздел Сокровищница | врилов Сергей, дата публикации 26 ноября 2002г. |
Данный модуль является компонентом со стандартной процедурой установки. Работает в среде Delphi 6.
После его регистрации перекрывается редактор свойства Hint в TControl (т.е. во всех control-ах) во время Design-а. При этом изменяется способ редактирования свойства Hint. У него появляется кнопка "...", он становится многострочным и > 255 символов. Длинные хинты дольше читать, и, возможно, Вам потребуется изменить свойства (пример): Application.HintPause := 700; Application.HintHidePause := 10000; По умолчанию установлено 500мс и 2500мс
Текст модуля:
| unit HintProperty; interface uses Windows, Messages, SysUtils, Classes, designintf, DesignEditors, vcleditors, StdCtrls, StrEdit; type THintProperty = class(TStringListProperty) protected ss : TStringList; function GetStrings: TStrings; override; procedure SetStrings(const Value: TStrings); override; end; procedure Register; implementation uses Controls; procedure Register; begin RegisterPropertyEditor(TypeInfo(string), TControl, 'Hint', THintProperty); end; function THintProperty.GetStrings: TStrings; begin ss := TStringList.Create; ss.Text := GetStrValue; Result := TStrings(ss); end; procedure THintProperty.SetStrings(const Value: TStrings); var l : integer; s : string; begin s := value.Text; l := Length(s); if (l > 0) then SetLength(s, l-2); // чтобы не добавляла в конце пустую строку SetStrValue(s); ss.Destroy; end; end. | Буду признателен за замечания. Гаврилов Сергей ноябрь 2002г.
Модуль для печати таблиц TStringGrid
Как-то потребовалось напечатать таблицу StringGrid. Написал простенький алгоритм. Может кому-то будет полезен. Добавляем к своему проекту модуль PrnGridUnit. Вызываем процедуру PrintGrid. Наслаждаемся. Для того, чтобы понять как все работает, смотри исходный код, нижеприведенную схему и прилагаемый пример печати.
Из модуля PrintGrid:
| //процедура печати StringGrid Var //отступы (поля) сверху и слева страницы LeftMarg,TopMarg:Integer; //переменная для хранения значения отступа сверху от страницы для текущей //строки (в пикселях) CurrLine, //переменная для хранения значения отступа слева от страницы длч положения левой //границы текущей ячейки (в пикселях) LeftBorder, //тоже для правой границы текущей ячейки RightBorder, //переменная для хранения значения отступа сверху от страницы для положения верхней //границы текущей ячейки (в пикселях) TopBorder, //тоже для нижней границы текущей ячейки BottomBorder, //текущая строка таблицы Row, //текущий столбец таблицы Col:Integer; //отступ текста от левой границы ячеки LeftOffset:Integer; //счетчик страниц PageCount:Integer; //флаг конца страницы PageEnded:Boolean; //позиция для печати номеров страниц PageCountPrnPos:Integer; //диалог принтера PrintDialog:TPrintDialog; |
Другие небольшие статьи, примеры и программы можете найти на Скачать пример (35K) Смотрите так же:
Модуль для получения интервала дат
Модуль предназначен для визуального выбора пользователем интервала дат с различными настройками.
В модуле находится одна единственная функция function GetPeriod(var StartDate, EndDate: TDateTime): Boolean; Выходные параметры: StartDate - Начальная дата интервала EndDate - Конечная дата интервала Результат: True - Пользователь нажал "Ok" False - Пользователь нажал "Отмена" | | Скачать (3.6K)
Модуль для расчета числовых и логических формул
Модуль предназначен для расчета любых математических или логических выражений. В него уже включен набор стандартных математических и логических функций, но можно создавать свои функции любых типов. Можно также создавать свои типы данных. Логика работы с модулем такова, что сначала создается формула, которая затем преобразуется в цифровой вид, т.н. сценарий, по которому будет производиться расчет. Это нужно для того, чтобы оптимизировать расчет формулы, при этом достигается огромный выигрыш в скорости. Для примера: у меня на компьютере (Athlon 1800XP, 512 MB DDR) расчет средней формулы 10000000 раз происходит за 1 - 1,5 секунды. Это при оптимизированном расчете. А при обычном (мои самые первые неудачные варианты) расчет той же формулы 10000 раз занимал около полминуты. Под словом "средней" я имею ввиду не очень длинную формулу, которая не перегруженна большим количеством функций. Но на саму формулу не накладывается вообще никаких ограничений, она может иметь любое количество операндов, она также может иметь любое количество вложенных выражений. Вложеннное выражение - это с технической точки зрения отдельная формула, которая находится внутри другой формулы и может иметь еще любое количество вложенных формул. А с точки зрения пользователя это просто выражение, заключенное в скобки и обладающее приоритетом в вычислении.
Модуль экспорта/импорта данных между Oracle и DBF
Краткое описание функций модуля: Ora2DBF - Функция конвертации данных из Oracle в DBF файл Параметры: nService - Имя сервиса nUserID - Имя пользователя nPasswd - Пароль fQuery - Файл запроса fTableName - Имя DBF файла isAppend: Boolean; - Добавлять записи в существующий файл или создавать заново ProgressBar - Указатель на объект строки прогресса (допустимо nil) MessageEvent - Указатель на процедуру отображения сообщений (допустимо nil) DBF2Ora - Функция конвертации данных из DBF файла в таблицу Oracle Параметры: nService - Имя сервиса nUserID - Имя пользователя nPasswd - Пароль fDTable - Имя DBF файла fTableName - Имя таблицы ProgressBar - Указатель на объект строки прогресса (допустимо nil) MessageEvent - Указатель на процедуру отображения сообщений (допустимо nil) Реализована возможность открытия файла DBF без наличия индекса.
Средство разработки: Delphi 5 Необходимо: RxLib Скачать : (4 K)
Смотрите также :
Модуль потоковой записи/чтения структуры и данных объекта TRxMemoryData.
RxLib - одна из самых лучших Delphi-библиотек, уже давно ставшая классикой разработки. В ее составе содержится компонент TRxMemoryData - "таблица в памяти", работающая напрямую, без каких-либо дополнительных платформ. Компонент очень удобен для операций с относительно небольшими объемами данных. Разумеется, можно использовать очень качественный и многофункциональный TClientDataSet, однако в условиях разработки на версиях Delphi младше D5 отсутствует возможность поставки приложения без дополнительных DLL. Кроме того, TRxMemoryData гораздо меньше добавляет веса к исполняемому модулю. Процедуры потоковой записи-чтения позволят организовать на базе TRxMemoryData простую и гибкую систему хранения информации, удобную для реализации задач, оперирующих данными сравнительно небольших объемов, с доступом "по законам" TDataSet. Ниже приводится полный текст модуля. Проверено на Delphi4 + RxLib 2.75.
Скачать (3K)
| unit RxMemDSUtil; // --------------------------------------------------------------------------------------- // Дополнительные инструменты для работы с TRxMemoryData // --------------------------------------------------------------------------------------- interface uses Classes, SysUtils, RxMemDS; type // Прикладные исключения записи и чтения (сообщения на русском) ERxMemoryDataWriteError = class(Exception); ERxMemoryDataReadError = class(Exception); // Обратная связь при чтении-записи TReadWriteRxMemoryDataCallback = procedure(ACurrent, ATotal : Integer; var ACancel : Boolean) of object; // Запись в поток. При ошибках генерируются исключения. procedure WriteRxMemoryDataToStream(AObject : TRxMemoryData; AStream : TStream; ABufSize : Integer = 32768; ACallback : TReadWriteRxMemoryDataCallback = nil); // Чтение из потока (структура и данные будут загружены из потока "как есть" - без учета текущей структуры). // При ошибках генерируются исключения. procedure ReadRxMemoryDataFromStream(AObject : TRxMemoryData; AStream : TStream; ABufSize : Integer = 32768; ACallback : TReadWriteRxMemoryDataCallback = nil); // Запись в файл. При ошибках генерируются исключения. procedure WriteRxMemoryDataToFile(AObject : TRxMemoryData; AFileName : String; AFileMode : Word = (fmCreate or fmOpenWrite or fmShareDenyWrite); ABufSize : Integer = 32768; ACallback : TReadWriteRxMemoryDataCallback = nil); // Чтение из файла. При ошибках генерируются исключения. procedure ReadRxMemoryDataFromFile(AObject : TRxMemoryData; AFileName : String; AFileMode : Word = (fmOpenRead or fmShareDenyWrite); ABufSize : Integer = 32768; ACallback : TReadWriteRxMemoryDataCallback = nil); implementation uses DB, TypInfo; // --------------------------------------------------------------------------------------- // Внутрение типы и константы // --------------------------------------------------------------------------------------- const // Поддерживаемые типы полей (запись, чтение) DefProcessableFields : set of TFieldType = [ ftString, ftSmallint, ftInteger, ftWord, ftBoolean, ftFloat, ftCurrency, ftDate, ftTime, ftDateTime, ftAutoInc, ftBlob, ftMemo, ftGraphic, ftFmtMemo, ftBytes ]; // --------------------------------------------------------------------------------------- // Внутрение вызовы // --------------------------------------------------------------------------------------- procedure _WriteFieldValueToStream(AField : TField; AWriter : TWriter); var tmpBool : Boolean; begin with AField, AWriter do begin // Отслеживаем NULL-значение tmpBool := (IsNull and (not (DataType in [ftBlob, ftMemo, ftGraphic, ftFmtMemo]))); WriteBoolean(tmpBool); if(tmpBool) then exit; // Строка или бинарные данные if((DataType in [ftString, ftBlob, ftMemo, ftGraphic, ftFmtMemo, ftBytes]) or IsBlob) then WriteString(AsString) else begin case(DataType) of // Целое ftSmallint, ftInteger, ftWord, ftAutoInc : WriteInteger(AsInteger); // Логическое ftBoolean : WriteBoolean(AsBoolean); // Вещественное ftFloat : WriteFloat(AsFloat); // Валюта ftCurrency : WriteCurrency(AsCurrency); // Дата и время ftDate, ftTime, ftDateTime : WriteDate(AsDateTime); else raise ERxMemoryDataWriteError.Create('Неожиданная ошибка записи (неизвестный тип поля).'); end; end; end; end; procedure _ReadFieldValueFromStream(AField : TField; AReader : TReader); begin with AField, AReader do begin // Отслеживаем NULL-значение if(ReadBoolean) then begin Value := NULL; exit; end; // Строка или бинарные данные if((DataType in [ftString, ftBlob, ftMemo, ftGraphic, ftFmtMemo, ftBytes]) or IsBlob) then AsString := ReadString else begin case(DataType) of // Целое ftSmallint, ftInteger, ftWord, ftAutoInc : AsInteger := ReadInteger; // Логическое ftBoolean : AsBoolean := ReadBoolean; // Вещественное ftFloat : AsFloat := ReadFloat; // Валюта ftCurrency : AsCurrency := ReadCurrency; // Дата и время ftDate, ftTime, ftDateTime : AsDateTime := ReadDate; else raise ERxMemoryDataReadError.Create('Неожиданная ошибка записи (неизвестный тип поля).'); end; end; end; end; procedure _Callback(ACallback : TReadWriteRxMemoryDataCallback; ACurrent, ATotal : Integer; AExceptionClass : ExceptClass); var tmpCancel : Boolean; tmp : String; begin if(not Assigned(ACallback)) then exit; tmpCancel := False; try ACallback(ACurrent, ATotal, tmpCancel); if(tmpCancel) then begin tmp := ' '; if(AExceptionClass = ERxMemoryDataWriteError) then tmp := ' записи '; if(AExceptionClass = ERxMemoryDataReadError) then tmp := ' чтения '; raise AExceptionClass.Create('Процесс' + tmp + 'прерван.'); end; finally tmp := ''; end; end; // --------------------------------------------------------------------------------------- // Внешние вызовы // --------------------------------------------------------------------------------------- // Запись в поток. При ошибках генерируются исключения. procedure WriteRxMemoryDataToStream(AObject : TRxMemoryData; AStream : TStream; ABufSize : Integer; ACallback : TReadWriteRxMemoryDataCallback); var tmpWriter : TWriter; tmpRecNo : Integer; i, n : Integer; begin // Проверка параметров if(not Assigned(AObject)) then raise ERxMemoryDataWriteError.Create('Неверный параметр (AObject).'); if(not Assigned(AStream)) then raise ERxMemoryDataWriteError.Create('Неверный параметр (AStream).'); if(ABufSize 'Неверный параметр (ABufSize).'); with AObject do begin // Получаем текущую позицию (заодно проверям активность таблицы) tmpRecNo := RecNo; // Проверяем типы полей for i := 0 to Fields.Count - 1 do begin if(not (Fields[i].DataType in DefProcessableFields)) then raise ERxMemoryDataWriteError.Create('Поля данного типа не поддерживаются (поле ' + Fields[i].FieldName + ', тип ' + GetEnumName(TypeInfo(TFieldType), Integer(Fields[i].DataType)) + ').'); end; end; // Далее AObject.DisableControls; tmpWriter := TWriter.Create(AStream, ABufSize); try with tmpWriter, AObject do begin // Вызываем callback _Callback(ACallback, 0, RecordCount, ERxMemoryDataWriteError); // Пишем сигнатуру и тип класса WriteSignature; WriteString(ClassName); // Пишем структуру WriteCollection(FieldDefs); // Пишем данные WriteInteger(RecordCount); WriteListBegin; First; n := 0; while(not EOF) do begin for i := 0 to Fields.Count - 1 do _WriteFieldValueToStream(Fields[i], tmpWriter); Inc(n); // Вызываем callback _Callback(ACallback, n, RecordCount, ERxMemoryDataWriteError); // Далее Next; end; WriteListEnd; if(n <> RecordCount) then raise ERxMemoryDataWriteError.Create('Неожиданная ошибка (несовпадение количества записей).'); // Все FlushBuffer; end; finally tmpWriter.Free; AObject.RecNo := tmpRecNo; AObject.EnableControls; end; end; // Чтение из потока (структура и данные будут загружены из потока "как есть" - без учета текущей структуры). // При ошибках генерируются исключения. procedure ReadRxMemoryDataFromStream(AObject : TRxMemoryData; AStream : TStream; ABufSize : Integer; ACallback : TReadWriteRxMemoryDataCallback); var tmpReader : TReader; i, j, n : Integer; begin // Проверка параметров if(not Assigned(AObject)) then raise ERxMemoryDataReadError.Create('Неверный параметр (AObject).'); if(not Assigned(AStream)) then raise ERxMemoryDataReadError.Create('Неверный параметр (AStream).'); if(ABufSize 'Неверный параметр (ABufSize).'); // Проверяем - открыта ли таблица ? (и на чтении, и на записи - должна быть открыта) // AObject.Next; // Далее AObject.DisableControls; tmpReader := TReader.Create(AStream, ABufSize); try with tmpReader, AObject do begin // Чистим таблицу Open; EmptyTable; Close; FieldDefs.Clear; Fields.Clear; // Вызываем callback _Callback(ACallback, 0, 0, ERxMemoryDataReadError); // Читаем сигнатуру и тип класса ReadSignature; if(ReadString <> AObject.ClassName) then raise ERxMemoryDataReadError.Create('Несоответствие типов сохраненного объекта и объекта назначения.'); // Читаем структуру ReadValue; ReadCollection(AObject.FieldDefs); // Открываем Open; // Проверяем типы полей for i := 0 to Fields.Count - 1 do begin if(not (Fields[i].DataType in DefProcessableFields)) then raise ERxMemoryDataReadError.Create('Поля данного типа не поддерживаются (поле ' + Fields[i].FieldName + ', тип ' + GetEnumName(TypeInfo(TFieldType), Integer(Fields[i].DataType)) + ').'); end; // Читаем данные n := ReadInteger; ReadListBegin; j := 0; while(j <> n) do begin Append; for i := 0 to Fields.Count - 1 do _ReadFieldValueFromStream(Fields[i], tmpReader); Post; Inc(j); _Callback(ACallback, j, n, ERxMemoryDataReadError); end; ReadListEnd; if((j <> n) or (n <> RecordCount)) then raise ERxMemoryDataReadError.Create('Неожиданная ошибка (несовпадение количества записей).'); First; // Все end; finally tmpReader.Free; AObject.EnableControls; end; end; // Запись в файл. При ошибках генерируются исключения. procedure WriteRxMemoryDataToFile(AObject : TRxMemoryData; AFileName : String; AFileMode : Word; ABufSize : Integer; ACallback : TReadWriteRxMemoryDataCallback); var tmpStream : TFileStream; begin tmpStream := TFileStream.Create(AFileName, AFileMode); try WriteRxMemoryDataToStream(AObject, tmpStream, ABufSize, ACallback); finally tmpStream.Free; end; end; // Чтение из файла. При ошибках генерируются исключения. procedure ReadRxMemoryDataFromFile(AObject : TRxMemoryData; AFileName : String; AFileMode : Word; ABufSize : Integer; ACallback : TReadWriteRxMemoryDataCallback); var tmpStream : TFileStream; begin tmpStream := TFileStream.Create(AFileName, AFileMode); try ReadRxMemoryDataFromStream(AObject, tmpStream, ABufSize, ACallback); finally tmpStream.Free; end; end; // --------------------------------------------------------------------------------------- end. | Скачать (3K)
Модуль реализации матричных вычислений для массивов больших размеров
В этом модуле «осели» все операции с матрицами и векторами, которые я использовал для работы. Но есть алгоритмы, которые многие, наверняка, увидят впервые: Divide – алгоритм прямого деления, MSqrt – квадратный корень, MAbs – абсолютная величина. Поскольку модуль содержит все, от элементарных операций до матричных, разобраться будет несложно: Например, решение системы ЛУ( консольное приложение )
| Var N : Integer; A : Matrix; b, x : Vector; begin N := . . .; A.Init( N, N ); b.Init( N ); x.Init( N ); // или x.Init( B ); или x.InitRow( A ); . . . { формирование A и b } . . . x.Divide( b, A ); x.Print; . . . end. | Некоторые алгоритмы требуют пояснения, например: Matrix.E( i, j : LongWord ) или Vector.E( i : Integer ) : RealPtr, (RealPtr = ^Real) функция для вычисления адреса элемента матрицы/вектора. Перешла из ДОС когда в модуле использовался алгоритм управления виртуальной памятью для больших размерностей. Matrix.Multiple( X, Y : Vector ) Результатом, которого является произведение вектора X на транспонированный вектор Y - матрица ранга 1. Matrix.Invert( A : Matrix ) – если A[N,M], и N <> M то результат – матрица размера [M,N] – псевдообратная = A+. Matrix.Addition( A : Matrix; B : Real ) – добавление числа в главную диагональ. Matrix.Diag( r : Real ) – присваивание значения главной диагонали. Когда есть исходный текст - разобраться можно всегда.
Этот модуль используется почти во всех реализованных мной численных алгоритмах и методах. Те части, которые писал не я – приводятся без изменений(по возможности) стиля и комментариев. Скачать (14K)
Модуль VHeapLow — модуль для работы с виртуальной памятью
Этот модуль был написан в 1992 году, для разработки приложений требующих большой объем памяти для хранения и обработки данных. Обычно такие задачи возникают при программной генерации систем уравнений ( линейных или дифференциальных ). Поддержку больших массивов в программе можно увидеть, например, в Turbo Proffesional. Модуль VheapLow - своеобразная реализация механизма управления виртуальной памятью построенная по образу кучи Turbo Pascal. Применение виртуальной памяти это самое очевидное решение проблемы. С развитием железа и ОС барьер в 640k перестал быть препятствием для решения задач большой размерности. А с этим и актуальность, даже очень крутых реализаций, виртуальных массивов отпала ( естественно, зачем нужна двойная виртуализация ? Я, и сам переделывал свои задачи для работы без этого модуля. Переделывается - очень просто: GetVMem - заменяется на GetMem, а FreeVMem на FreeMem.)
Но, совсем недавно, появилась задача обработки больших объемов текстовой информации, и соответственно возник вопрос как ее хранить и накапливать. Самое простое решение - вообще не выгружать программу из памяти, самое сложное - база данных. Но базу данных со сложной организацией данных вполне можно заменить виртуальной кучей( Virtual Heap ), и работать с ней как с обычной памятью. Поскольку блоки памяти, выделенные в виртуальной куче, можно сохранять сколько угодно долго после завершения программы, добавлять и изменять при последующих запусках. Виртуальная куча - это отображение на дисковом пространстве реальной выделенной памяти. Физически виртуальная куча - файл на диске. В заголовке такого файла есть место для нескольких виртуальных адресов пользователя( см. исх. текст ). По ним можно разместить адреса начал всех цепочек данных, а ООП дает очень удобные механизмы для реализации программы. Основные функции VheapLow : GetVMem - Выделить блок виртуальной памяти. FreeVMem - Освободить память. VirtualToPtr - Центральная процедура модуля. Преобразует виртуальный адрес ( VPtr ) в реальный адрес ОП ( Pointer ). Пример очень простого случая :
| Type MyRec = Record A, B : Real; Q, X : Integer; End; MyRecPtr = ^MyRec; VMayRec = object VAddr : VirtualPtr; {- Virtual heap Address -} function Addr : MyRecPtr; procedure Init; procedure Free; end; function VMyRec.Addr : MyRecPtr; begin RESULT := VirtualToPtr( VAddr ); End; Procedure VMyRec.Init; Begin VAddr := GetVMem( SizeOf( MyRec ) ); End; Procedure VmayRec.Free; Begin FreeVMem( Vaddr ); End; ... Var MR : VMayRec; Begin // InitVHeap( имя файла, оставить после выполнения программы) InitVHeap( 'F.vhp', True ); if VHeapStatus = OldVHeap then MR.VAddr := VHBases[ User01 ] else MR.Init; ... With MR.Addr^ do begin Q := Pi; ... ... | Чтобы сохранить виртуальный адрес до следующего запуска программы его нужно записать в один из базовых адресов перед завершением : VHBases[ User01 ] := MR.VAddr; Для строковых переменных свои отдельные функции по образу Turbo/Object Professional : StringToVHeap( S : String ) : VPtr и StringFromVHeap( VP : VPtr ) : String;
Первая записывает строка в виртуальную память и возвращает виртуальный адрес, вторая по виртуальному адресу возвращает строку.
Модуль еще не полностью переделан под среду Delphi. В нем исправлены лишь явные ошибки, мешающие компилятору. Реализовать все можно абсолютно по-другому и на более высоком уровне. Я, хотел лишь осветить саму идею. Могу добавить, что под ДОС все работало. Была даже программа для разработки структур данных виртуальной памяти. Результатом ее работы был исходный текст модуля с описанием типов объектов и реализацией их методов. К сожалению, программа и ее исходный текст были потеряны вместе с HDD…
| {$A-} unit VHEAPLow; {*********************************************************} {* VHEAPLOW.PAS 7.0 *} {* Writen by HNV 1991,92. *} {* Low level support for virtual heap. *} {*********************************************************} interface uses Classes, Dialogs, OpStrDev, SysUtils; const MaxFree = 256; {- size of free list -} type VirtualPtr = Real; VirtualPtrRec = record Len : LongWord; Addr : LongWord; end; Base = array[ 0..11 ] of VirtualPtr; UserBasesType = ( User01, User02, User03, User04, User05, User06, User07, User08, User09, User10 ); VHeapStatus = { Virtual Heap Status } ( NewVHeap, OldVHeap ); const UseKeyAccess : Boolean = false; { True if use key access } ShowHeapStatus : Boolean = false; { True if need show statistics } MaxHeapSpace : LongWord = 0; { Disk Free } } SaveVHeap : Boolean = False; { true if need saving v-heap } VHeapOk : Boolean = true; { VHeap Error flag } BaseAddr : Base = ( 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ); VHeapCacheProc : Pointer = nil; { Addres of function VirtualToPtr } VHeapErrorProc : Pointer = nil; { Addres of VHeap error routine } {- Virtual Heap File Caching -} MaxCacheSize : LongWord = 0; {- Maximum size of cache -} CacheHeapRatio : Real = 0.5; {- CacheSize / HeapSize -} BytesInCache : LongWord = 0; {- Cache use -} MinCacheLevel : Byte = 8; {- Min number of caching records -} CacheLevel : LongWord = 0; {- Current number of caching records -} CacheRead : LongWord = 0; {- Number of reading records -} CacheWrite : LongWord = 0; {- Number of swaping records -} CacheHits : LongWord = 0; {- Number of cache hits -} CacheEff : Real = 0; {- Caching effect -} CacheSearch : LongWord = 0; {- Cache routine calls -} var VHBases : array[ UserBasesType ] of VirtualPtr Absolute BaseAddr; VHStatus : VHeapStatus; {- Virtual Heap status -} type StringPtr = ^string; VString = object VAddr : VirtualPtr; {- Virtual heap Address -} function Addr : StringPtr; procedure Init; procedure Free; end; procedure InitVHeap( FName : string; Save : Boolean ); {- Initiator of Virtual Heap -} function GetVMem( L : Word ) : VirtualPtr; {- Allocate virtual space -} procedure FreeVMem( V : VirtualPtr ); {- Deallocate virtual space -} function VirtualToPtr( V : VirtualPtr ) : Pointer; {- Converted a virtual pointer to real pointer -} function StrVPtr( V : VirtualPtr ) : StringPtr; {- Converted a virtual pointer to real pointer -} procedure SetCaching( TF : Boolean ); {- Turn Caching OFF if TF is false. -} function StructToVHeap( var S; L : Word ) : VirtualPtr; {- Allocate space for S and return virtual pointer -} procedure StructFromVHeap( var S; V : VirtualPtr ); {- Return S at virtual pointer V -} function StringToVHeap( S : string ) : VirtualPtr; {- Allocate space for S and return virtual pointer -} function StringFromVHeap( V : VirtualPtr ) : string; {- Return S at virtual pointer V -} procedure Statistics( ST : TStrings ); {- Show virtual heap staistics on the screen. -} implementation type FreeListA = array[ 1..MaxFree ] of VirtualPtrRec; FreeListV = array[ 1..MaxFree ] of VirtualPtr; FreeListVHeap = record NFree : LongWord; case Byte of 1 : ( FreeL : FreeListA ); 2 : ( FreeV : FreeListV ); end; FreeListPtrV = ^FreeListVHeap; type CacheAddr = ^CacheRec; CacheRec = record Next : CacheAddr; RecA : Pointer; case byte of 1 : ( HAddr : VirtualPtr ); 2 : ( VRec : VirtualPtrRec ); end; const MaxHeapAddr : LongWord = $7FFFFFFF; MaxHeapBlock : LongWord = $7FFFFFFF; MinHeapBlock : LongWord = 1; HeaderVHeap : string = 'HNV(C)VIRTUAL HEAP FILE V 1.0'; HeaderVFree : string = 'FREE BLOCKS LIST'; Init : Boolean = False; StructRPtr : Pointer = nil; StructVPtr : VirtualPtr = 0; UseFactor : LongWord = 0; DelFreeArea : LongWord = 0; VHeapOrg : LongWord = 0; {- begin of virtual heap -} VHeapPtr : LongWord = 0; {- end of heap -} Caching : Boolean = True; Cache : CacheAddr = nil; var OldExit : Pointer; FreeListArea : FreeListPtrV; F : file; FileName : string; function VString.Addr : StringPtr; begin if VAddr <> 0 then Addr := VirtualToPtr( VAddr ) else begin VAddr := GetVMem( Succ( SizeOf( string ) ) ); Addr := VirtualToPtr( VAddr ); end; end; procedure VString.Init; begin VAddr := 0; end; procedure VString.Free; begin FreeVMem( VAddr ); end; procedure VHeapError( S : string ); begin ShowMessage( S ); Halt( 1 ); end; procedure Abort( S : string ); begin {Inline( $FF/$1E/>VHeapErrorProc );} VHeapError( S ) end; function AllocateFreeList : Pointer; var i : Word; P : Pointer; begin GetMem( P, SizeOf( FreeListVHeap ) ); with FreeListPtrV( P )^ do begin NFree := 0; for i := 1 to MaxFree do begin FreeL[ i ].Len := 0; FreeL[ i ].Addr := 0; end; end; AllocateFreeList := P; end; procedure Store( var S; V : VirtualPtr ); var i : LongWord; VR : VirtualPtrRec Absolute V; begin Inc( CacheWrite ); VHeapOk := True; Seek( F, VR.Addr ); BlockWrite( F, S, VR.Len, i ); VHeapOk := i = VR.Len; end; procedure Load( var S; V : VirtualPtr ); var i : LongWord; VR : VirtualPtrRec Absolute V; begin Inc( CacheRead ); VHeapOk := True; Seek( F, VR.Addr ); BlockRead( F, S, VR.Len, i ); VHeapOk := i = VR.Len; end; procedure LoSwapProc( var P : Pointer; V1, V2 : VirtualPtr ); var VR1 : VirtualPtrRec Absolute V1; VR2 : VirtualPtrRec Absolute V2; begin if P <> nil then begin Store( P^, V1 ); FreeMem( P, VR1.Len ); end; if not VHeapOk then exit; GetMem( P, VR2.Len ); Load( P^, V2 ); end; function LoCacheProc( V : VirtualPtr ) : Pointer; begin VHeapOk := True; if StructVPtr <> V then begin LoSwapProc( StructRPtr, StructVPtr, V ); LoCacheProc := StructRPtr; end else LoCacheProc := StructRPtr; end; procedure CacheNormalizing( L : LongWord ); {--------------------------------------} { Normalizing of Cache size to L value } {--------------------------------------} var P, T : CacheAddr; Done : Boolean; begin repeat P := Cache; T := P; Done := True; while ( BytesInCache + L > MaxCacheSize ) and ( P <> nil ) do with P^ do if Next = nil then begin Store( RecA^, HAddr ); Dec( BytesInCache, VRec.Len ); { Decrement Cache length } if CacheLevel 'INSSUFICIENT MEMORY FOR CACHING.' ); WriteLn( 'PROGRAM TERMINATED.' ); Abort( '$16 CRITICAL ERROR.' ); end; Dec( CacheLevel ); { Decrement Cache level } FreeMem( RecA, VRec.Len ); { Dispose structure } if T <> P then T^.Next := nil { goto next Cache record } else Cache := nil; FreeMem( P, SizeOf( CacheRec ) ); { dispose Cache record } P := nil; Done := False; end else begin T := P; P := Next; end; until Done; end; function AddRecToCache( V : VirtualPtr ) : Pointer; { addition new Cache record to Cache structure } var P : CacheAddr; VR : VirtualPtrRec Absolute V; begin GetMem( P, SizeOf( CacheRec ) ); with P^ do begin Next := Cache; Cache := P; GetMem( RecA, VR.Len ); Inc( CacheLevel ); {- -} Load( RecA^, V ); Inc( BytesInCache, VR.Len ); HAddr := V; AddRecToCache := RecA; end; end; procedure DelCacheRec( V : VirtualPtr ); { deleting Cache record from Cache structure. uses in FreeVmem } var P, T : CacheAddr; begin if not Caching then { exit if Caching is not active } exit; { search a virtual heap pointer in Cache structure } P := Cache; T := P; while P <> nil do with P^ do if HAddr = V then begin Dec( BytesInCache, VRec.Len ); { Decrement Cache length } FreeMem( RecA, VRec.Len ); { Dispose this structure } if T <> P then T^.Next := P^.Next { go to next Cache record } else Cache := P^.Next; { go to next Cache record } FreeMem( P, SizeOf( CacheRec ) ); { dispose Cache record } exit; end else begin T := P; P := Next; end; end; {-------------------------------------------------------} { Center function of VHeapLow unit } { converted a virtual heap pointer to real heap pointer } {-------------------------------------------------------} function VirtualToPtr( V : VirtualPtr ) : Pointer; var P, T : CacheAddr; VR : VirtualPtrRec Absolute V; begin Inc( CacheSearch ); if ( VR.Addr < VHeapOrg ) or ( VR.Addr + VR.Len > VHeapPtr ) then Abort( '$46 Invalid virtual pointer.' ); P := Cache; T := P; while P <> nil do if P^.HAddr = V then begin {- Set top of cache -} if T <> P then begin T^.Next := P^.Next; P^.Next := Cache; Cache := P; end; VirtualToPtr := P^.RecA; Inc( CacheHits ); exit; end else begin T := P; P := P^.Next; end; CacheNormalizing( VR.Len ); VirtualToPtr := AddRecToCache( V ); end; function StrVPtr( V : VirtualPtr ) : StringPtr; {- Converted a virtual pointer to string pointer -} begin StrVptr := VirtualToPtr( V ); end; procedure FlushOldRec; begin if StructRPtr <> nil then Store( StructRPtr^, StructVPtr ); end; procedure SetCaching( TF : Boolean ); {- turn Caching OFF if TF is false -} begin end; function ReadLongWord( var F : file ) : LongWord; var L : LongWord; i : LongWord; begin VHeapOk := True; BlockRead( F, L, SizeOf( LongWord ), i ); if i <> SizeOf( LongWord ) then Abort( '$0B initialization error.' ); ReadLongWord := L; end; function ReadString( var F : file ) : string; var i : LongWord; S : string; begin VHeapOK := True; ReadString := ''; i := ReadLongWord( F ); ShowMessage( intToStr( i ) ); SetLength( S, i ); BlockRead( F, S[ 1 ], i, i ); if i <> Length( S ) then Abort( '$0A Initialization Error.' ); ReadString := S; end; function ReadVirtualPtr( var F : file ) : VirtualPtr; var L : VirtualPtr; i : LongWord; begin VHeapOk := True; BlockRead( F, L, SizeOf( VirtualPtr ), i ); if i <> SizeOf( VirtualPtr ) then Abort( '$13 initialization error.' ); ReadVirtualPtr := L; end; function ReadBoolean( var F : file ) : Boolean; var L : Boolean; i : LongWord; begin VHeapOk := True; BlockRead( F, L, SizeOf( Boolean ), i ); if i <> SizeOf( Boolean ) then Abort( '$0D initialization error.' ); ReadBoolean := L; end; procedure WriteLongWord( var F : file; L : LongWord ); var i : LongWord; begin VHeapOk := True; BlockWrite( F, L, SizeOf( LongWord ), i ); if i <> SizeOf( LongWord ) then Abort( '$10 initialization error.' ); end; procedure WriteString( var F : file; S : string ); var i : LongWord; begin VHeapOK := True; WriteLongWord( F, Length( S ) ); BlockWrite( F, S[ 1 ], Length( S ), i ); if i <> Length( S ) then Abort( '$0F Initialization Error.' ); end; procedure WriteVirtualPtr( var F : file; L : VirtualPtr ); var i : LongWord; begin VHeapOk := True; BlockWrite( F, L, SizeOf( VirtualPtr ), i ); if i <> SizeOf( VirtualPtr ) then Abort( '$12 initialization error.' ); end; procedure WriteBoolean( var F : file; L : Boolean ); var i : LongWord; begin VHeapOk := True; BlockWrite( F, L, SizeOf( Boolean ), i ); if i <> SizeOf( Boolean ) then Abort( '$11 initialization error.' ); end; procedure StoreHeader; begin Seek( F, 0 ); WriteString( F, HeaderVHeap ); WriteLongWord( F, UseFactor ); WriteLongWord( F, DelFreeArea ); WriteLongWord( F, VHeapOrg ); WriteLongWord( F, VHeapPtr ); end; procedure StoreBase; var i : Word; begin for i := 0 to 11 do WriteVirtualPtr( F, BaseAddr[ i ] ); end; procedure StoreFreeList; var i : Word; begin WriteString( F, HeaderVFree ); WriteLongWord( F, FreeListArea^.NFree ); for i := 1 to MaxFree do WriteVirtualPtr( F, FreeListArea^.FreeV[ i ] ); end; procedure LoadHeader; begin Seek( F, 0 ); HeaderVHeap := ReadString( F ); UseFactor := ReadLongWord( F ); DelFreeArea := ReadLongWord( F ); VHeapOrg := ReadLongWord( F ); VHeapPtr := ReadLongWord( F ); end; procedure LoadBase; var i : Word; begin for i := 0 to 11 do BaseAddr[ i ] := ReadVirtualPtr( F ); end; procedure LoadFreeList; var i : Word; begin if ReadString( F ) <> HeaderVFree then Abort( '$32 file is not Virtual Heap' ); FreeListArea^.NFree := ReadLongWord( F ); for i := 1 to MaxFree do FreeListArea^.FreeV[ i ] := ReadVirtualPtr( F ); end; function HasExtension( Name : string; var DotPos : Word ) : Boolean; {-Return whether and position of extension separator dot in a pathname} var I : Word; begin DotPos := 0; for I := Length( Name ) downto 1 do if ( Name[ I ] = '.' ) and ( DotPos = 0 ) then DotPos := I; HasExtension := ( DotPos > 0 ) and ( Pos( '\', Copy( Name, Succ( DotPos ), 64 ) ) = 0 ); end; function ForceExtension( Name, Ext : string ) : string; {-Return a pathname with the specified extension attached} var DotPos : Word; begin if HasExtension( Name, DotPos ) then ForceExtension := Copy( Name, 1, DotPos ) + Ext else ForceExtension := Name + '.' + Ext; end; procedure MakeNewVHeap; var i : Word; begin StoreHeader; for i := 0 to 11 do begin BaseAddr[ i ] := 0; end; StoreBase; FreeListArea^.NFree := 0; StoreFreeList; VHeapOrg := FilePos( F ); VHeapPtr := VHeapOrg; StoreHeader; end; procedure VHeapExit; begin if SaveVHeap then begin if Caching then begin CacheNormalizing( MaxCacheSize ); if Cache <> nil then WriteLn( ' Cache ERROR.' ); end else FlushOldRec; StoreHeader; StoreBase; StoreFreeList; Seek( F, VHeapPtr ); Truncate( F ); Close( F ); end else begin Close( F ); Erase( F ); end; end; { --------------------------- GetVmem --------------------------------} function GetVmemPrim( L : Word ) : VirtualPtr; var V : VirtualPtr; VR : VirtualPtrRec Absolute V; j : Word; K : LongWord; procedure SetVHeapPtr; begin VR.Len := L; VR.Addr := VHeapPtr; Inc( VHeapPtr, L ); end; function Hole : Word; var i : Word; begin Hole := 0; with FreeListArea^ do begin K := MaxHeapBlock; for i := 1 to NFree do if ( FreeL[ i ].Len >= L ) and ( ( FreeL[ i ].Len - L ) < K ) then begin K := FreeL[ i ].Len - L; Hole := i; end; end; end; begin with FreeListArea^ do if NFree = 0 then begin SetVHeapPtr; GetVmemPrim := V; exit; end else begin { Search of Minimum Heap Hole } j := Hole; if j <> 0 then begin VR.Len := L; VR.Addr := FreeL[ j ].Addr; Dec( FreeL[ j ].Len, L ); Inc( FreeL[ j ].Addr, L ); if FreeL[ j ].Len = 0 then begin FreeV[ j ] := FreeV[ NFree ]; Dec( NFree ); end; GetVmemPrim := V; exit; end; SetVHeapPtr; GetVmemPrim := V; end; end; function GetVmem( L : Word ) : VirtualPtr; var V : VirtualPtr; P : ^Byte; begin VHeapOk := True; V := GetVmemPrim( L ); GetMem( P, L ); Store( P^, V ); Freemem( P ); if not VHeapOk then GetVmem := VHeapPtr else GetVmem := V; Dec( MaxHeapSpace, L ); end; { --------------------------- GetVmem --------------------------------} { --------------------------- FreeVmem --------------------------------} procedure SortFreeListByAddress; procedure Sort( l, r : Word ); var i, j : Word; x : LongWord; y : VirtualPtr; begin with FreeListArea^ do begin i := l; j := r; x := FreeL[ ( l + r ) div 2 ].Addr; repeat while FreeL[ i ].Addr < x do i := i + 1; while x < FreeL[ j ].Addr do j := j - 1; if i j; if l < j then sort( l, j ); if i < r then sort( i, r ); end; end; begin {quicksort} ; sort( 1, FreeListArea^.NFree ); end; function ReorgFreeList : Boolean; var Q : LongWord; i : Word; begin ReorgFreeList := False; with FreeListArea^ do begin if NFree MaxHeapBlock then exit; Inc( FreeL[ i ].Len, FreeL[ i + 1 ].Len ); FreeV[ i + 1 ] := FreeV[ NFree ]; Dec( NFree ); ReorgFreeList := True; exit; end; end; end; procedure FreeVmem( V : VirtualPtr ); { Erased a virtual heap pointer } var VR : VirtualPtrRec Absolute V; i : Word; K, j : Word; begin DelCacheRec( V ); { free real heap space if Caching is active } with FreeListArea^ do begin if ( VR.Addr + VR.Len ) <> VHeapPtr then begin if ( NFree + 1 ) > MaxFree then begin K := MaxHeapBlock; j := 1; for i := 1 to MaxFree do if FreeL[ i ].Len < K then begin K := FreeL[ i ].Len; j := i; end; FreeV[ j ] := V; end else begin Inc( NFree ); FreeV[ NFree ] := V; end; end else Dec( VHeapPtr, VR.Len ); repeat if NFree > 1 then SortFreeListByAddress; until not ReorgFreeList; end; end; function StructToVHeap( var S; L : Word ) : VirtualPtr; var V : VirtualPtr; begin StructToVHeap := 0; VHeapOk := True; V := GetVMemPrim( L ); if not VHeapOk then exit; Store( S, V ); StructToVHeap := V; end; procedure StructFromVHeap( var S; V : VirtualPtr ); begin VHeapOk := True; Load( S, V ); end; function StringToVHeap( S : string ) : VirtualPtr; var V : VirtualPtr; begin StringToVHeap := 0; VHeapOk := True; V := GetVMemPrim( Length( S ) ); if not VHeapOk then exit; Store( S, V ); StringToVHeap := V; end; function StringFromVHeap( V : VirtualPtr ) : string; begin VHeapOk := True; StringFromVHeap := StringPtr( VirtualToPtr( V ) )^; end; procedure Statistics( ST : TStrings ); var i : Word; begin with FreeListArea^ do begin UseFactor := 0; if NFree > 0 then for i := 1 to NFree do UseFactor := UseFactor + FreeL[ i ].Len; end; Write( TPStr, '------------ VIRTUAL HEAP STATISTICS ---------------' ); ST.Add( ReturnStr ); Write( TPStr, 'і --- Heap --- і' ); ST.Add( ReturnStr ); Write( TPStr, 'і Bytes in Heap......................: ', VHeapPtr - VHeapOrg : 10, ' і' ); ST.Add( ReturnStr ); Write( TPStr, 'і Start Virtual Heap Address.........: ', VHeapOrg : 10, ' і' ); ST.Add( ReturnStr ); Write( TPStr, 'і Bytes available to Virtual Heap....: ', MaxHeapSpace : 10, ' і' ); ST.Add( ReturnStr ); Write( TPStr, 'і і' ); ST.Add( ReturnStr ); Write( TPStr, '----------------------------------------------------' ); ST.Add( ReturnStr ); Write( TPStr, 'і --- Holes --- і' ); ST.Add( ReturnStr ); Write( TPStr, 'і Number of Heap Holes...............: ', FreeListArea^.NFree : 10, ' і' ); ST.Add( ReturnStr ); Write( TPStr, 'і Bytes in accessible Heap Holes.....: ', UseFactor : 10, ' і' ); ST.Add( ReturnStr ); Write( TPStr, 'і Bytes in not accessible Heap Holes.: ', DelFreeArea : 10, ' і' ); ST.Add( ReturnStr ); if ( VHeapPtr - VHeapOrg ) <> 0 then Write( TPStr, 'і Percent Holes in Heap..............: ', ( UseFactor + DelFreeArea ) / ( VHeapPtr - VHeapOrg + UseFactor ) * 100 : 8 : 4, ' % і' ); ST.Add( ReturnStr ); Write( TPStr, 'і --- Cache --- і' ); ST.Add( ReturnStr ); Write( TPStr, 'і Bytes in Cache.....................: ', BytesInCache : 10, ' і' ); ST.Add( ReturnStr ); Write( TPStr, 'і General Cache Space................: ', MaxCacheSize : 10, ' і' ); ST.Add( ReturnStr ); Write( TPStr, 'і Cache Level........................: ', CacheLevel : 10, ' і' ); ST.Add( ReturnStr ); Write( TPStr, 'і Disk Input : ', CacheRead : 10, '; Output : ', CacheWrite : 10, ' і' ); ST.Add( ReturnStr ); if CacheSearch <> 0 then Write( TPStr, 'і Cache Hits : ', CacheHits : 10, '; Eff [%] : ', CacheHits / CacheSearch * 100 : 10 : 3, ' і' ); ST.Add( ReturnStr ); Write( TPStr, 'і і' ); ST.Add( ReturnStr ); Write( TPStr, '----------------------------------------------------' ); ST.Add( ReturnStr ); end; procedure CalcDiskSize( FName : string ); begin if Pos( ':', FName ) <> 0 then MaxHeapSpace := DiskFree( Pos( UpCase( FName[ 1 ] ), 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' ) ) else MaxHeapSpace := DiskFree( 0 ); end; procedure InitVHeap( FName : string; Save : Boolean ); begin if Init then Abort( '$25 Double Initialization.' ); FileName := FName; SaveVHeap := Save; FreeListArea := AllocateFreeList; if CacheHeapRatio > 0.8 then CacheHeapRatio := 0.8; MaxCacheSize := Trunc( MaxHeapAddr * CacheHeapRatio ); Assign( F, FName ); if not FileExists( FName ) then begin Rewrite( F, 1 ); MakeNewVHeap; VHStatus := NewVHeap; CalcDiskSize( FName ); end else begin Reset( F, 1 ); VHStatus := OldVHeap; if ReadString( F ) <> HeaderVHeap then Abort( 'file is not Virtual Heap.' ); LoadHeader; LoadBase; LoadFreeList; Init := True; CalcDiskSize( FName ); end; end; initialization finalization VHeapExit; end. | | Для данного материала нет комментариев. |
Модули для рисования математических формул
игорьев, дата публикации 27 ноября 2002г. |
Модули ExprDraw и ExprMake служат для рисования математических формул. Модуль ExprDraw содержит классы, использующиеся для отображения формул. Эти классы описаны в файле . Создание и установление взаимосвязей между классами, описанными в модуле ExprDraw, для отображения конкретной формулы - занятие трудоёмкое, поэтому для его автоматизации создан модуль ExprMake, который создаёт классы на основании символьной записи формулы. Описание модуля находится в файле .
Описание языка, на котором описываются формулы, вынесено в программу ExprGuide, которая содержится в архиве как в откомпилированном варианте, так и в виде исходных кодов. В левой части окна программы находится список всех конструкций языка. При выборе одного из элементов списка в правой части окна отображается описание и пример использования данной конструкции. Кнопка "Печать" позволяет вывести на принтер описание всех конструкций языка с примерами. Модули ExprDraw и ExprMake поставляются "as is", в том виде, в каком я сам их использую. Первоначально я разрабатывал их исключительно для личного пользования, но потом решил поделиться. Возможности модулей позволяют отображать очень большой спектр формул. В математическом справочнике Бронштейна и Семендяева мне не удалось найти ни одной формулы, которая была бы модулям "не по зубам". Такие формулы есть в некоторых томах "Курса теоретической физики" Ландау и Лифшица, но это связано исключительно с использованием тических и прочих непонятных букв, все остальные конструкции (включая постоянную Планка и лямбду с чертой) модули отображают без проблем. С одним замечанием: моё личное предпочтение - использование для обозначения векторов стрелки над символом, а не жирного шрифта, поэтому модули поддерживают именно стрелку. Текст модулей практически не содержит комментариев, все комментарии вынесены в файлы и . Эти комментарии далеки от полноты, поэтому тем, кто захочет не только использовать готовую библиотеку, но и изменить что-то в ней, придётся серьёзно поработать, чтобы разобраться в коде. Модули написаны на Delphi 5, испытаны в Windows 95 OSR 2.1, Windows NT 4.0 SP6 Workstation, Windows 2000 Advanced Server. По идее, ничего не мешает использовать эти модули и в других версиях Delphi, так как они поставляются в исходных кодах. Никакой специальной установки модулей не нужно, просто поместите файлы ExprDraw.pas и ExprMake.pas в один из тех каталогов, в которых Delphi ищет библиотеки, и добавьте ExprDraw и ExprMake в раздел uses своего модуля. Просьба всем тем, кто будет использовать модули в своих программах, найти в About Box'е или ещё где-нибудь место для фразы типа "Для отображения математических формул используются модули ExprDraw и ExprMake, разработанные Григорьевым Антоном, e-mail." По этому же адресу можно высылать мне замечания, информацию об ошибках в модулях и предложениях.
Скачать библиотеку: (278 K) дата обновления 04.12.02 — решена проблема работы модулей под Windows 98/ME
Убедительная просьба НЕ ПОСЫЛАТЬ мне писем следующих типов: "Что такое каталог, где Delphi ищет библиотеки?" Ответ на такой этот вопрос можно найти в справочной системе Delphi или в любой книге про Delphi для начинающих. "Для чего нужна функция XXXX в классе TExprXXXX?" "Как работает функция XXXX?" и т.п. Все комментарии, которые я считал нужным дать, находятся в файлах Expr*.txt. В остальном разберитесь сами с помощью исходных текстов. "Мне нравятся ваши модули, но не хватает таких-то функций и/или классов: ... Не могли бы вы помочь мне их разработать?" Не мог бы. Вы и так на халяву получили сложную библиотеку, на которую у меня ушло очень много времени. Сделайте хоть что-то сами. Или давайте обсудим стоимость доработки модулей под ваши нужды. "Я начинающий программист, помогите мне, пожалуйста, сделать то-то и то-то..." Для таких вопросов существуют интернет-конференции. Например, "" в "Королевстве Delphi", который я регулярно просматриваю и отвечаю на все вопросы, на которые смогу. Пишите туда, а не в мой ящик. С пожеланиями успешной работы
, Черноголовка, 27.11.02 Специально для
Набор функций для создания диалоговых окон в стиле диалогов помощника MSOffice 2000.
| Раздел Сокровищница | рь Шевченко, дата публикации 26 апреля 2002г. |
Заменяет стандартные диалоги из Dialogs.pas, создаваемые по функции CreateMessageDialog (ShowMessage, MessageDlg). Все диалоги можно перемещать мышью за область формы. Чего не стал добиваться, это поведения кнопок, аналогичного кнопкам в диалогах помощника: при наведении на них мышью, они меняют свой вид. Каждое диалоговое окно можно ассоциировать с Control'ом (хвостик диалога будет указывать на Control), если параметр Control не указан или равен nil, то хвостик в диалогах появляться не будет, и диалог будет размещен по центру главной формы приложения. Имеется ряд глобальных переменных, действующих на внешний вид всех диалогов: MessageColor - цвет фона диалогов, RoundRectCurve - Размер эллипса для скругления углов формы TriangleWidth - Ширина треугольного хвостика TriangleHeight - Высота треугольного хвостика TriangleIndent - Смещение треугольного хвостика относительно края диалога Состав: unit HSFlatButton - компонент плоской кнопки для отображения в диалогах. unit HSDialogs - собственно, функции для создания диалогов. function CreateHSMessageDialog (const Msg: string; DlgType: TMsgDlgType; Buttons: TMsgDlgButtons; { Control, на который указывает хвостик } AEditorControl : TControl = nil; { Позиция хвостика относительно левого края Control } AXCursorOffset : Integer = 20; { Позиция хвостика относительно верхнего (или нижнего) края Control } AYCursorOffset : Integer = 2): TCustomForm; { Замена функции MessageDlg } function HSMessageDlg(const Msg: string; DlgType: TMsgDlgType; Buttons: TMsgDlgButtons; HelpCtx: Longint; AEditorControl : TControl = nil; AXCursorOffset : Integer = 20; AYCursorOffset : Integer = 2): Integer; { Замена функции ShowMessage } procedure HSShowMessage(const Msg: string; AEditorControl : TControl = nil; AXCursorOffset : Integer = 20; AYCursorOffset : Integer = 2); { Замена функции ShowMessageFmt } procedure HSShowMessageFmt(const Msg: string; Params : array of const; AEditorControl : TControl = nil; AXCursorOffset : Integer = 20; AYCursorOffset : Integer = 2); { Замена функции ShowMessage cо стандартной пиктограммой "Ошибка" } procedure HSShowError(const Msg: string; AEditorControl : TControl = nil; AXCursorOffset : Integer = 20; AYCursorOffset : Integer = 2); { Замена функции ShowMessageFmt cо стандартной пиктограммой "Ошибка" } procedure HSShowErrorFmt(const Msg: string; Params : array of const; AEditorControl : TControl = nil; AXCursorOffset : Integer = 20; AYCursorOffset : Integer = 2); { Замена функции ShowMessage cо стандартной пиктограммой "Предупреждение" } procedure HSShowWarning(const Msg: string; AEditorControl : TControl = nil; AXCursorOffset : Integer = 20; AYCursorOffset : Integer = 2); { Замена функции ShowMessageFmt cо стандартной пиктограммой "Предупреждение" } procedure HSShowWarningFmt(const Msg: string; Params : array of const; AEditorControl : TControl = nil; AXCursorOffset : Integer = 20; AYCursorOffset : Integer = 2); { Замена функции ShowMessage cо стандартной пиктограммой "Информация" } procedure HSShowInfo(const Msg: string; AEditorControl : TControl = nil; AXCursorOffset : Integer = 20; AYCursorOffset : Integer = 2); { Замена функции ShowMessageFmt cо стандартной пиктограммой "Информация" } procedure HSShowInfoFmt(const Msg: string; Params : array of const; AEditorControl : TControl = nil; AXCursorOffset : Integer = 20; AYCursorOffset : Integer = 2); { Диалог подтверждения с кнопками "Да"/"Нет" и стандартной пиктограммой "Подтверждение" } function HSConfirmMessage(const Msg: string; AEditorControl : TControl = nil; AXCursorOffset : Integer = 20; AYCursorOffset : Integer = 2) : Boolean; { Диалог подтверждения с кнопками "Да"/"Нет" и стандартной пиктограммой "Подтверждение", с параметрами для функции Format } function HSConfirmMessageFmt(const Msg: string; Params : array of const; AEditorControl : TControl = nil; AXCursorOffset : Integer = 20; AYCursorOffset : Integer = 2) : Boolean; { Диалог подтверждения с кнопками "Да"/"Нет"/"Отмена" и стандартной пиктограммой "Подтверждение" } function HSAskYesNoCancel(const Msg: string; AEditorControl : TControl = nil; AXCursorOffset : Integer = 20; AYCursorOffset : Integer = 2) : Integer; { Диалог подтверждения с кнопками "Да"/"Нет"/"Отмена" и стандартной пиктограммой "Подтверждение", с параметрами для функции Format } function HSAskYesNoCancelFmt(const Msg: string; Params : array of const; AEditorControl : TControl = nil; AXCursorOffset : Integer = 20; AYCursorOffset : Integer = 2) : Integer;
В (11.8K) содержатся unit's для диалогов и тестовый пример.
Любая критика, предложения и пожелания принимаются :-) С уважением,
Набор классов для работы с журналом событий в WinNT/2000/XP.
Класс TDLLCache. Предназначен для хранения списка загруженных динамических библиотек, используемых при формировании текстов сообщений при расшифровке записей журнала событий. Используется внутри класса TEventLogRecordDecoder.
Методы: function LoadLibrary( const Name : string; FLags : DWORD ) : HInstance; загружает библиотеку и возвращает ее описатель. procedure UnloadLibrary( const Name : string ); выгружает библиотеку. procedure UnloadAll; выгружает все загруженные библиотеки. Класс TEventLogRecordDecoder. Предназначен для расшифровки данных записи журнала событий. Большинство свойств соответствуют полям структуры EVENTLOGRECORD. Их подробное описание есть в справке и MSDN.
Методы: constructor Create( const ALogName : string ); ALogName имя журнала. Должно соответствовыть имени одного из подключей ключа HKLM\SYSTEM\CurrentControlSet\Services\Eventlog реестра, обычно Application, System и Security. Это имя можно также задать при помощи свойства LogName. procedure Reset; - выгружает все загруженные при расшифровке динамические библиотеки. procedure GetRawData(Stream : TStream); Сохраняет в поток Stream двоичные данные, содержащиеся в записи о событии. Указатель на этот блок данных можно получить также из свойства Data, а длину - из DataLength. procedure GetRawRecord(Stream : TStream); Сохраняет в поток Stream содержимое записи журнала целиком. procedure ValidateRecord; Выполняет проверку формата записи, и если она не соответствует формату, возбуждает исключение EInvalidEventLogRecord.
Свойства: property RecPtr : PEVENTLOGRECORD read FRecPtr write FRecPtr; Предназначено для задания указателя на структуру EVENTLOGRECORD, данные которой нужно прочитать. property LogName : string read FLogName write FLogName; Предназначено для задания имени журнала. Класс TEventLog
Методы: procedure Open; Открывает журнал событий, заданный свойством LogName. procedure OpenBackup( const BackupName : string ); Открывает резервную копию журнала. procedure Close; Закрывает журнал событий procedure Clear( const BackupName : string = '' ); Очищает журнал событий. Журнал должен быть открыт. Если задан параметр BackupName, то создается резервная копия журнала. procedure Backup( const BackupName : string ); Создает резервную копию журнала. function CreateIterator( Direction : TLogIterateDirection=idBackward) : TEventLogIterator; Создает и возвращает итератор, связанный с данным экземпляром класса TEventLog.
Свойства:
property Active : boolean read GetActive write SetActive; Показывает открыт или закрыт журнал. property Count : DWORD read GetCount; Количество записей в журнале property Handle : THandle read FHandle; Описатель открытого журнала. property LogName : string read FLogName write SetLogName; Задает имя журнала. property OldestRecord : DWORD read GetOldestRecord; Номер самой старой записи в журнале. property RegKey : string read GetRegKey; Имя корневого ключа реестра для выбранного журнала. Класс TEventLogIterator Предназначен для перемещения по открытому журналу сообщений, предоставляет доступ к текущей записи.
Методы:
constructor Create( AEventLog : TEventLog; ADirection : TLogIterateDirection ); AEventLog - Экземпляр класса TEventLog, для которого создается итератор ADirection - задает направление прохода по журналу. function IsEmpty : boolean; Возвращает true если журнал пуст. procedure Reset; Сбрасывает текущее состояние итератора и переходит, в зависимости от заданного направления обхода, к первой или последней записи в журнале. Вызов Reset необходим, например, в том случае, если было изменено имя журнала в соответствующем экземпляре класса TEventLog. function IsDone : boolean; function Next : boolean; Переход к следующей записи в журнале в соответствие с выбранным направлением обхода. function Seek( Number : DWORD ) : boolean; Переход к записи журнала с заданным номером.
свойства:
property Current : TEventLogRecordDecoder read GetCurrent; Дает доступ к текущей записи журнала. property EventLog : TEventLog read FEventLog write SetEventLog; Экземпляр класса TEventLog, с которой связан с данным экземпляром итератора. property Direction : TLogIterateDirection read FDirection write SetDirection; Направление, в котором идет перемещение по журналу при вызове Next.
Примечание: при написании использовались модули из библиотеки Jedi WinAPI Library (JWA). Библиотеку можно найти здесь
Скачать пример, иллюстрирующий работу классов: (10.7K)
Ссылки по теме:
Наследник TComboBox, показывающий Hint для строки в ListBox'овой части, не видимой целиком.
| Раздел Сокровищница | рь Шевченко, дата публикации 17 апреля 2002г. |
Ограничения: Компонент проверялся при работе с значением Style: csDropDown, csDropDownList. при остальных значениях работа не гарантируется :-) Компонент не тестировался в режиме design-time.
История изменений: 16.04.2002 Исправлено поведение при закрытии, когда показан hint и ComboBox закрывается по клавише Enter, Escape или F4. Теперь hint убирается. Добавлено свойство HorizontalExtent, позволяющее устанавливать горизонтальный Scrollbar в списке ComboBox'a. По умолчанию свойство имеет значение -1, что запрещает установку горизонтального ScrollBar'а. | |
Скачать компонент (5 K)
Настройка DCOM при помощи DCOMCNFG.EXE
Закладка DefaultProperties | Элемент | Значение | 9x | NT | | Enable Distributed COM on this computer | True | + | + | | Enable COM Internet Services on this computer | Не имеет значения | - | + | | Default Authentication Level | None | + | + | | Default Impersonation Level | Impersonate | + | + | | Provide additional security for reference tracking | False | + | + |
Закладка DefaultSecurity | Элемент | Значение | 9x | NT | | Enable remote connection | True | + | - | | Default access permissions | Everyone = allow access | - | + | | Default access permissions | The world = grant access | + | - | | Default launch permissions | Everyone = allow launch | - | + |
Закладка Applications Позиционироваться на сервер.exe. Кнопка "Properties..." Закладка Location | Элемент | Значение | 9x | NT | | Run application on the computer where the data is located | False | + | + | | Run application on this computer | True | + | + | | Run application on the following computer | False | + | + |
Закладка Security | Элемент | Значение | 9x | NT | | Use default access permissions | True | + | + | | Use default launch permissions | True | - | + | | Use custom configuration permissions | False | - | + |
Закладка Identity | Элемент | Значение | 9x | NT | | Which user account do you want to use to run this application | The interactive user | - | + |
Небольшое оступление.
В принципе, написать что-либо не так сложно, если имеешь в голове какую-то идею и если подойти к реализации этой идеи с верной стороны, сложно начинать писать с нуля. Весь пыл растрачивается еще на подступах - в процессе написания стартовой площадки. Сама идея с написанием своего репорта, в общем-то, появилась легко: Нужен готовый компонент с окном предварительного просмотра с минимальным набором функций (в идеале умеющий только переключаться между просмотром/печатью и поддерживающий режим масштабирования изображения на "листе"). Нужен буфер, куда_будут_писаться/откуда_будут_считываться все объекты печати (линии, прямоугольники, текст, картинки...). Необходимы свобода действий и творчества. Желательна легкость расширения функций. Итааак, цель ясна, желание есть (а это самое главное) - зарываемся в архивы в поисках той самой стартовой площадки... И понимаем, что в следующий раз, создавая архивы, надо присваивать им (архивам) более конкретные имена, потому что память наотрез отказывается помнить все сокращения в именах файлов, назавая все это бессмысленным набором букв. Пролистывая архивы с сокращениями типа "Rpt" и "Rep", натолкнулся на некий "PrnSvr", в комментариях которого обнаружил следующее: "Компонент предназначен для реализации всех функций, связанных с выводом на печать: выбор принтера, его настройка, предварительный просмотр и собственно печать." - ну вы поняли, да? - на блюдечке с голубой каемочкой. Остальное дело техники: берем его за основу, зачищаем; берем идею, набиваем ее на клавиатуре; привинчиваем к основе; красим и смотрим, что получилось - в общем получилось примерно то, что и задумывал. Слово за Вами, господа. Если не понравится удалим из королевства (буду сам пользоваться), если понравится - оставим. На ошибки и дополнения постараюсь отреагировать.
Несколько функций для работы со списками
Добавление группы в список TListView procedure AddToListView(LV: TListView; Par: array of string); var NI : TListItem; i: integer; begin NI := LV.Items.Add; NI.Caption := Par[Low(Par)]; for i := 1 to (High(Par) - Low(Par)) do NI.SubItems.Append(Par[i]); end;
Удаление элемента из листа TList. Достаточно универсальна, хотя требуется не так часто. procedure RemovePtrFromList(L: TList; P: pointer); var i: integer; begin if not Assigned(L) then Exit; i := L.IndexOf(P); if i >= 0 then L.Delete(i); end;
Добавить объект в список, если такого еще нет. procedure IncludePtrToList(L: TList; P: pointer); var i: integer; begin if not Assigned(L) then Exit; i := L.IndexOf(P); if i < 0 then L.Add(P); end;
Алексей Еремеев
Но к делу
Взявшись оформлять этот пример для общественности, я понял, что меняются не только времена и люди, но и исходники лежащие в архиве. Да их не узнать! Да неужели это писал я? Да... точно... странно... Но ведь он все еще работает! Вдвойне странно... Так что если что - сильно не ругаться - я был молодой и временами делал некрасивости. Старинный закон гласит: последняя ошибка программы выявляется через 7 лет эксплуатации. Если вы заметили ошибку, которой не заметил я - то буду благодарен, если вы мне о ней напишите. Я, пожалуй, не буду следовать примеру Д. Кнута и высылать деньги за замеченные ошибки, но спасибо скажу :).
О компоненте TListView
В этом компоненте есть один недостаток - нет обычный средств для того, чтобы определить редактируется ли в данный момент один из пунктов этого компонента. Например в компоненте TStringGrid есть для этого свойство EditorMode. А тут вообще ничего нету. Поэтому, кстати, в прошлой версии программы производилась очень кривая проверка на этот счет. Я все-таки нашел как это можно узнать. Естественно на помощь приходит Api Windows. Там есть такая функция: ListView_GetEditControl, в качестве параметра которой нужно указать дескриптор окна TListView. Эта функция возвращает дескриптор компонента, в котором происходит редактирование пункта компонента TListView. Если же компонент не редактируется, то она возвращает ноль. Но это все описывается в справочной системе. Плюс там еще есть куча полезный функций этого же типа с соответствующими сообщениями, которые возможно будут полезны. Но большая их часть, конечно, реализована в компоненте TListView обычными дельфивскими методами или свойствами. Я на всякий случай приведу их полный список: LVM_ARRANGE ListView_Arrange LVM_CREATEDRAGIMAGE ListView_CreateDragImage LVM_DELETEALLITEMS ListView_DeleteAllItems LVM_DELETECOLUMN ListView_DeleteColumn LVM_DELETEITEM ListView_DeleteItem LVM_EDITLABEL ListView_EditLabel LVM_ENSUREVISIBLE ListView_EnsureVisible LVM_FINDITEM ListView_FindItem LVM_GETBKCOLOR ListView_GetBkColor LVM_GETCALLBACKMASK ListView_GetCallbackMask LVM_GETCOLUMN ListView_GetColumn LVM_GETCOLUMNWIDTH ListView_GetColumnWidth LVM_GETCOUNTPERPAGE ListView_GetCountPerPage LVM_GETEDITCONTROL ListView_GetEditControl LVM_GETIMAGELIST ListView_GetImageList LVM_GETISEARCHSTRING ListView_GetISearchString LVM_GETITEM ListView_GetItem LVM_GETITEMCOUNT ListView_GetItemCount LVM_GETITEMPOSITION ListView_GetItemPosition LVM_GETITEMRECT ListView_GetItemRect LVM_GETITEMSPACING ListView_GetItemSpacing LVM_GETITEMSTATE ListView_GetItemState LVM_GETITEMTEXT ListView_GetItemText LVM_GETNEXTITEM ListView_GetNextItem LVM_GETORIGIN ListView_GetOrigin LVM_GETSELECTEDCOUNT ListView_GetSelectedCount LVM_GETSTRINGWIDTH ListView_GetStringWidth LVM_GETTEXTBKCOLOR ListView_GetTextBkColor LVM_GETTEXTCOLOR ListView_GetTextColor LVM_GETTOPINDEX ListView_GetTopIndex LVM_GETVIEWRECT ListView_GetViewRect LVM_HITTEST ListView_HitTest LVM_INSERTCOLUMN ListView_InsertColumn LVM_INSERTITEM ListView_InsertItem LVM_REDRAWITEMS ListView_RedrawItems LVM_SCROLL ListView_Scroll LVM_SETBKCOLOR ListView_SetBkColor LVM_SETCALLBACKMASK ListView_SetCallbackMask LVM_SETCOLUMN ListView_SetColumn LVM_SETCOLUMNWIDTH ListView_SetColumnWidth LVM_SETIMAGELIST ListView_SetImageList LVM_SETITEM ListView_SetItem LVM_SETITEMCOUNT ListView_SetItemCount LVM_SETITEMPOSITION ListView_SetItemPosition LVM_SETITEMPOSITION32 ListView_SetItemPosition32 LVM_SETITEMSTATE ListView_SetItemState LVM_SETITEMTEXT ListView_SetItemText LVM_SETTEXTBKCOLOR ListView_SetTextBkColor LVM_SETTEXTCOLOR ListView_SetTextColor LVM_SORTITEMS ListView_SortItems LVM_UPDATE ListView_Update
Скачать: 97K 4K 1K
О назначении пользовательского TNotifyEvent
| Раздел Сокровищница | ний Колеватов, дата публикации 14 мая 2002г. |
Динамическое назначение вашей процедуры на событие, может быть полезно при динамическом создании компонентов или создании плагинов живущих в dll
Все просто стоит только обратить внимание что определение TNotifyEvent = procedure(Sender: TObject) of object; а сие значит что These types represent method pointers. A method pointer is really a pair of pointers; the first stores the address of a method, and the second stores a reference to the object the method belongs to. Given the declarations (Delphi help :)) главное не забыть что при обьявлении процедуры надо указать пару указателей procedure MyProcOnClick(P1,P2 :pointer); begin if P2<>nil then Showmessage(TComponent(P2).Name); end; procedure TForm1.Button1Click(Sender: TObject); begin @Button2.OnClick := @MyProcOnClick; end; Вот и все теперь при нажатии на кнопку два выполниться процедура MyProcOnClick, P2 указывает на обьект Button2
Аналогичная проблема может быть решена
2. Аналогичная проблема может быть решена для StringGrid. Привожу пример обработчика события OnDrawCell. Он выводит нестандартно во все ячейки, а ячейку, координаты которой совпадают с внешними параметрами SgKritCol, SgKritRow, красит желтым цветом. Пример фрагмента сетки StringGrid показан на Рисунок 2. | procedure TfAg.SgKritDrawCell(Sender: TObject; ACol, ARow: Integer; Rect: TRect; State: TGridDrawState); var r: TRect; begin With Sender as TStringGrid do begin With Canvas do begin if (SgKritCol = ACol) and (SgKritRow = ARow) then Brush.Color:= clYellow else Brush.Color := clWhite; Font.Color:= clBlack; FillRect(Rect); end; r:= KdnRect(Rect,0,4,-3,0); DrawText(Canvas.Handle, PChar(SgKrit.Cells[ACol, ARow]), Length(SgKrit.Cells[ACol, ARow]),r, DrawTextBiDiModeFlags(DT_RIGHT)); end; end; |
Рисунок 2. Фрагмент StringGrid с нестандартной закраской ячейки и форматированием ее содержимого по правому краю и по высоте ячейки
Коднянко Владимир Красноярск, 17.05.2002
Смотрите по теме: Компонент TStringGrid - назначение цвета для каждой строки, вывод содержимого ячейки в несколько строк
О нестандартном выводе в DBGrid и StringGrid
1. Во многих FAQ-ах и книгах по Delphi приходилось видеть процедуры нестандартной закраски отдельных ячеек DBGrid. Однако при их исполнении текст отформатирован по левому краю и располагается по высоте ячейки не так, как в ячейках стандартного вывода. Ниже привожу пример обработчика события OnDrawColumnCell для сетки gAg: TDBGrid, где эти проблемы сняты для случая форматирования по правому краю:
| procedure TfAg.gAgDrawColumnCell(Sender: TObject; const Rect: TRect; DataCol: Integer; Column: TColumn; State: TGridDrawState); var r: TRect; begin With gAg.Canvas do begin if not (gdFocused in State) and (Column.Field.AsString = '-1') and (Column.FieldName = 'PointN') then begin Brush.Color:= clRed; // цвет подложки - красный Font.Color:= clWhite; // цвет символов - белый FillRect(Rect); // закраска всей ячейки With Sender as TDBGrid do begin r:= KdnRect(Rect,0,2,-3,0); //уменьшенный и смещенный Rect //нестандартный вывод DrawText(Canvas.Handle, PChar(Column.Field.Text), Length(Column.Field.Text),r, DrawTextBiDiModeFlags(DT_RIGHT)); end; end else // стандартный вывод gAg.DefaultDrawColumnCell(Rect, DataCol, Column, State); end; end; | На Рисунок 1 приведен фрагмент сетки, сформированной этим обработчиком.
Рисунок 1. Фрагмент DBGrid с нестандартной закраской отдельных ячеек и форматированием их содержимого по правому краю и по высоте ячейки
Процедура выводит нестандартно только значения -1 в ячейки столбеца PointN сетки fAg. Суть состоит в использовании уменьшенного Rect-а, сформированного по базовому Rect с помощью функции KdnRect (текст приведен ниже) с последующей подгонкой уменьшенного Rect-а под формат стандартного вывода и рисованием содержимого ячейки по нужному формату выравнивания. Так, уменьшенный r смещен по отношению к Rect вниз на 2 (2), правая граница смещена влево на 3 (-3). Константа DT_RIGHT указывает на способ форматирования. Для того, чтобы вывести текст в центре ячейки следует константу DT_RIGHT заменить на DT_CENTER. При этом оператор формирования r лучше заменить на r:= KdnRect(Rect,0,2,0,0) с целью использования всей ширины ячейки.
| function KdnRect(Rect: TRect; DLeft,DTop,DRight,DBottom: Integer): TRect; begin With Result do begin Left:= Rect.Left + DLeft; Top:= Rect.Top + DTop; Right:= Rect.Right + DRight; Bottom:= Rect.Bottom + DBottom; end; end; |
Обмен текстовой информацией между модулями проекта
й Перовский, дата публикации 20 февраля 2003г. |
При написании программ я всегда старался четко отделять пользовательский интерфейс от алгоритма задачи. С появленинием в Delphi ActionList'а стало гораздо проще писать алгоритмическую часть без оглядки на структуру пользовательского интерфейса.
Но осталась проблема отображения информации: в некоторой точке программы требуется вывести сообщение, а куда выводить неизвестно. Разработчик интерфейса еще не решил, что выводить в StatusBar, что в MessageBox. Какую отладочную информацию поместить в логфайл. Я решаю эту проблему следующим образом — во всех существенных точках алгоритма вызывается процедура ToLog. Два ее параметра определяют текст сообщения и ее предназначение. 256 "каналов" должны быть поделены между сообщениями об ошибках, отладочной информацией, информационными сообщениями и результатами. Это единственное соглашение, которое должны соблюдать все участники проекта. Разработчик интерфейса решает для компонента - сообщения из каких каналов он должен отображать и регистрирует соответствующий объект (Log). "Расчетные" модули могут не ссылаться на модули с описанием форм и диалогов. Единственный модуль, через который они общаются - и на который обязаны ссылаться (причем в implementation разделе) это uLogs. Модуль uLogs предназначен для передачи текстовых сообщений между различными модулями, визуальными компонентами и файлами. Для разделения сообщений различных типов введено 256 "каналов". Каждому сообщению должен быть сопоставлен номер канала, определяющий цель сообщения и технологию его обработки. Для протоколирования и/или визуализации сообщений предназначены специальные объекты "Логи" - наследники базового объекта TLog. Каждый "лог" может обрабатывать все сообщения из заданного множества каналов. Логи разных типов различаются способом фиксации или отображения информации. В модуле описано 5 различных наследников абстрактного класса TLog. По их образцу легко создать классы для других способов обработки сообщений.
Обработка сообщений от мыши потомками собственного компонента
Проблема: имеем свой собственный компонент, который может содержать несколько объектов с собственным внешним видом, каждый из которых должен реагировать на перемещение мыши. Например -- подсвечиваться. Для гуру: ничего интересного вы здесь не найдёте, примерчик это не более, чем пропаганда использования стандартного оконного механизма в противовес различным самоизобретённым велосипедам. Классы: класс TMyControl -- основной компонент; TMySubControl -- класс того объекта, который будет лежать на TMyControl и подсвечиваться.
Наследование от TGraphicControl необязательно. Фактически, можно выбирать из четырёх вариантов: TControl базовый класс всех элементов управления, не имеет виндовского Handle(дескриптора) окна, т.е. данный элемент Windows не считает окном; вся реализация сообщений, отрисовки и пр. выполняется в VCL; (+) -- меньше кушает ресурсов, (-) -- см. TWinControl TGraphicControl то же, что и TControl, но имеет свойство Canvas, при помощи которого удобно рисовать и метод Paint, в котором надо рисовать TWinControl это полноценное Windows-окно со всеми преимуществами перед TControl: (а) может получать фокус ввода, (б) может содержать "детей" -- другие окна на своей поверхности, (в) -- имеет дескриптор, св-во Handle TCustomControl наследник TWinControl, отличия между ними те же, что и между TControl и TGraphicControl Выбран TGraphicControl по причине отсутствия "детей" и наличия Canvas.
Данные, составляющие компонент: FItem: TCollectionItem входит в какую-либо коллекцию и, собственно, содержат смысловое наполнение элемента. Я встречал вариант, когда у TMyControl не определялись "дети", а в качестве реакции на WM_PAINT перебирались элементы некоторой коллекции, которые кроме смысловых данных хранили свой контур, координаты и пр. и ручками всё это рисовалось... Жуть! Собственно, мой пример -- антиреклама описанного подхода
Скачать файл (3K) unit Unit1; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, StdCtrls, Buttons, ComCtrls; type TMySubControl = class(TGraphicControl) private FSelected: Boolean; //флаг, отмечающий подсвеченность FItem: TCollectionItem; procedure SetMouseOver(Val: Boolean); procedure MsMove(var M: TWMMouseMove); message WM_MOUSEMOVE; { Реакция на перемещение мыши } protected procedure Paint(); override; //по этому сообщению надо перерисовывать public constructor Create(AOwner: TComponent); override; destructor Destroy(); override; property IsSelected: Boolean read FSelected write SetMouseOver; { Свойство, отмечащее факт "подсвеченности" } end; { "Главный" элемент управления. Собственную процедуру отрисовки я не определял, а "дети" есть. Поэтому -- TWinControl } TMyControl = class(TWinControl) private procedure MsMove(var M: TWMMouseMove); message WM_MOUSEMOVE; public constructor Create(AOwner: TComponent); override; end; { Класс основной формы. Ничего интересного } TMain = class(TForm) CloseButt: TBitBtn; Label1: TLabel; Label2: TLabel; procedure CloseWndExecute(Sender: TObject); procedure FormCreate(Sender: TObject); private public end; var Main: TMain; implementation {$R *.DFM} { По кнопочке "Закрыть" } procedure TMain.CloseWndExecute(Sender: TObject); begin Close(); end; { Создание элементов вручную. Главное: вызвать конструктор, задать размеры и положение, назначить "родителя". Поскольку пакеты не используются, то на автомате создать их не выйдет. } procedure TMain.FormCreate(Sender: TObject); var c: TMyControl; begin c := TMyControl.Create(Self); with c do begin SetBounds(8, 8, 240, 180); Color := clTeal; Parent := Self; //"родитель" -- формочка end; with TMySubControl.Create(Self) do begin SetBounds(3, 7, 49, 11); Parent := c; //у всех TMySubControl родитель -- TMyControl end; with TMySubControl.Create(Self) do begin SetBounds(140, 53, 94, 25); Parent := c; end; with TMySubControl.Create(Self) do begin SetBounds(38, 100, 88, 70); Parent := c; end; end; { Мониторинг перемещений мыши по основному control-у. Отметьте, что когда курсор над "детьми", control не получает данное сообщение. } procedure TMyControl.MsMove(var M: TWMMouseMove); begin inherited; Main.Label1.Caption := Format('%d:%d', [M.XPos, M.YPos]); end; { Добавляем стиль 3D-рамки. Её отрисовка производится стандартными средствами винды. } constructor TMyControl.Create(AOwner: TComponent); begin inherited; ControlStyle := ControlStyle + [csFramed]; end; { Перерисовка. Простой прямоугольник. Цвет -- стандартный или подсвеченный, в зависимости от IsSelected } procedure TMySubControl.Paint(); const a: array[Boolean] of TColor = (clWindow, clHighlight); begin inherited; Canvas.Brush.Color := a[IsSelected]; Canvas.FillRect(Canvas.ClipRect); with Canvas.ClipRect do //показываем -- какая именно часть перерисовывается Main.Label2.Caption := Format('(%d:%d) - (%d:%d)', [Left, Top, Right, Bottom]); end; { Смена значения свойства. Только один из TMySubControl может быть подсвеченным } procedure TMySubControl.SetMouseOver(Val: Boolean); var i: Integer; begin if Val <> FSelected then begin Invalidate(); //если изменилась подсветка, то надо перерисоваться if Val then //нас подсветили (Val = TRUE) for i := Parent.ControlCount - 1 downto 0 do //среди "братьев" ищем другие TMySubControl и снимаем им подсветку if (Parent.Controls[i] <> Self) and (Parent.Controls[i] is TMySubControl) then TMySubControl(Parent.Controls[i]).IsSelected := FALSE; FSelected := Val; end; end; procedure TMySubControl.MsMove(var M: TWMMouseMove); begin IsSelected := TRUE; //над нами переместили мышку -- значит подсветили end; constructor TMySubControl.Create(AOwner: TComponent); begin inherited; FItem := TCollectionItem.Create(nil {тут произвольный объект-коллекция, например его можно указать в параметрах конструктора}); end; destructor TMySubControl.Destroy(); begin FItem.Free(); inherited; end; end.
Общее
В прошлой статье я подробно описал программу, все ее возможности сохранились в полной мере и сейчас, появилось только несколько нововведений. Когда программа требует ввести какое-либо значение его можно вводить как угодно. То есть можно вводить любой сивол (в старой программе это тожно можно было сделать, но не во всех компонентах). Если он будет неправильным (нечисловой величиной), то она преобразуется в соответствующий Ansi код. В окне просмотра в компоненте ViewGrid в десятеричном режиме можно вводить любые тесковые символы, т.е. по сути писать текст, если надо. Плюс в этом же компоненте слева появился новый столбик в котором отображается адрес ближайшей к нему ячейки. Плюс некоторые мелкие доработки, как автоматическая фокусировка выбранной в ячейке в компоненте ViewGrid и т.д. Также в компонентах DataGrid и ListView (через точку с запятой) тоже можно также вводить произвольные значения. В форме просмотра появились компоненты типа TEdit в которых отображается текущая величина выбранно ячейки. Эти компоненты представляют собой числа величинами 8, 16, 32 бита в знаковом и беззнаковом вариантах. Чтобы изменить значения в ячейке, можно вводить данные прямо в соответствующий компонент типа TEdit.
Ограничения текущей версии
В ситуациях, когда входному выражению соответствует обратная польская запись вида: a b c d e f g h i + + + + + + + + , где число подряд идущих переменных больше восьми, а также в некоторых других неудачных случаях, модуль откажется генерировать код функции.
Это связано с использованием при вычислениях одного лишь стека сопроцессора для хранения промежуточных результатов, и будет исправлено в следующей версии. Кроме того, для нормальной работы придется отключить Tools/Debugger options/Language Exceptions/Stop on Delphi Exceptions, иначе будет довольно утомительно: при анализе исключения возникают десятками.
Описание программы "Репликатор"
Для начала, скачаем и установим программу "Репликатор", благо она распространяется свободно. Войдём в программу под именем и паролем администратора (SYSDBA). Входим в меню Репликация -> Генерация базы данных. В верхней таблице перечисляются проекты и пути к базам данных. В нижней таблице перечисляется последовательность копируемых таблиц. Копируются те таблицы, у которых установлена галочка в поле И (Используется). Поле ID должно быть уникальным во всех проектах. Если указана галочка Блокировать все триггера, то перед копированием информации будут отключены триггера, а после копирования - опять включены. То же относится к индексам и ограничениям. Если стоит галочка Выполнять скрипты, то при копировании будут выполняться скрипты, названия файлов которых перечислены в закладке Файлы скриптов. Если у Вас имя и пароль администратора не совпадает с SYSDBA, masterkey, то Вам понадобится указать имена и пароли в закладке Пароли.
Определение установленных версий .NET Framework в системе
Определение установленных версий .NET Framework в системе. Пример на Delphi.
| /// /// Enumerates all installed Common Language Runtime Engines. /// /// Zero-based index of looked runtime record. /// True if runtime with specified index found. function EnumInstalledRuntimes(Index: Integer; out VersionName: String): Boolean; var hkey: Windows.HKEY; hsubkey: Windows.HKEY; I: Cardinal; J: Cardinal; NameBuf: array[0..MAX_PATH] of Char; CNameBuf: Cardinal; lwt: TFileTime; vt: DWORD; AnyFound: Boolean; begin Result := False; VersionName := ''; if ERROR_SUCCESS = RegOpenKeyEx(HKEY_LOCAL_MACHINE, PChar('SOFTWARE\Microsoft\.NETFramework\policy'), 0, KEY_ENUMERATE_SUB_KEYS, hkey) then try I := 0; while True do begin AnyFound := False; CNameBuf := MAX_PATH + 1; if ERROR_SUCCESS <> RegEnumKeyEx(hkey, I, @NameBuf[0], CNameBuf,nil, nil, nil, @lwt) then begin Break; end; if (NameBuf[0] = 'v') and (NameBuf[1] in ['1'..'9']) then begin VersionName := String(NameBuf); if ERROR_SUCCESS = RegOpenKeyEx(hkey, @NameBuf[0], 0,KEY_QUERY_VALUE, hsubkey) then try J := 0; while true do begin CNameBuf := MAX_PATH + 1; if ERROR_SUCCESS <> RegEnumValue(hsubkey, J, @NameBuf[0],CNameBuf, nil, @vt, nil, nil) then begin Break; end; if (vt = REG_SZ) and (NameBuf[0] <> #0) then begin VersionName := VersionName + '.' + String(NameBuf); AnyFound := True; Break; end; Inc(J); end; finally RegCloseKey(hsubkey); end; end; Inc(I); if AnyFound then begin if Index = 0 then begin Result := True; Break; end; Dec(Index); end; end; finally RegCloseKey(hkey); end; end; |
| Для данного материала нет комментариев. |
Ошибка в процедуре _AddRefArray в Delphi 5 и ее исправление
Мотов, дата публикации 10 января 2003г. |
Эта ошибка была обнаружена и исправлена "за бугром" еще в 2000 г. Однако, когда в фидо возник вопрос по этому поводу, никто не привел метода решения этой проблемы. Эта ошибка исправлена в Delphi6, но так как многие еще продолжают использовать Delphi5, то в данном материале предлагается описание ошибки и метод ее исправления. В Delphi 5 есть ошибка в процедуре _AddRefArray в модуле System.pas. Если вы попробуете выполнить следующий код, то получите сообщение об ошибке: Invalid variant operation.
| procedure func(p: array of variant); begin if Length(p) > -1 then ShowMessage(p[0]); end; procedure TForm1.Button1Click(Sender: TObject); begin func([]); end | Дело в том, что компилятор Delphi автоматически вставляет в код процедуры func вызов _AddRefArray, а эта процедура не может корректно работать с пустым массивом. Исправить ошибку несложно, достаточно добавить проверку на количество элементов массива в процедуру _AddRefArray, которая находится в модуле system.pas. Исправленный текст _AddRefArray приведен ниже: | procedure _AddRefArray { p: Pointer; typeInfo: Pointer; elemCount: Longint}; asm { -> EAX pointer to data to be referenced } { EDX pointer to type info describing data } { ECX number of elements of that type } PUSH EBX PUSH ESI PUSH EDI TEST ECX,ECX JZ @@exit MOV EBX,EAX MOV ESI,EDX MOV EDI,ECX ... | Затем надо скомпилировать system.pas с отладочной информацией и без и заменить файлы Delphi5\lib\system.dcu и Delphi5\lib\Debug\system.dcu. Для этого я написал небольшой bat-файл, который надо поместить в каталог Delphi5\Source\Rtl и запустить его на выполнение. | del lib\system.dcu make copy lib\system.dcu ..\..\lib\system.dcu del lib\system.dcu make -DDEBUG copy lib\system.dcu ..\..\lib\Debug\system.dcu | Хочу заметить, что для компиляции требуется файл tasm32.exe, который не поставляется с Delphi. После выполнения этих действий ошибка будет устранена. Однако остается одна нерешенная проблема - в проекте нельзя использовать пакет времени выполнения vcl50.bpl. Если собрать проект с использованием пакетов, то будет использована функция не из исправленного модуля system.dcu а из пакета vcl50.dcu. Ситуация усугубляется тем, что модуль vcl50.bpl нельзя корректировать. Другой способ исправления _AddRefArray я нашел на , желающие могут обратиться по
Идея оказалась очень простой - раз нельзя исправить процедуру _AddRefArray в файле vcl50.bpl, значит ее нужно исправить в памяти программы во время работы. Ниже я привожу исходный текст, который я оставил практически без изменений:
| unit PatchAddRefArray; interface implementation uses Windows; var NewAddRefArray: Pointer; OldAddRefArray: Pointer; procedure _NewAddRefArray { p: Pointer; typeInfo: Pointer; elemCount: Longint}; asm { -> EAX pointer to data to be referenced } { EDX pointer to type info describing data } { ECX number of elements of that type } { проверка на количество элементов в массиве} TEST ECX, ECX JZ @exit { старый код затертый командой перехода} PUSH EBX PUSH ESI PUSH EDI MOV EBX,EAX MOV ESI,EDX { продолжить выполнение процедуры _AddRefArray} JMP OldAddRefArray @exit: end; type TJumpDWord = packed record OpCode: Word; Distance: Pointer; end; PJumpDWord = ^TJumpDWord; PPointer = ^Pointer; const // Несколько инструкций из AddRefArray: // PUSH EBX, PUSH ESI и т.д. COrigARACode = $89D689C389575653; // JMP CJmpCode = $25FF; procedure PatchAddRef; var Jmp: TJumpDWord; Addr: ^TJumpDWord; OldProtect: DWORD; begin {Получить адрес процедуры AddRefArray} asm mov eax, offset System.@AddRefArray mov Addr, eax end; {Переход к телу процедуры AddRefArray} while Addr^.OpCode = CJmpCode do Addr := PPointer(Addr^.Distance)^; {Сравнить начало процедуры AddRefArray с ее "сигнатурой" если совпадает, значит это та процедура, которую мы ищем} if PInt64(Addr)^ = COrigARACode then begin OldAddRefArray := Pointer(Integer(Addr) + SizeOf(TJumpDWord) + 1); NewAddRefArray := @_NewAddRefArray; Jmp.OpCode := CJmpCode; Jmp.Distance := @NewAddRefArray; VirtualProtect(Addr, SizeOf(TJumpDWord), PAGE_READWRITE, OldProtect); Addr^ := Jmp; VirtualProtect(Addr, SizeOf(TJumpDWord), OldProtect, OldProtect); end; end; initialization PatchAddRef; end. | Исходный текст очевиден и не требует дополнительных комментариев.
В заключение я бы хотел заметить, что везде где это возможно при описании входных параметров следует использовать параметр const. Это позволит сэкономить как вычислительные ресурсы так и память.
Олег Мотов январь 2003г. домашняя страница материала нет комментариев.
Отдельное спасибо
(да я знаю, что благодарности помещают в конце, но там их редко кто читает :)) так вот отдельное спасибо: Спасибо человеку, который сделал из меня программиста. Спасибо Королеве Елене Филипповой. Если вы здесь, то вы знаете за что.:) Эта программа написана в то время когда меня можно было легко "взять на "слабо"". Так вот спасибо тому кто меня подначил на ее написание :)
Открытие файлов DFM версий 5 и 6 в младших версиях
Появление новых версий Delphi затруднило жизнь (программистскую) тех, кто остался верен версии 4: она не может открыть проект, созданный ее потомками.
Причина проста - файлы формы сохранили расширение (DFM), но принципиально изменили содержание: в версии 4 они имели двоичный формат, в следующих версиях - стали текстовыми. Однако разработчики отказались от использования стандартного для таких файлов расширения TXT (видимо, чтобы народ не пытался работать с ними в обычных текстовых редакторах - может плохо кончиться), и новое расширение ввести не решились (DFT, например). В результате открытие проекта, созданного под версией 5-6, в версии 4 сопровождается сообщением об ошибке создания формы из-за неверного формата потока - и форма не создается. Соответственно проект невозможно использовать.
Выход, однако, есть, и даже два. Один заложен в самой Delphi: в папке BIN есть утилита convert.exe, которая как раз и занимается переводом файла формы из двоичного формата (с расширением DFM) в текстовый (с расширением TXT) и обратно. Единственная тонкость - нужно вручную сменить расширение файла формы из проекта версий 5-6: вместо DFM должно стать TXT. Дальше утилита легко создаст файл формы в родном формате с нужным расширением.
Второй вариант (если менять расширение не хочется) - воспользоваться специальными утилитами: Утилита dfmconv.exe от Markus Stephany из Германии (, архив dfmconv.zip объемом 61 Кб содержит также исходные тексты утилиты): она бесплатно (по лицензии GNU) сделает все преобразования, причем исходный файл тоже сохранит. Утилита D4toD5 c сайта (zip-архив exe-файла утилиты объемом 240 К). Преобразует текстовое представление форм (DFM-файлы Delphi 5) в двоичное (Delphi 4) в указанном каталоге. Исходные тексты не предоставляются.
Таким образом, притормозившие на версии Delphi-4 вполне могут изучать самые современные проекты. Простая смена формата - вовсе не повод для огорчений или спешной закупки новой версии Delphi. Конечно, в версиях 5 и 6 огромное количество преимуществ и достоинств, но если версия 4 справляется со всеми вашими задачами - не надо тратить деньги.
Отображение длинных строк при движении мыши по списку для нескольких TListBox.
| Раздел Сокровищница | анович Олег, дата публикации 19 марта 2002г. |
При движении по списку TListBox содержимое каждой строки показывается с помощью Hint-а. Код поддерживает обработку нескольких TListBox на форме. {Вставляем в раздел public вашей формы:} procedure ShowHint (var HintStr: string; var CanShow: Boolean; var HintInfo: THintInfo); {Вставляем где нибудь после implementation:} procedure TForm1.ShowHint (var HintStr: string; var CanShow: Boolean; var HintInfo: THintInfo); var ListRect,a: TRect; begin with HintInfo do begin {Здесь необходимо указать все ListBox на вашей форме, например в этом случае это ListBox1 и ListBox2} if (HintControl = ListBox1)or(HintControl = ListBox2) then with HintControl as TListBox do begin if (ItemAtPos(CursorPos,true)<>-1)and (Canvas.TextWidth(items.Strings[ItemAtPos(CursorPos,true)]) > ItemRect(ItemAtPos(CursorPos,true)).Right-2)then begin HintStr := items.Strings[ItemAtPos(CursorPos,true)]; ListRect := ClientRect; ListRect.Top := ListRect.Top + (ItemAtPos(CursorPos,true)-TopIndex)*ItemHeight; ListRect.Bottom := ListRect.Top + ItemHeight; CursorRect := ListRect; GetWindowRect(Handle,a); HintInfo.HintPos:=Point(ListRect.Left+a.Left+1,ListRect.Top+a.Top-1); end; end; end; end; {В обработчике FormShow формы прописываем:} Application.OnShowHint := ShowHint; Application.HintHidePause:=5000; //Время которое будет держаться Hint. Application.HintPause:=300; //Время перед появлением Hint'а. {В обработчике FormHide формы прописываем:} Application.HintHidePause:=2500; Application.HintPause:=500; Во всех ListBox'ах устанавливаем свойсто ShowHint в True
Агранович Олег Смотрите по этой теме:
Отправка SMS на мобильные телефоны МТС
Я долго мучался над этой проблемой, и наконец нашёл оптимальное ДЛЯ СЕБЯ решение: набрасываем NMHTTP nmhttp1.HeaderInfo.Referer:='www.mts.ru'; nmHTTP1.Get('http://www.mts.ru/sms/sent.html?Posted=1&To=ПОЛНЫЙ НОМЕР ТЕЛЕФОНА&Msg=СООБЩЕНИЕ&count=ДЛИНА СООБЩЕНИЯ БЕЗ ПРОБЕЛОВ&SMSHour=1&SMSMinute=16&SMSDay=12&SMSMonth=11&SMSYear=2001'); Буду рад, если это кому поможет.
Парсер комбинированных выражений
| Раздел Сокровищница | рь Серебренников, дата публикации 22 января 2002г. |
Долго искал парсер и компилятор комбинированных выражений, но так и не нашел - только математика. Пришлось сделать самому.
Парсер вычисляет любые выраженя, состоящие из констант, функций и знаков действий (операций) между ними. Костанты четырех типов - целочисленные, вещественные, строки и логические. Операции - какие душе угодно, функции - тоже. Результатом вычислений является запись - упрощенный аналог типа Variant (нужно было для переноса на C++).
Примеры выражений: 2**2+4 "Pi is "+3.14 'The bool expression is ' + iif(2>3 && !('A' < 'B'), "True", "False") "Html YELLOW is ""#" + Hex(0xff 1. Парсер работает с четырьмя типами выражений - число double (123.456) - число integer (123, 0xff) - булево true/false - строка ( "a string", 'a string', " a ""string""") 2. Парсер допускает вызов функций Опрерации, приоритеты Op U/B Pr Comment - U Изменение знака числа + B Сложение чисел, конкатенация строк - B Вычитание одного числа из другого * B Перемножение чисел / B Деление чисел ** B Возведение в степень % B Остаток от деления ~ U Побитная инверсия целого числа | B or двух целых чисел & B and двух целых чисел ^ B xor двух целых чисел >> B побитный сдвиг вправо B == B != B = B
Скачать исходные коды: (11K)
Перехват меню IE ( TWebBrowser ) и подмена его собственным PopupMenu
| Раздел Сокровищница | рь Серебренников , дата публикации 29 августа 2001г. |
После ответа на вопрос КС о блокировании контекстного меню IE (), получил кучку писем с просьбой выслать модуль, который это делает. Думаю, такая информация полезна для народа.
Модуль просто подключается к проекту. После этого все меню (TWebBrowser.PopupMenu) начинают работать нормально.
unit WbPopup; interface // Для преобразования кликов правой кнопкой в клики левой, раскомментировать // {$DEFINE __R_TO_L} implementation uses Windows,Controls,Messages,ShDocVw; var HMouseHook:THandle; function MouseProc( nCode: Integer; // hook code WP: wParam; // message identifier LP: lParam // mouse coordinates ):Integer;stdcall; var MHS:TMOUSEHOOKSTRUCT; WC:TWinControl; {$ifdef __R_TO_L} P:TPoint; {$endif} begin Result:=CallNextHookEx(HMouseHook,nCode,WP,LP); if nCode=HC_ACTION then begin MHS:=PMOUSEHOOKSTRUCT(LP)^; if ((WP=WM_RBUTTONDOWN) or (WP=WM_RBUTTONUP)) then begin WC:=FindVCLWindow(MHS.pt); if (WC is TWebBrowser) then begin Result:=1; {$ifdef __R_TO_L} P:=WC.ScreenToClient(MHS.pt); if WP=WM_RBUTTONDOWN then PostMessage(MHS.hwnd,WM_LBUTTONDOWN,0,P.x + P.y shl 16); if WP=WM_RBUTTONUP then PostMessage(MHS.hwnd,WM_LBUTTONUP,0,P.x + P.y shl 16); {$endif} if (TWebBrowser(WC).PopupMenu<>nil) and (WP=WM_RBUTTONUP) then begin TWebBrowser(WC).PopupMenu.PopupComponent:=WC; TWebBrowser(WC).PopupMenu.Popup(MHS.pt.x,MHS.pt.y); end; end; end; end; end; initialization HMouseHook:=SetWindowsHookEx(WH_MOUSE,@MouseProc,HInstance,GetCurrentThreadID); finalization CloseHandle(HMouseHook); end.
Переименование группы файлов
Приложение является доработкой примера из поставки Дельфи "X:\Program Files\Borland\Delphi6\Demos\ActiveX\ShellExt\" Потребность в данной программе возникла при необходимости переименовать группу выделенных файлов, с чем она прекрасно справляется. Пример позволяете переименовывать группу файлов в проводнике Windows 95/98/ME. Поддерживается шаблонная операция [*] (звездочка). Приложение интегрируется в оболочку проводника и добавляет команду в контекстное меню. Поддерживает английский и русский язык автоматически (как мне кажется :-)). Примеры шаблонов: 1. A.JPG - все выделенные файлы примут имя A.JPG, A1.JPG, A2.JPG и т.д. 2. T*.BMP - файлы примут имя с буквы T, а далее будет добавлено исходное имя файла. 3. D.* - расширение файла останется исходным, имена как (1). Тоже самое относится к расширению файла.
Вы можете доработать алгоритм шаблонной операции, чтобы сделать его более развернутым. Все основные операции по переименованию в следующих методах (файл ZRFile.pas): procedure TContextMenu.RenameFiles; function TContextMenu.RenameTemplate(strTemplate, strName: String): String;
Скачать проект: (200 K)
Перенаправление вывода консольной программы
й, дата публикации 02 июня 2003г. |
Понадобилось мне отобразить работу консольной программы в каком-нибудь Memo, а саму консоль не показывать. Поискал в инете - много кто ищет, мало кто предлагает готовые решения. Понял только, что плясать надо с "пайпами". Взял свой парадный бубен и... Вовремя подвернулась хорошая статья в тему на КД: рбань С.В. Но мне не нужен целый класс! Да и собственные наработки уже появились. Вообщем, не буду утомлять процессом поисков и метаний, просто скажу что получилось. А получилась следующая функция:
| function RunAny(CommandLine: string; Str: TStrings): boolean; var I: byte; S: string; Flag: boolean; tRead, cWrite, dwRead, dwAvail: cardinal; SA: TSecurityAttributes; PI: TProcessInformation; SI: TStartupInfo; begin Result:=False; SA.nLength:=SizeOf(SECURITY_ATTRIBUTES); SA.bInheritHandle:=True; SA.lpSecurityDescriptor:=nil; if not CreatePipe(tRead, cWrite, @SA, 0) then Exit; ZeroMemory(@SI, SizeOf(TStartupInfo)); SI.cb:=SizeOf(TStartupInfo); SI.dwFlags:=STARTF_USESTDHANDLES or STARTF_USESHOWWINDOW; SI.wShowWindow:=SW_HIDE; SI.hStdOutput:=cWrite; if CreateProcess(nil, PChar(CommandLine), nil, nil, True, 0, nil, nil, SI, PI) then begin CloseHandle(PI.hProcess); CloseHandle(PI.hThread); Str.Clear(); Flag:=True; while Flag do begin for I:=0 to 9 do begin PeekNamedPipe(tRead, nil, 0, nil, @dwAvail, nil); if (dwAvail>0) then begin Flag:=True; Break; end else Flag:=False; Sleep(100); end; //for I:= if dwAvail>0 then begin SetLength(S, dwAvail); ReadFile(tRead, PChar(S)^, Length(S), dwRead, Nil); OemToChar(PChar(S), PChar(S)); Str.Add(S); Application.ProcessMessages; Result:=True; end; // if dwAvail end; // while Flag end; // if CreateProcess end; | Вот. Может кому пригодится. Естественно пока не причесано, но спешу поделиться :-)
P.S. с format. Да, действительно, такая проблема существует. Под win98SE у меня так и не получилось с тем же format''ом и рядом архиваторов, таких как RAR 2.0 и ARJ 2.50. Однако, под WIN200 PROF RUS все решилось небольшим изменением: CommandLine:=''cmd.exe /c ''+CommandLine - и телемаркет! Работает даже с bat-файлом.
Перевод "короткого" имени файла (short filename) в "длинное" (long filename)
Столкнулся с необходимостью перевода "короткого" имени файла (short filename) в "длинное" (long filename). Дело в том что существующая функция Win32 API GetLongFilename не поддерживается в Windows 95(r) и в Delphi по этой же причине не инкапсулирована. Предлагаю свой вариант функии. Функция работает как с сетвыми, так и с локальными именами. Вход: short(string) filename, выход: long filename(string) или пустая строка(string), в случае некоректного имени файла. Текст оной прилагаю ниже. uses Windows, SysUtils{для функции FileExists()}; function GetLongFileName(InputName: string): string; var Root, Net: Boolean; InPath, CurP, BegP: PChar; CurItem, CurPath, OutPath: string; RootGuard: SmallInt; FindHandle: Cardinal; FindData: WIN32_FIND_DATA; begin if not FileExists(InputName) then begin Result:= ''; Exit; end;{if not FileExists(InputName) then} OutPath:= InputName; InPath:= PChar(InputName); Root:= True; Net:= False; RootGuard:= 0; CurP:= InPath; while CurP^<>#0 do begin BegP:= CurP; while (CurP^<>'\') and (CurP^<>#0) do CurP := CharNext(CurP); SetString(CurItem, BegP, CurP - BegP); if CurItem='' then CurPath:= CurPath+'\' else begin CurPath:= CurPath+CurItem; if Root then begin OutPath:= CurPath; CurPath:= CurPath+'\'; end;{if Root then} end;{if CurItem='' then CurPath:= CurPath+'\' else} if (CurPath='\\') or (CurPath='\') then Net:= True; if Root then begin if Net then begin RootGuard:= -1; Net:= False; end;{if Net then} Inc(RootGuard); if RootGuard>0 then Root:= False; end{if Root then} else begin FindHandle:= FindFirstFile(PChar(CurPath), FindData); OutPath:= OutPath+'\'+FindData.cFileName; Windows.FindClose(FindHandle); CurPath:= CurPath+'\'; end;{if Root then ... else} CurP := CharNext(CurP); end;{while CurP^ <> #0 do} Result:= OutPath; end;{GetLongFileName}
По мотивам обсуждения :
Не столько в качестве "ответов", сколько в качестве самостоятельного дополнения и пояснения к статье. >>Еще куча всяких "бонусов", просто лень описывать >Никаких других бонусов не наблюдаю. Ну, что же, перечислю...
Первоначальный вариант Игоря Василенко | with TMyDlg.Create(nil) do try if execute then ... begin end; finally free; end; | Мой вариант
| ShowMessage('Вы ввели '+InputString('Начальное значение')); | Другие Бонусы: Лаконичность вызова. Краткая форма - "SomeVar:=InputString;" - что, согласитесь, гораздо лаконичнее... Если вам нужен ввод данных в одном-двух местах программы, это особого значения не имеет, но в случае, когда таких мест в программе 100-150... Экономия 8 строк может показаться весьма ощутимой... ДО показа формы определяются начальные значения контролов. Поверьте, иногда это ОЧЕНЬ важно! Возможна работа практически с любым кол-вом и качеством данных. Видимо не все понимают как это делается. Поясню: например надо передать 10 строковых переменных (поля какой-нить формы). Делаем вот так: (пример переехал выше. в первую часть письма :-))
> К тому же проверка правильности введенного значения будет производится, > как я понял на основе исходников, после выхода из формы диалога. > Я предпочитаю делать это до, чтобы дать пользователю возможность исправить > ошибку без повторного открытия диалога. Итак. Оговорка №1 - это концепт!!! Я специально сидел и удалял незначащий код! Проверки, защиту от ошибок и т.д.! Кроме того, а это что???
| If ShowModal = mrOk Then Result:=Edit1.Text Else Result:='"Отмена"'; | Итак... Во первых, основная цель написания такого рода диалогов - СТАНДАРТИЗАЦИЯ процедур и интерфейсов ввода. Вторая задача - РАЗГРУЗИТЬ код ОСНОВНЫХ модулей программы. Т.е. Чем короче вызов диалога - тем лучше. Крайне желательно, чтобы основной модуль получал ТОЛЬКО результат ввода (успех/НЕуспех) и, в случае успешного ввода - данные. Все. Все проверки, защита и т.д. ДОЛЖНЫ быть ВЫНЕСЕНЫ из рабочих модулей программы. Если ввод с ограничением диапазонов и т.д. - пишите диалог, принимающий на вход список ограничений и реализующий их! Не тащите это в основной модуль! > Возвращает единственное строковое значение, а зачастую их должно быть > несколько. > Что делать? Запихивать все в строку, а после проводить разбор на мой взгляд > неприемлемо. Ок. Приведу пример ввода МНОГИХ переменных. Да еще и по именам, да еще и в разных комбинациях и количествах...
| function InputStringsByName(BeginVal: TStrings): TModalResult; Var i: Integer; Cmpnt: TComponent; begin With TOptionsDlg.Create(Application.MainForm) do Try //Иницализируем поля начальными значениями For i:= 0 to BeginVal.Count - 1 do begin Cmpnt:=FindComponent(BeginVal.Names[i]); If Cmpnt is TEdit Then (Cmpnt as TEdit).Text:=BeginVal.ValueFromIndex[i] Else Try If Cmpnt is TSpinEdit Then (Cmpnt as TSpinEdit).Value:=StrToInt(BeginVal.ValueFromIndex[i]) Except End; end; //Показываем диалог Result:=ShowModal; //Если ввод успешен - копируем введенные значения на место начальных If Result = mrOk Then For i:= 0 to BeginVal.Count - 1 do begin Cmpnt:=FindComponent(BeginVal.Names[i]); If Cmpnt is TEdit Then BeginVal.ValueFromIndex[i]:=(Cmpnt as TEdit).Text Else Try If Cmpnt is TSpinEdit Then BeginVal.ValueFromIndex[i]:=IntToStr((Cmpnt asTSpinEdit).Value); Except End; end; Finally Free; End; end; | Для особо непонятливых можно дописать, что ф-ии проверок, защиты и конвертации данных следует добавить по вкусу!
добавлено 19.12.02
Скачать: Исходный код (3K) Откомпилированный пример (218K) Исходники - для Delphi 6. Главное отличие - формат файлов форм... Остальное должно работать "на ура"
Получение адреса из входящего сообщения в MS Outlook
| Раздел Сокровищница | ров Алекс, дата публикации 30 апреля 2002г. |
Функция демонстрирует решение проблемы, связанной с получением адреса из входящего сообщения в MS Outlook Function GetEAddr(InputMailItem : Variant {mailitem}) : String; Var MapiFile: TextFile; FirstLine, MailAddress : String; StrLength, Index : Integer; begin MailAddress := ''; // Сохраняем сообщение в текстовом файле... InputMailItem.SaveAs(WideString(ExtractFilePath(Application.EXEName) + 'mailitem.txt'), $00000000); // Если рассмотреть структуру созданного файла, то в первой строке кроме всего прочего, // содержится электронный адрес отправителя. Задача состоит в том, чтобы прочитать его... AssignFile(MapiFile, ExtractFilePath(Application.EXEName) + 'mailitem.txt'); Reset(MapiFile); Readln(MapiFile, FirstLine); CloseFile(MapiFile); If Pos('@', Trim(FirstLine)) > 0 Then Begin StrLength := Length(Trim(FirstLine)); Index := StrLength; While FirstLine[Index] <> ' ' Do Dec(Index); MailAddress := Copy(FirstLine, Index + 1, StrLength - Index); For Index := 1 To Length(Trim(MailAddress)) Do If (MailAddress[Index] = '[') Or (MailAddress[Index] = ']') Then MailAddress[Index] := ' '; MailAddress := Trim(MailAddress); End Else MailAddress := Trim(InputMailItem.SenderName); Result := MailAddress; // В том случае, если адрес все же не определен, возвращаем известный нам SenderName... end;
Поверхностный подход
При работе с полями в формате "дата-время" объектов типа TDataSet мои коллеги неоднократно сталкивались с проблемой поведения маски. Недавно у меня тоже возникла задача работы с такими полями. Возможно, ни один из нас просто не разобрался, как нужно делать правильно, но нужно было действовать.
Проблема заключается в том, что при вводе с клавиатуры требуется обязательно указывать все знаки, включая ненужные в конкретном случае (временную часть). В противном случае генерируется ошибка:
'Invalid input value. Use escape key to abandon changes' После часа, потраченного на разбирательство с маской, возникло желание написать собственный компонент. Спросив у коллег, которые уже ходили этим путем, я решил посмотреть в исходниках - вдруг получится быстро обойти этот вопрос.
Не буду брать на себя смелость комментировать, что и как делается в модуле Mask.pas. Кто хочет, может разобраться самостоятельно - ничего особо сложного там нет.
Для начала в свойстве EditMask замените символ BlankChar с '_' на '0'. В результате получится маска вроде
!99/99/99 99:99:99;1;0 Чтобы при редактировании и просмотре значение выглядело одинаково, укажите свойство DisplayFormat
dd.mm.yy hh:nn:ss
Далее нужно добавить в проект файлы Consts.pas, Sysconsts.pas и Mask.pas. После внесения изменений закройте Дельфи, и открыв снова, перекомпилируйте проект. Затем указанные файлы можно исключить из проекта. Пример приведен для Дельфи 5.
Изменения следующие: Consts.pas //SMaskEditErr = 'Invalid input value. Use escape key to abandon changes'; SMaskEditErr = 'Введено некорректное значение. Нажмите Esc для отмены'; SysConsts.pas //SInvalidDateTime = '''%s'' is not a valid date and time'; SInvalidDateTime = '''%s'' - некорректное значение даты и времени'; Mask.pas function TCustomMaskEdit.RemoveEditFormat(const Value: string): string; … {шестая строка снизу} {так было} // if Result[I] = FMaskBlank then // Result[I] := ' '; {так стало} if Result[I] = FMaskBlank then if FMaskBlank='0' then Result[I] := FMaskBlank else Result[I] := ' '; … function TCustomMaskEdit.Validate(const Value: string; var Pos: Integer): Boolean; … {одинадцатая строка снизу} {так было} // if (Value [Offset] = FMaskBlank) or // ((Value [Offset] = ' ') and (EditMask[MaskOffset] <> mMskAscii)) then if (FMaskBlank<>'0') and ((Value [Offset] = FMaskBlank) or ((Value [Offset] = ' ') and (EditMask[MaskOffset] <> mMskAscii))) then … В завершении хочу поделиться полезной и простой функцией. Как правило, при создании документа, мы вставляем текущие дату и время. При этом секунды как правило не нужны. function GetDateTimeWOSec(DateTime: TDateTime): TDateTime; begin Result:=StrToDateTime(FormatDateTime('dd.mm.yy hh:nn',DateTime)); end;
После проведения описанных манипуляций с полем в формате дата-время становиться так же приятно работать, как с компонентом TRXDateEdit.
Житель без титула Виктор Светлов
Практический пример
Также я сделал небольшой пример того, как можно использовать этот модуль на практике. Программа Drawing рисует картику по уже готовым математическим формулам, находящимся в файле Drawing.dat. Вы можете его открыть при помощи блокнота. Вы также можете вписать туда свои формулы. При создании этой программы мне пришлось зарегистрировать несколько новых функций. Первая из них (x) используется для расчета координаты X, вторая (y) используется для получения координыты Y, функция index возвращает индекс текущего пикселя. Напоминаю, что начало координат находится в верхнем левом углу. Кстати, если формула написана неправильно, то она будет проигнорирована, а для расчета картинки требуется как минимум три правильных формулы, по одной для кажного цвета: красного, зеленого и синего. Программа каждый раз при расчете картики выбирает случайные формулы. При стандартном разрешении 1024 : 768 получаем, что для вычисления одного составляющего всех пикселей картинки требуется произвести 1024 * 768 = 786432 операций, а всего 786432 * 3 = 2359296 операций. На моем компьютере расчет всей картинки занимает 1 - 3 секунды. На старых компьютерах расчет будет занимать намного больше времени, например, я был неприятно удивлен, когда запустил эту же программу на компьютере Celeron-400, там картинка расчитывалась 5 - 10 секунд.
Правильные диалоги от Борланда
рбань С.В., дата публикации 16 декабря 2002г. |
Почитал тут статью . Гммм.. Все в целом верно, но неудобно. Не хочу обижать РАЗДО более удачную конструкцию (которую, кстати, я уже давно использую).
Еще раз подчеркну - это не моя придумка, а ребят из Борланда. Эта конструкция позволяет: Возвращать ЛЮБЫЕ значения; ДИНАМИЧЕСКИ создавать форму; Еще куча всяких "бонусов", просто лень описывать :-) Итак, смотрим исходники...
В этом примере я привел два наиболее типичных случая. 1-й - InputString - просто ввод, без анализа отмены, второй - MrInputString - с анализом отмены ввода (ModalResult).
Оба случая используют начальные значения. Без них - Еще проще... В принципе - ваша фантазия ничем не ограничивается. Я, например, храню последние вводившиеся значения в реестре и читаю их оттуда после создания формы. Удобно.
Пользователь не мается вводя по 10 раз одно и то же, а у меня не болит голова с инициализацией полей (есть специальный класс, который этим занимается, но это отдельная тема...)
ИСХОДНИКИ: | //************************************************************** //Основной модуль Обратите Внимание! "uses Dialog;" implementation {$R *.dfm} uses Dialog; procedure TForm1.BitBtn1Click(Sender: TObject); begin ShowMessage('Вы ввели '+InputString('Начальное значение')); end; procedure TForm1.BitBtn2Click(Sender: TObject); Var Str: String; begin Str:='Начальное значение'; If MrInputString(Str) = mrOk Then ShowMessage('Вы ввели '+Str) Else ShowMessage('Вы отменили ввод'); end; //******************************************************** //Модуль диалога unit Dialog; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, ExtCtrls, Buttons; type TOptionsDlg = class(TForm) Bevel1: TBevel; BitBtn1: TBitBtn; BitBtn2: TBitBtn; Edit1: TEdit; Label1: TLabel; Bevel2: TBevel; Label3: TLabel; procedure FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); private { Private declarations } public { Public declarations } end; var OptionsDlg: TOptionsDlg; function InputString(BeginVal: String): String; function MrInputString(Var Str: String): TModalResult; implementation {$R *.dfm} function InputString(BeginVal: String): String; begin With TOptionsDlg.Create(Application.MainForm) do Try Edit1.Text:=BeginVal; If ShowModal = mrOk Then Result:=Edit1.Text Else Result:='"Отмена"'; Finally Free; End; end; function MrInputString(Var Str: String): TModalResult; begin With TOptionsDlg.Create(Application.MainForm) do Try Edit1.Text:=Str; Result:=ShowModal; Str:=Edit1.Text; Finally Free; End; end; procedure TOptionsDlg.FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); begin Case Key of 27: ModalResult:=mrCancel; 13: ModalResult:=mrOk; End; end; end. |
Преобразование денежной суммы в пропись
Невизуальный компонент для преобразования денежной суммы в пропись. За образец был взят метод, используемый в 1С. Т.е. вся сумма хранится в текстовом файле. Подгружая этот файл, можно управлять выводом суммы. Таким образом в примере реализована многоязыковая "сумма прописью". В MInWord.zip находятся: MInWord.pas - компонент TInWord для вывода суммы прописью. Project1.* и Unit1.* - файлы примера работы компонента. *.lng - подгружаемые языковые описания для компонента. | | Скачать (9К)
NetShareAdd , NetShareDel для Win9x
| Раздел Сокровищница | ан Николаевич, дата публикации 14 мая 2002г. |
Предлагается еще один пример реализации функций создания(удаления) расшаренного диска для Win9x Procedure NetShareDriveC(SetShared: Boolean); const LM20_NNLEN = 12; SHPWLEN = 8; SHI50F_RDONLY = 1; SHI50F_FULL = 2; SHI50F_DEPENDSON = (SHI50F_RDONLY or SHI50F_FULL); SHI50F_ACCESSMASK = (SHI50F_RDONLY or SHI50F_FULL); SHI50F_PERSIST = 256; SHI50F_SYSTEM = 512; STYPE_DISKTREE = 0; ACCESS_NONE = 0; ACCESS_READ = $01; ACCESS_WRITE = $02; ACCESS_CREATE = $04; ACCESS_EXEC = $08; ACCESS_DELETE = $10; ACCESS_ATRIB = $20; ACCESS_PERM = $40; ACCESS_GROUP = $8000; ACCESS_ALL = (ACCESS_READ or ACCESS_WRITE or ACCESS_CREATE or ACCESS_EXEC or ACCESS_DELETE or ACCESS_ATRIB or ACCESS_PERM); type share_info_2= record shi2_netname : PWideChar; shi2_type : DWORD; shi2_remark : PWideChar; shi2_permissions : DWORD; shi2_max_uses : DWORD; shi2_current_uses : DWORD; shi2_path : PWideChar; shi2_passwd : PWideChar; end; share_info_50 = packed record shi50_netname : array [0..LM20_NNLEN] of Char; shi50_type : Byte; shi50_flags : Short; shi50_remark : PChar; shi50_path : PChar; shi50_rw_password: array [0..SHPWLEN] of Char; shi50_ro_password: array [0..SHPWLEN] of Char; end; var hDll : THandle; NetShareAddWin9x : function(pszServer : PChar; sLevel : Short; pbBuffer : Pointer; cbBuffer : Short):DWORD;stdcall; NetShareDelWin9x : Function(pszServer : PChar; pszNetName : PChar; usReserved : Short):DWORD;stdcall; si50 : share_info_50; si2 : share_info_2; tamano : Short; res, err : DWORD; EsNT: Boolean; Begin If SetShared then begin hDll := LoadLibrary('SvrApi.dll'); if hDll > 32 then begin // NetShareAdd NetShareAddWin9x := GetProcAddress(hDll, 'NetShareAdd'); tamano := sizeof(si50); FillChar(si50, tamano, 0); StrCopy(si50.shi50_netname, 'SH_ACCESS'); si50.shi50_type := STYPE_DISKTREE; si50.shi50_flags := SHI50F_Full; //SHI50F_RDONLY; si50.shi50_path := 'C:\'; StrCopy( si50.shi50_rw_password, 'siemensw'); StrCopy( si50.shi50_ro_password, 'siemens'); res := NetShareAddWin9x(nil, 50, @si50, tamano); Showmessage('NetShare added to C:\Test .'); FreeLibrary(hDll); end; end else begin hDll := LoadLibrary('SvrApi.dll'); if hDll > 32 then begin // NetShareDel NetShareDelWin9x := GetProcAddress(hDll, 'NetShareDel'); res := NetShareDelWin9x(nil, PChar('SH_ACCESS'), 0); Showmessage('NetShare deleted. Check C:\test .'); FreeLibrary(hDll); end; end; End;
Смотрите по теме:
Пример работы по последовательному порту
| Раздел Сокровищница | атьев, дата публикации 22 марта 2001г. |
Модуль для работы с весами (ПетВес серия EB4) по последовательному порту Реально работающий "драйвер" и может быть использован по назначению. Скачать модуль (2K) ... type ComPort = 1..4; const ComPortName : array [ComPort] of string = ('COM1','COM2','COM3','COM4'); type TWeightAdapter = class ( TComponent ) private FPort : ComPort; FTimeOut : integer; function GetWeight: double; procedure SetPort(const Value: ComPort); public constructor Create( AOwner : TComponent );override; function AsString: string; published property Weight : double read GetWeight; property Port : ComPort read FPort write SetPort; property TimeOut : Integer read FTimeOut write FTimeOut; end; function GetWeight ( Port : integer = 1 ): double; procedure Register; implementation uses SysUtils,Windows; function GetWeight ( Port : integer = 1 ): double; var A : TWeightAdapter; Begin A := TWeightAdapter.Create(nil); A.Port := Port; Result := A.Weight; A.Free; End; const SIncorrectPort = 'Неверный номер порта'; SPortNotOpen = 'Невозможно открыть порт'; { TWeightAdapter } function TWeightAdapter.AsString: string; begin result := Format('%f',[weight]); end; constructor TWeightAdapter.Create(AOwner: TComponent); begin inherited Create( AOwner ); FTimeOut := 2; FPort := 1; end; function TWeightAdapter.GetWeight: double; var S : string; hComm,Readed : Cardinal; Buffer : byte; Mode : TDCB; TimeOuts : COMMTIMEOUTS; StartTime,Finish : TDateTime; Done : boolean; const Numbers = ['0'..'9','.',',']; Function GetString : string ; var B,E : integer; Begin B := 0; E := Length(S); While (E>0) and (S[E]<>#13) do Dec(E); If E>0 then B := E; While (B>0) and (S[B]<>#10) do Dec(B); If B>0 then Result := Copy(S,B+1,E-1) else Result := ''; End; function ParseString : extended; var T,S : string; // 'ST, 102,12kgG'#13#10 begin result := -1; S := GetString; // Формат : ST. 100.05 kgG // ST/US/OL : Стабильно / нестабильно / перегруз // Число : вес // KG : Единица измерения ( других похоже нет ) // H/G/L : Верхний предел/норма/нижний предел T := UpperCase(Copy(S,1,2)); If (T='US') or (T='OL') then Exit; If (T='ST') or (T='+ ') or (T='- ') then Begin While ((Length(S)>0) and (not (S[1] in ['0'..'9']))) do Delete(S,1,1); T := ''; While (Length(S)>0) and (S[1] in Numbers) do Begin If (S[1] = ',') then T := T+'.' else T := T+S[1]; Delete(S,1,1); End; Val(T,Result,Readed); Done := true; End; end; begin Result := 0; if csDesigning in ComponentState then Exit; Finish := FTimeOut / 86400; Done := false; // Открываем HComm := CreateFile( PChar(ComPortName[Port]), GENERIC_READ, 0, nil, OPEN_EXISTING, 0, 0); if hComm = INVALID_HANDLE_VALUE then raise Exception.Create(SPortNotOpen); with Mode do Begin BaudRate := 9600; ByteSize := 8; Parity := NOPARITY; StopBits := ONESTOPBIT; Flags := EV_RXCHAR + EV_EVENT2; End; SetCommState ( hComm, Mode ); // Устанавливаем таймауты with TimeOuts do Begin ReadIntervalTimeout := MAXDWORD; ReadTotalTimeoutMultiplier := 0; ReadTotalTimeoutConstant := 0; End; SetCommTimeOuts(hComm,TimeOuts); // Собираем строку StartTime := Now; repeat ReadFile(hComm,Buffer,1,Readed,nil); If Readed>0 then Begin S := S+Char(Buffer); Result := ParseString; End; until (Now-StartTime>Finish ) or Done; // Заметаем следы CloseHandle(HComm); // Закрываем хэндл файла end; procedure TWeightAdapter.SetPort(const Value: ComPort); begin If (Value>0) and (Value<5) then FPort := ComPort(Value) else raise Exception.Create(SIncorrectPort); end; ...
дата публикации 10 ноября 2001г.
| Раздел Сокровищница | тов, дата публикации 10 ноября 2001г. |
Предлагается новый вариант архива (41.5K) В этом архиве представлены 3 версии программы, которая демонстрирует работу со слоями(Layers) в Windows 2000: Берется форма, на нее накладывается рисунок (любой). Форма окна подгоняется под рисунок (цвет точки в координатах [0,0] считаем прозрачным). Потом - две новые прикольные WinAPI функции. Первая располагает окно на отдельном Layer-е в Windows 2000+ SetWindowLong Вторая - она там в цикле по таймеру крутится - устанавливает степень прозрачности Layer-а с использованием Alpha-канала. SetLayeredWindowAttributes В результате - окно по форме скина (кто-то интересовался) и демонстрация новых функций API Windows 2000. Прозрачными формами тоже кто-то развлекался.
В каталоге D5 - версия для Delphi5 с объявлением внешних функций API Windows 2000. Написана Ярославом Богатовым (aka AnorAglar) Аскетизм и прямолинейность кода демонстрируют не только соответствующие качества мозга программиста, но и позволяют запихать всё в один модуль, где всё понятно и без комментариев.
В каталоге NewLayer - версия для Delphi5. Оптимизирована Андреем Пляко (aka EinWill) По скорости создания региона По логичности кода По структурированности и комментированности. В каталоге D6 - версия для Delphi6 с использованием новых свойств формы. Почувствуйте разницу между D5 и D6. И ужас от того, что скоро все будут это использовать.
P.S. Код Андрея Пляко опубликован с согласия чной теме смотрите проекты Антона Григорьева: Окно с изменяемой степенью прозрачности.
Пример работы с окнами средствами Win API
| Раздел Сокровищница | анович Олег, дата публикации 21 марта 2002г. |
Приложенные файлы: (216 K) - программа откомпилированная Delphi 6, (3.8 K) - исходный код программы, для Delphi 6. Программа распространяется свободно, разработана в обучающих целях на Delphi 6. Всю информацию по работе смотрите в исходных кодах.
Смотрите материалы Королевства по этой теме: | |
Принцип работы
Компонент ищет таблицу по ячейкам ее верхнего ряда (шапка таблицы). Существует структура типа TTableInfo, которая описывает критерии поиска: TFilterOption = (foCaseInsensitive, foSoftFiltration); THeadLine = record Cells: array of string; FilterOptions: array of TFilterOption; end; TBorderOption = (boRight, boBottom); TBorderOptions = set of TBorderOption; TTableInfo = record HeadLine: THeadLine; Rect: TRect; BorderOptions: TBorderOptions; TableIndex, BottomIndex: Integer; end; Описание шапки таблицы находится в элементе TTableInfo.HeadLine, где TTableInfo.HeadLine.Cells это строковый массив верхнего ряда ячеек, а TTableInfo.HeadLine.FilterOptions это массив, который соответствует каждой ячейки массива TTableInfo.HeadLine.Cells и определяет способ сравнения элементов массива TTableInfo.HeadLine.Cells с ячейками таблицы Excel. TTableInfo.HeadLine.FilterOptions может быть двух типов:
foCaseInsensitive означает, что соответствующий элемент массива Cells должен в точности совпадать с ячейкой в документе Excel. Регистр не учитывается.
foSoftFiltration означает, что каждая ячейка из документа Excel может содержать в себе соответствующий элемент массива Cells. Регистр не учитывается. Если массив TTableInfo.HeadLine.FilterOptions пустой, то для сравнения используется свойство DefaultFilter компонента ExcelManager. Задавать этот массив не обязательно, а в большинстве случает вообще не нужно. Тем не менее он позволяет определять достаточно гибкие критерии поиска.
Элемент TTableInfo.Rect обозначает координаты и размеры таблицы по отношению к шапке таблицы:
Left обозначает смещение влево относительно левой верхней ячейки таблицы (шапки таблицы). Right обозначает количество колонок таблицы.
Top обозначает смещение вниз относительно левой верхней ячейки таблицы (шапки таблицы).
Bottom обозначает количество рядов таблицы.
Задавать ширину (Rect.Right) и высоту (Rect.Bottom) таблицы не обязательно, так как компонент сам может определять размеры таблицы. Для автоопределения ширины таблицы, элемент TTableInfo.BorderOptions должен содержать boRight и, соответственно, для автоопределения высоты таблицы TTableInfo.BorderOptions должен содержать boBottom. Определение границы осуществляется путем нахождение первой пустой ячейки. Просмотр таблицы в документе Excel происходит сверху вниз, слева направо. Для изменения способа обнаружения нижней границы таблицы используется еще один элемент: TTableInfo.BottomIndex. Он определяет колонку, которая должна содержать пустую ячейку. Например, если TTableInfo.BorderOptions включает в себя boBottom и TTableInfo.BottomIndex равен 0, то определение высоты таблицы будет находиться по первой пустой ячейке колонки 0, на рисунке это колонка "A": В таком случае высота таблицы будет равна 7, то есть будет содержать в себе 7 рядов. Если же TTableInfo.BorderOptions включает boBottom и TTableInfo.BottomIndex равен 1, то определение высоты таблицы будет находиться по первой пустой ячейке колонки 1, на рисунке это колонка "B" и высота таблицы будет равна 5, то есть будет содержать в себе 5 рядов.
Так как в документе Excel может быть найдена более чем одна таблица, удовлетворяющая условиям структуры TTableInfo, то существует элемент: TTableInfo.TableIndex. Он указывающий на индекс нужной таблицы. Чаще всего документ Excel содержит в себе только одну искомую таблицу, поэтому целесообразно задавать значение TTableInfo.TableIndex равным 0.
Для импорта таблицы используется функция:
function ImportTable(Table: TTable; TableInfo: TTableInfo): Boolean; virtual;
В ней указывается таблица Table, которая будет заполнена соответствующими данными и информация о таблице, которую необходимо найти. Если все прошло успешно, то функция возвращает истину. Прежде чем импортировать таблицу, нужно открыть документ Excel. Это делается с помощью процедуры:
procedure Open(const FileName: string); virtual;
Параметр FileName содержит путь к файлу Excel. Соответственно, после открытия файла его нужно закрыть. Делается это с помощью процедуры:
procedure Close(SaveChanges: Boolean); virtual;
Параметр SaveChanges определяет, нужно ли сохранять изменения.
Процесс экспотра таблицы намного проще процесса импорта. Для задания условий экспортирования используется структура:
TExportTableInfo = record Cell1, Cell2: string; Rect: TRect; end; Элементы TExportTableInfo.Cell1 и TExportTableInfo.Cell2 задают координаты левой верхней ячейки экспортируемой таблицы в формате Excel. То есть, например, если необходимо экспортировать таблицу в самое начало документа Excel, то задаем значение TExportTableInfo.Cell1 равным "A", значение TExportTableInfo.Cell2 равным "1", в таком случае левая верхняя ячейка таблицы разместиться в документе Excel по адресу "A1". Элемент TExportTableInfo.Rect определяет смещение экспортируемой таблицы относительно координат TExportTableInfo.Cell1 и TExportTableInfo.Cell2. Используются только элементы Left и Top структуры TExportTableInfo.Rect. На первый взгляд это кажется бессмысленным. Действительно, зачем нужно смещение, если и так можно задать любую координату элементами TExportTableInfo.Cell1 и TExportTableInfo.Cell2? Все дело в том, что в Excel специфическая система координат. Для пользователя она весьма удобна, а для программиста это небольшая проблема. Горизонтальная координата - это система исчисления, состоящая из английского алфавита. То есть, двадцатишестиричная система исчисления. Но беда в том, что в этой системе исчисления нет нуля. То есть если использовать двадцатишестиричную систему исчисления по правилам, то A это 0, B это 1, C это 2 и так далее. Но в любом документе присутствуют такие координаты, как AA, AB, AC и так далее. По всем правилам они должны выглядеть, как A, B, C и так далее, так как первым числом ноль (A) никогда не ставится. Но это все философия. Вернусь к тому, от чего я ушел. Если нужно задать координату, скажем 100:200, то не нужно пересчитывать горизонтальную координату в формат Excel, достаточно установить, скажем, TExportTableInfo.Cell1 в "A", TExportTableInfo.Cell2 в "1", TExportTableInfo.Rect.Left в 100 и TExportTableInfo.Rect.Top в 200.
Проблемы копирования русского текста в clipboard и обратно
рь Цысь ( Igoreha ), дата публикации 24 апреля 2003г. |
У многих возникает проблема с копированием русского текста в буфер обмена на ОС Win2000 и WinXP а может и Win9x. Простого и надежного решения данной проблемы найти, к сожалению, не удалось :-( Представляю модуль который поможет решить проблему копирования русского текста в clipboard и обратно. Спасибо всем, кто помог решить эту проблему !!! Нужно просто добавить в проект ...
| unit RusClipboard; interface uses Clipbrd; type TRusClipboard = class(TClipboard) private procedure SetCodePage(const CodePage: longint); public procedure Open; override; procedure Close; override; end; implementation uses Windows; { TRusClipboard } procedure TRusClipboard.Close; begin SetCodePage($0419); inherited; end; procedure TRusClipboard.Open; begin inherited; SetCodePage($0419); end; procedure TRusClipboard.SetCodePage(const CodePage: longint); var Data: THandle; DataPtr: Pointer; begin // Назначить кодовую страницу для буфера обмена Data:= GlobalAlloc(GMEM_MOVEABLE + GMEM_DDESHARE, 4); try DataPtr := GlobalLock(Data); try Move(CodePage, DataPtr^, 4); SetClipboardData(CF_LOCALE, Data); finally GlobalUnlock(Data); end; except GlobalFree(Data); end; end; var FClipboard: TClipboard; OldClipboard: TClipboard; initialization // Установить клипборд FClipboard:= TRusClipboard.Create; OldClipboard:= SetClipboard(FClipboard); if OldClipboard <> nil then OldClipboard.Free; end. |
Процедура печати TStringList на принтер
Процедура печатает TStringList на принтер, переносит на следующий лист бумаги, если список не помещается. Поля - фиксированные в пол-дюйма. procedure PrintStrings(S: TStrings; Font: TFont; Title: string); var LeftMargin, TopMargin, LineCoord, LineOnPage, LinesOnDoc, CurrentLine, TextHeight, LinesPerPage, LineInterval: integer; procedure StartDoc; begin LinesOnDoc := S.Count; Printer.Canvas.Font.Assign(Font); Printer.Canvas.TextOut(0, 0, ' '); LeftMargin := (Printer.Canvas.Font.PixelsPerInch) div 2; TopMargin := (Printer.Canvas.Font.PixelsPerInch) div 2; TextHeight := Abs(Printer.Canvas.Font.Height); LineInterval := TextHeight + (TextHeight div 2); LinesPerPage := (Printer.PageHeight - TopMargin) div LineInterval; CurrentLine := 0; end; function MorePages:boolean; begin Result := (CurrentLine < LinesOnDoc) and not Printer.Aborted; end; procedure StartPage; begin LineOnPage := 0; LineCoord := TopMargin; end; procedure NextPage; begin if MorePages then Printer.NewPage; end; function MoreLines:boolean; begin Result := (LineOnPage < LinesPerPage) and (LineOnPage < LinesOnDoc) and not Printer.Aborted; end; procedure NextLine; begin Inc(LineOnPage); Inc(LineCoord, LineInterval); Inc(CurrentLine); end; procedure PrintLine; begin Printer.Canvas.TextOut(LeftMargin, LineCoord, S.Strings[CurrentLine]); end; begin Printer.Title := Title; Printer.BeginDoc; StartDoc; while MorePages do begin StartPage; while MoreLines do begin PrintLine; NextLine; Application.ProcessMessages; end; NextPage; end; Printer.EndDoc; end;
Алексей Еремеев Смотрите так же:
Процедура выравнивает "уехавшую" форму внутри рабочей части экрана
procedure SafeFormPlace(Form: TForm); var R: TRect; L,T: integer; begin if not SystemParametersInfo(SPI_GETWORKAREA, 0, @R, 0) then with Screen do R := Bounds(0, 0, Width, Height); L := Form.Left; if L < R.Left then L := R.Left else if (L + Form.Width) > R.Right then L := R.Right - Form.Width; T := Form.Top; if T < R.Top then T := R.Top else if (T + Form.Height) > R.Bottom then T := R.Bottom - Form.Height; Form.SetBounds(L, T, Form.Width, Form.Height); end;
Алексей Еремеев
Проект "Warp Button". Иллюстрация к статье "Пространство имен оболочки Windows"
| Раздел Сокровищница | н , дата публикации 23 августа 2001г. |
Этот фриварный проект может принести как практическую, так и теоретическую пользу. Фактически это иллюстрация к статье известного гуру Акжана Абдулина (есть в Свитках и на его личном сайте). Именно она послужила толчком и основанием для этой разработки.
Идея проста. Хотелось получить в Windows функциональность кнопки Warp оболочки WarpCenter операционной системы OS/2 Warp 4.0 Merlin. Отличается она от кнопки Start Explorer'а тем, что показывает не отдельно формируемое меню, а иерархическое содержимое десктопа. То есть, все, что есть на десктопе и во вложенных папках, она разворачивает в виде меню и позволяет запускать.
Скачать проект: exe-файл + исходные коды (52 K) Программа ставится в автостарт и помещает свой значок в System Tray. Исходные тексты могут ответить на массу часто задаваемых вопросов о программировании в Windows. Вот некоторые темы: - Получение и использование папок рабочего стола, перечисление всех объектов. - Работа без VCL, использование Win32API. - Организация неконсольной программы без видимого окна. - Работа с "иконкой в Tray" без компонентов ;) - Использование меню из ресурса и меню "ручной сборки", иконки в меню (стиль OWNERDRAW). - Отслеживание изменений в директории (в частности - папка десктопа). - Получение иконок для объектов рабочего стола. - Контекстное меню для объектов рабочего стола. - Операции с идентификаторами объектов оболочки (pidl). - Хранение опций в реестре. - И многое другое. Единственный большой недостаток - отсутствие комментариев.
В основу положен принцип "Лучше день потерять, потом за пять минут долететь" (с) мультик. При старте зачитываются все объекты, зато потом меню работает быстро. Некоторые известные спецпапки десктопа по-умолчанию не развертываются во вложенное меню (можно отключить в конфигурации). Диски в "My Computer" тоже не развертываются - было бы слишком много объектов. Изменения на самом десктопе контролируются и запускается процедура перечитывания объектов (во вложенных папках контроля нет).
Горячая клавиша вызова меню - Ctrl-Alt-F12. Вызов контекстного меню для выбранного объекта - Ctrl-Enter или правая кнопка мыши.
Проект компилируется в D5, первоначально был написан в среде D3 (есть различия в инициализации COM). Программа была разработана в 1999 году в порядке изучения Delphi и методов работы с API и объектами эсплорера.
Отдельное спасибо Акжану Абдулину за вышеупомянутую статью о пространстве имен, а также Анатолию Тенцеру за его "конструктор юного любителя иконок в SysTray" (модуль TaskBar.pas - его, без изменений).
Программа для тестирования скорости расчета
Программа предназначена для расчета скорости вычисления формул (как математических, так и логических) на Вашем компьютере. В ниспадающем списке я уже приготовил несколько формул различной сложности. Как Вы увидите, скорость вычисления простых и сложных формул различается. Программа также показывает структуру сценария. Структура отображается числами по 4 байта, разделенных между собой знаком пробела. Количество выполняемых операций также можно регулировать.
Программа для установки параметров экрана из командной строки
Агранович, дата публикации 09 января 2003г. |
Утилита, которая меняет параметры экрана на заданные в командной строке. Может пригодиться дизайнерам, разработчикам софта, а так же тем, кто работает на компьютере не один, и предпочитает пользоваться своими настройками экрана. Достаточно запустить ярлык программы и параметры экрана мгновенно изменятся на указанные в командной строке. Например команда "ScreenSet.exe 800 600 8 100" установит: разрешение в 800 на 600, глубину цвета в 8 бит на пиксель, а частоту экрана в 100Гц. Запуск программы: "ScreenSet.exe Ширина Высота Цвет Частота" Пример: "ScreenSet.exe 800 600 8 100"
Для изменения параметров экрана используется следующая функция: | function SetFullscreenMode(PelsWidth, PelsHeight, BitsPerPixel, DisplayFrequency: Integer):Boolean; var DeviceMode : TDevMode; begin with DeviceMode do begin dmSize:=SizeOf(DeviceMode); dmBitsPerPel:=BitsPerPixel; dmPelsWidth:=PelsWidth; dmPelsHeight:=PelsHeight; dmDisplayFrequency:=DisplayFrequency; dmFields:=DM_BITSPERPEL or DM_PELSWIDTH or DM_PELSHEIGHT or DM_DISPLAYFREQUENCY; Result:=ChangeDisplaySettings(DeviceMode,CDS_UPDATEREGISTRY) = DISP_CHANGE_SUCCESSFUL; end; end; | Скачать исходный код: (12.8 K)
| Для данного материала нет комментариев. |
Программная настройка DCOM
Для программной настройки DCOM можно воспользоваться процедурами модуля BDcomPrm
DefaultProperties
| Элемент | Процедура | Описание | 9x | NT | | Enable Distributed COM on this computer | IsDCOMOk | Проверяет наличие поддержки DCOM | + | + | | IsEnabledDCOM | Проверяет разрешен ли DCOM на данной машине | + | + | | SetEnableDCOM | Разрешает DCOM на данной машине | + | + | | IsDCOMProtocolsEnabled | Проверяет наличие протоколов DCOM | - | + | | Default Authentication LevelDefault Impersonation Level | IsInitializeSecurityOk | Проверяет можно ли устанавливать параметры Security. Возвращает True для платформы NT и False для 9x | - | + | | InitializeDefaultSecurity | Устанавливает параметры Security по умолчанию.Вызов данной процедуры необходимо поместить перед Application.Initialize в клиентской и серверной программе.Процедуру можно вызывать только один раз для текущего процесса.Процедура должна быть вызвана до первого обращения к COM-объекта, требующего маршалинга | . + | + | | SetDefaultDCOMCommunicationProperties | Устанавливает параметры по умолчанию для Authentication Level, Impersonation Level | + | + | | CreateRemoteComObjectEx | Определяет Authentication Level, Impersonation Level запускаемого серверного приложения.Данная процедура может использоваться вместо CreateRemoteComObject | + | + | | Provide additional security for reference tracking | RemoveLegacySecure-References | При разрешении DCOM необходимо вызвать эту процедуру, чтобы сбросить флажок Повышенной безопасности для отслеживания ссылок | + | + |
DefaultSecurity
| Элемент | Процедура | Описание | 9x | NT | | Enable remote connection | IsDCOMOk, IsEnabledDCOM, SetEnabledDCOM | | + | + | | Default access permissions | ListDefaultAccessACL | Возвращает в строке описание разрешений доступа к DCOM приложениям по умолчанию. Данное описание может быть выведено в Memo. | - | + | | | ChangeDefaultAccessACL | ChangeDefaultAccessACL Изменяет параметры доступ к DCOM приложениям по умолчанию.Principal - имя пользователя (например Everyone)SetPrincipal - True добавить пользователя в список, False - удалить.Permit - разрешить параметры для указанного пользователя | - | + | | Default launch permissions | ListDefaultLaunchACL | Возвращает в строке описание разрешений запуска DCOM приложений по умолчанию. Данное описание может быть выведено в Memo | - | + | | | ChangeDefaultLaunchACL | ChangeDefaultLaunchACL Изменяет параметры запуска DCOM приложений по умолчанию - + | - | + | | | IsDefaultLaunchAccess-Allowed | Возвращает True, если разрешен запуск DCOM приложений по умолчанию. | - | + |
Application Security
Все процедуры и функции данной категории получают в качестве входного параметра AppID - CLSID объекта сервера. | Элемент | Процедура | Описание | 9x | NT | | Access permissions | ListAppIDAccessACL | Возвращает в строке описание разрешений доступа к DCOM приложению. Данное описание может быть выведено в Memo.AppID - CLSID объекта сервера. | - | + | | ChangeAppIDAccessACL | Изменяет параметры доступ к DCOM приложениям по умолчанию.AppID - CLSID объекта сервера.Principal - имя пользователя (например Everyone)SetPrincipal - True добавить пользователя в список, False - удалить.Permit - разрешить параметры для указанного пользователя | - | + | | Default launch permissions | ListAppIDLaunchACL | Возвращает в строке описание разрешений запуска DCOM приложения. Данное описание может быть выведено в Memo.AppID - CLSID объекта сервера. | - | + | | ChangeAppIDLaunchACL | Изменяет параметры запуска DCOM приложений по умолчанию.AppID - CLSID объекта сервера. | - | + | | IsLaunchAccessAllowed | Возвращает True, если разрешен запуск DCOM приложения. | - | + | | AllowLaunchAccess | Разрешает запуск DCOM приложения | - | + |
Закладка Identity
Все процедуры и функции данной категории получают в качестве входного параметра AppID - CLSID объекта сервера. | Элемент | Процедура | Описание | 9x | NT | | Which user account do you want to use to run this application | IsInteractiveUser | Проверяет, используется ли для запуска приложения учетная запись взаимодействующего пользователя | - | + | | SetInteractiveUser | Устанавливает параметр: использовать для запуска приложения учетную запись взаимодействующего пользователя | - | + |
Остальные процедуры и функции
Все остальные процедуры и функции модуля BDcomPrm носят служебный характер.
Абдулин Марат , руководитель отдела программирования
Скачать исходные коды: (28 K)
Статьи по теме:
Работа с БД: Поиск и фильтрация.
| Раздел Сокровищница | Автор Александр Мефодьев дата публикации 31 января 2000г. |
П О И С К 1. Метод Locate: function Locate(const KeyFields: String; const KeyValues: Variant; Options: TLocateOptions): Boolean; Метод Locate ищет первую запись, удовлетворяющую критерию поиска, и если такая запись найдена, делает ее текущей. В этом случае в качестве результата возвращается True. Если запись не найдена - False.
Список KeyFields указывает поле, или несколько полей, по которым ведется поиск. В случае нескольких поисковых полей их названия разделяются точкой с запятой. Критерии поиска задаются в вариантном массиве KeyValues так, что i-е значение KeyValues ставится в соответствие i-му полю в KeyFields. Options позволяет указать необязательные значения режимов поиска: type TLocateOption = (loCaseInsensitive, loPartialKey); TLocateOptions = set of TLocateOption; loCaseInsensitive - поиск ведется без учета регистра букв, т.е. KeyValues будет считать слова "принтер" и "ПРИНТЕР", а также "ПрИнТеР" одинаковыми. loPartialKey - запись считается удовлетворяющей условию поиска, если она содержит часть поискового контекста, например, удовлетворяющими контексту "Ма" будут признаны слова: "Мама", "Машина" и т.д. Locate производит поиск по любому полю; полк или поля, по которым производится поиск, могут не только не входить в текущий индекс, но и не быть индексированными вообще.
В случае, если поля входят в какой-либо индекс, Locate использует этот индекс при поиске. Если искомые поля входят в несколько индексов, трудно сказать, какой из них будет использован. Соответственно трудно предсказать, какая запись из множества записей, удовлетворяющих критерию поиска, будет сделана текущей - особенно в случае, если поиск ведется не по текущему индексу.
При поиске по полям, не входящим ни в один индекс, применяется фильтр BDE. Вот пример использования Locate: procedure TForm1.LocateButtonClick(Sender: TObject); begin Table1.Locate('Field1;Field2', VarArrayOf(['Ма','Зд']), [loPartialKey]); end; В этом примере поиск произведен при помощи одной строчки кода: procedure TDataBase.SearchButtonClick(Sender: TObject); begin Table.Locate(FieldsCombo.Text, SearchEd.Text, [loPartialKey, loCaseInsensitive]);; end;
2. Метод Lookup
function Lookup( const KeyFields: String; const KeyValues: Variant; const ResultFields: String): Variant; Метод Lookup находит находи нужную запись, но не делает ее текущей, а возвращает значения некоторых полей этой записи. Тип результата - Variant или вариантный массив. Независимо от успеха поиска записи, указатель текущей записи в таблице не меняется. В отличие от Locate, Lookup осуществляет поиск только на точное соответствие критерия поиска и значения полей записи. В KeyFields указывается список полей, по которым необходимо осуществлять поиск. При наличии в этом списке более чем одного поля соседние поля разделяются точкой с запятой. KeyValues указывает поисковые значения полей, список которых содержится в KeyFields.
Если имеется несколько поисковых полей, каждому i-му полю в KeyFields ставится в соответствие i-ое значение в KeyValues. При наличии одного поля его поисковое значение можно указывать в качестве KeyValues непосредственно; в случае нескольких полей их необходимо приводить к типу вариантного массива при помощи VarArrayOf.
В качестве поисковых полей можно указывать поля как входящие в какой-либо индекс, так и не входящие в него; тип текущего индекса не имеет значения. Если поисковые поля входят в какие-либо индексы, их использование производится автоматически; в противном случае используются фильтры BDE.
Если в результате поиска запись не найдена, метод Lookup возвращает Null, что можно проверить с помощью оператора: If VarType(LookupResults) = varNull then ... В противном случае Lookup возвращает из этой записи значения полей, список которых содержит ResultFields. При этом размерность результата зависит от того, сколько результирующих полей указано в ResultFields: одно поле - результатом будет значение соответствующего типа или Null, если поле найденной записи содержит пустое значение; несколько полей - результатом будет вариантный массив, число элементов в котором меньше или равно числу результирующих полей (некоторые поля найденной записи могут содержать пустые значения). Пример: Одно результирующее поле procedure TForm1.LookupButtonClick(Sender: TObject); var LookUpResults: Variant; begin LookupResults:=Table1.Lookup('Name', Edit1.Text, 'Phone'); Case varType(LookUpResults) of varEmpty : Label1.caption:='Пустой результат'; varNull : Label1.Caption:='Запись не найдена'; else Label1.Caption:=LookUpResults; end; end; Пример: Несколько результирующих полей procedure TForm1.LookupButtonClick(Sender: TObject); var LookUpResults: Variant; begin LokUpResults:=Table1.Lookup('Name', Edit1.Text, 'TabNum;Doljnost;Phone'); If VarIsArray(LookUpResults) then begin Label1.Caption:=LookUpResults[0]; If LookUpResults[1] <> Null then Label2.Caption:=LookUpResults[1]; If LookUpResults[2] <> Null then Label3.Caption:=LookUpResults[2]; end else case VarType(LookUpResults) of varEmpty : Label1.caption:='Пустой результат'; varNull : Label1.Caption:='Запись не найдена'; end; end; Если запись не найдена, VarType(LookUpResults) возвращает значение varNull. Если поиск по какой-либо причине не был произведен, VarType(LookUpResults) возвращает значение VarEmpty. Если какое-либо из полей, что значения возвращаются в результате поиска в вариантном массиве, содержит пустое значение, соответствующий элемент вариантного массива также будет содержать пустое значение (Null). В этом случае обращение к нему приведет к исключительной ситуации, поэтому нужна предварительная проверка.
Ф И Л Ь Т Р А Ц И Я
Свойство Filter
Свойство Filter компонента TTable позволяет задать критерий фильтрации. В этом случае база будет отфильтрована, как только свойство Filtered будет равно TRUE. Синтаксис описания критерия похож на синтаксис секции WHERE SQL-запроса с тем исключением, что имена переменных программы указывать нельзя, можно указывать имена полей и литералы (явно заданные значения); можно использовать обычные операции отношения и логические операторы AND, NOT и OR, например:
Эта запись фильтра оставит в таблице записи, в которых поля Doljnost='доцент' и TabNum больше 3000 Filter:='([Doljnost]=''доцент'') and ([TabNum] > 3000)'; Filtered:=True; Строку критерия фильтрации можно ввести во время прогона программы или на этапе конструирования. Например, с помощью такого обработчика события OnChecked компонента CheckBox1 критерий фильтрации считывается из поля Edit1 и помещается в свойство Filter компонента Table1: procedure TForm1.CheckBox1Click(Sender: TObject); begin Table1.Filter := Edit1.Text; Table1.Filtered := CheckBox1.Checked; end; С помощью свойства type TFilterOption = (foCaseInsensitive, foNoPartialCompare); property FilterOptions: TFilterOptions; можно определить дополнительные свойства фильтрации строковых полей: foCaseInsensitive - фильтрация производится без учета разницы регистра foNoPartialCompare - поиск производится на точное соответствие.
Событие OnFilterRecord
Событие OnFilterRecord возникает при установке значения True в свойство Filtered. Обработчик события имеет два параметра: имя фильтруемого набора данных и переменную Accept, в которую программа должна поместить True, если текущая запись удовлетворяет условию фильтрации.
В отличие от критерия в строке Filtered, ограниченного рамками синтаксиса условного выражения, критерий, реализуемый в обработчике события OnFilterRecord, определяется синтаксисом Object Pascal и может организовать сложные алгоритмы фильтрации. Однако следует помнить, что в обработчике OnFilterrecord последовательно перебираются все записи БД, в то время как методы SetRange, ApplyRange и им сопутствующие методы компонента TTable используют индексно-последовательный метод, т.е. работают с частью записей в физической БД. Это делает использование обработчика OnFilterRecord предпочтительным для фильтрации небольших объемов записей и сильно ограничивает его применение при больших объемах данных.
Всякий раз, когда приложение обрабатывает событие OnFilterRecord, набор данных переводится из состояния dsBrowse в состояние dsFilter. Это предотвращает модификацию набора данных во время фильтрации. После завершения текущего вызова обработчика события ObFilterRecord набор данных переводится в состояние dsBrowse.
Пример: чтобы создать набор данных из тех записей базы данных, в которых поле "Должность" содержит значение "преподаватель", можно использовать такой обработчик: procedure TForm1.Table1FilterRecord(DataSet: TDataSet; var Accept: Boolean); begin Accept := DataSet['Должность'] = 'преподаватель'; end; Еще один пример: отфильтровать базу "Сотрудники" по условию "Отобрать всех, у кого табличный номер (поле "#") больше значения, вводимого пользователем в Edit1, и в поле "ФИО" есть подстрока символов, вводимых пользователем в Edit2":
procedure TForm1.Table1FilterRecord(DataSet: TDataSet; var Accept: Boolean); begin Accept := (DataSet['#'] > Edit1.Text) and (Pos(Edit2.Text, DataSet['ФИО']) > 0); end; Если в строке Filter и обработчике события OnFilterRecord заданы разные критерии фильтрации, выполняются оба.
Методы расширенной фильтрации
1. Методы фильтрации
Помимо описываемых ниже методов, присущих только TTable, наборы данных имеют также общие свойства, методы и события для фильтрации - Filter, Filtered, OnFilteredRecord, FindFirst, FindLast, FindNext, FindPrior.
Для фильтрации записей TTable имеет следующие методы: SetRangeStart - устанавливает нижнюю границу фильтра; EditRangeEnd - утанавливает верхнюю границу фильтра; ApplyRange - осуществляет фильтрацию записей в TTable; SetRange - имеет тот же эффект, что и последовательное выполнение методов SetRangeStart, EditRangeEnd и ApplyRange. В качестве параметров используются массивы констант, каждый из которых содержит значения ключевых полей.
Фильтрация методами ApplyRange, SetRange должно проводиться по ключевым полям.По умолчанию берется текущий индекс, определяемый свойством TTable.IndexName или TTable.IndexFieldNames. Если значения этих свойств не установлены, по умолчанию используется главный индекс. Поэтому, если нужно использовать индекс, отличный от главного, необходимо явно переустановить значение свойства TTable.IndexName (имя текущего индекса) или TTable.IndexFieldNames (список полей текущего индекса).
2. Использование SetRange
Метод procedure SetRange( const StartValues, EditValues: array of const); показывает не только записи, индексные поля которых лежат в диапазоне [StartValues..EndValues].
Пример: Пусть в наборе данных Table1 показываются все записи. Включим в структуру записи набора данных два поля: "Номер группы" и "Наименование товара". Пусть текущий индекс построен по полю "Номер группы". Напишем такой обработчик события: CheckBox1.Click: procedure TForm1.CheckBox1Click(Sender: TObject); var GrNumTmp: Integer; begin If CheckBox1.Checked then begin GrNumTmp := StrToInt(Edit1.Text); With Table1 do begin CancelRange; SetRange([GrMunTmp],[GrNumTmp]); end; end else Table1.CancelRange; end; В отфильтрованном наборе данных показываются только те записи, индексное поле текущего индекса у которых (в нашем случае "Номер группы") имеет значение, лежащее в заданном диапазоне. В данном случае диапазон определяется переменной GrNumTmp. Поэтому для GrNumTmp = 3 будут показаны записи, принадлежащие к группе 3.
Если бы мы захотели, чтобы в наборе данных фильтровались записи из нескольких групп, то нам следовало бы добавить в форму второй компонент Edit2, в котором вводился бы номер конечной группы, в то время как в Edit1 вводился бы номер начальной группы: procedure TForm1.CheckBox1Click(Sender: TObject); var GrNumTmp1, GrNumTmp2: Integer; begin If CheckBox.Checked then begin GrNumTmp1 := StrToInt(Edit1.Text); GrNumTmp2 := StrToInt(Edit2.Text); With Table1 do begin CancelRange; SetRange([GrNumTmp1],[GrNumTmp2]); end; end else Table1.CancelRange; end;
Александр Мефодьев, ICQ 56666220 31 января 2000г. Специально для
Работа с локальной сетью - NetShareAdd
Долго и упорно мучался по освоению этой функции и, наконец, решил эту проблему (не без помощи добрых людей :).
unit Unit1; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls; type TForm1 = class(TForm) Button1: TButton; procedure Button1Click(Sender: TObject); end; Share_INFO_2= record shi2_netname:PWideChar; // ОБЯЗАТЕЛЬНО PWideChar иначе работать не будет... shi2_type:DWORD; shi2_remark:LPTSTR; shi2_permissions:DWORD; shi2_max_uses:DWORD; shi2_current_uses:DWORD; shi2_path:PWideChar; shi2_passwd:LPTSTR; end; PShare_INFO_2 = ^Share_INFO_2; LPShare_INFO_2 = ^Share_INFO_2; SHARE_INFO_502 =record shi502_netname: PCHAR; shi502_type: DWORD; shi502_remark: PCHAR; shi502_permissions: DWORD; shi502_max_uses: DWORD; shi502_current_uses: DWORD; shi502_path: PCHAR; shi502_passwd: PCHAR; shi502_reserved: DWORD; shi502_security_descriptor: PSECURITY_DESCRIPTOR; end; PSHARE_INFO_502= ^SHARE_INFO_502; LPSHARE_INFO_502=^SHARE_INFO_502; const STYPE_DISKTREE = $0001; ACCESS_READ = $0001; var Form1: TForm1; F:Cardinal; function NetShareAdd( Server : PwideChar; level : cardinal; Buf : Pointer; var Parm_Err : DWORD):Cardinal;stdcall; external 'netapi32.dll' name 'NetShareAdd'; implementation {$R *.DFM} procedure TForm1.Button1Click(Sender: TObject); var buf:Pointer; UserInf:Share_INFO_2; UserInf502:Share_INFO_502; err:dWord; begin err:=0; f:=0; UserInf.shi2_netname:='test'; UserInf.shi2_type:=0; UserInf.shi2_remark:='test'; UserInf.shi2_permissions:=1; UserInf.shi2_max_uses:= 1; UserInf.shi2_current_uses:=0; UserInf.shi2_path:='C:\test'; UserInf.shi2_passwd:=nil; GetMem(Buf ,sizeof(UserInf)); F:=NetSHAREAdd(nil,2,@UserInf,err); FreeMem(Buf); end; end.
Расширение возможностей стандартной функции MessageDlg
рякин Руслан, дата публикации 04 июля 2003г. |
Функция TimedMessageBox представляет собой расширение возможностей стандартной функции MessageDlg (большая часть кода взята из нее же). Дополнительной является возможность закрытия окна сообщения по таймеру без участия пользователя (в случае его отсутствия за компьютером).
Может пригодиться при длительных обработках, когда нужно и вывести какое-либо сообщение пользователю, и продолжить работу, даже если он его проигнорировал (например в файловых менеджерах при копировании большого количества файлов: если какой-то из них не читается, весь процесс останавливается, хотя логичнее было бы просто пропустить этот файл и записать информацию об ошибке в лог, если пользователь не ответил на сообщение).
Скачать: (230K) В архиве содержится сам модуль, демонстрационная программа (dpr и exe) и картинки, используемые в кнопках.
| Для данного материала нет комментариев. |
Реализация шаблонов в Delphi
| Раздел Сокровищница | ркуша Алексей, дата публикации 07 сентября 2001г. |
Многие скажут что сабж невозможен. Но...посмотрите что у меня получилось (На примере простого списка).
Итак. Необходимо создать два пустых ((Через File-> New->Text или в файловой системе) без interface, implementation, uses... и т.д.) .pas файла. Первый назовем InterfaceTemp.pas(заголовок), второй ImplementTemp.pas(реализация). Далее копируем, соответственно, в них в InterfaceTemp.pas (заголовочный файл шаблона): TemplateList = class // заголовочный файл шаблона (для ordinal types или real types, shortstring) private FList: PIntList; FCount: Integer; FCapacity: Integer; protected procedure Grow; function Get(Index: Integer): _DATA_TYPE_; // Вот оно чудо :-) procedure Put(Index: Integer; Item: _DATA_TYPE_); procedure SetCapacity(NewCapacity: Integer); procedure SetCount(NewCount: Integer); public destructor Destroy; override; class procedure Error(const Msg: string; Data: Integer); overload; virtual; class procedure Error(Msg: PResStringRec; Data: Integer); overload; function Add(Item: _DATA_TYPE_): Integer; procedure Clear; function Last: _DATA_TYPE_; function First: _DATA_TYPE_; procedure Delete(Index: Integer); procedure Exchange(Index1, Index2: Integer); function IndexOf(Item: _DATA_TYPE_): Integer; procedure Insert(Index: Integer; Item: _DATA_TYPE_); procedure Move(CurIndex, NewIndex: Integer); procedure Sort; function Min: _DATA_TYPE_; function Max: _DATA_TYPE_; property Count: Integer read FCount write SetCount; property Items[Index: Integer]: _DATA_TYPE_ read Get write Put; default; end; в ImplementTemp.pas (файл реализации шаблона): function TemplateList.Add(Item: _DATA_TYPE_): Integer; begin Result := FCount; if Result = FCapacity then Grow; FList^[Result] := Item; Inc(FCount); end; procedure TemplateList.Clear; begin SetCount(0); SetCapacity(0); end; procedure TemplateList.Delete(Index: Integer); begin if (Index < 0) or (Index >= FCount) then Error(@SListIndexError, Index); Dec(FCount); if Index < FCount then System.Move(FList^[Index + 1], FList^[Index], (FCount - Index) * SizeOf(_DATA_TYPE_)); end; destructor TemplateList.Destroy; begin Clear; end; procedure TemplateList.Exchange(Index1, Index2: Integer); var Item: _DATA_TYPE_; begin if (Index1 < 0) or (Index1 >= FCount) then Error(@SListIndexError, Index1); if (Index2 < 0) or (Index2 >= FCount) then Error(@SListIndexError, Index2); Item := FList^[Index1]; FList^[Index1] := FList^[Index2]; FList^[Index2] := Item; end; function TemplateList.Get(Index: Integer): _DATA_TYPE_; begin if (Index < 0) or (Index >= FCount) then Error(@SListIndexError, Index); Result := FList^[Index]; end; procedure TemplateList.Grow; var Delta: Integer; begin if FCapacity > 64 then Delta := {371053//}FCapacity div 4 else if FCapacity > 8 then Delta := 16 else Delta := 4; SetCapacity(FCapacity + Delta); end; function TemplateList.IndexOf(Item: _DATA_TYPE_): Integer; begin Result := 0; while (Result < FCount) and (FList^[Result] <> Item) do Inc(Result); if Result = FCount then Result := -1; end; procedure TemplateList.Insert(Index: Integer; Item: _DATA_TYPE_); begin if (Index < 0) or (Index > FCount) then Error(@SListIndexError, Index); if FCount = FCapacity then Grow; if Index < FCount then System.Move(FList^[Index], FList^[Index + 1], (FCount - Index) * SizeOf(_DATA_TYPE_)); FList^[Index] := Item; Inc(FCount); end; function TemplateList.Max: _DATA_TYPE_; var i: Integer; begin if Fcount=0 then Error(@SListCountError, 0); Result:=Flist^[0]; for i:=0 to Fcount-1 do if Result < Flist^[i] then Result:=Flist^[i]; end; function TemplateList.Min: _DATA_TYPE_; var i: Integer; begin if Fcount=0 then Error(@SListCountError, 0); Result:=Flist^[0]; for i:=0 to Fcount-1 do if Result>Flist^[i] then Result:=Flist^[i]; end; procedure TemplateList.Move(CurIndex, NewIndex: Integer); var Item: _DATA_TYPE_; begin if CurIndex <> NewIndex then begin if (NewIndex < 0) or (NewIndex >= FCount) then Error(@SListIndexError, NewIndex); Item := Get(CurIndex); Delete(CurIndex); Insert(NewIndex, Item); end; end; procedure TemplateList.Put(Index: Integer; Item: _DATA_TYPE_); begin if (Index < 0) or (Index >= FCount) then Error(@SListIndexError, Index); FList^[Index] := Item; end; procedure TemplateList.SetCapacity(NewCapacity: Integer); begin if (NewCapacity < FCount) or (NewCapacity > MaxListSize) then Error(@SListCapacityError, NewCapacity); if NewCapacity <> FCapacity then begin ReallocMem(FList, NewCapacity * SizeOf(_DATA_TYPE_)); FCapacity := NewCapacity; end; end; procedure TemplateList.SetCount(NewCount: Integer); begin if (NewCount < 0) or (NewCount > MaxListSize) then Error(@SListCountError, NewCount); if NewCount > FCapacity then SetCapacity(NewCount); if NewCount > FCount then FillMemory(@(FList^[FCount]), (NewCount - FCount) * SizeOf(_DATA_TYPE_),0); FCount := NewCount; end; procedure QuickIntSort(ia: PIntList; iLo,iHi : integer); var Lo, Hi : Integer; // индексы Mid, T : _DATA_TYPE_; // значения begin Lo := iLo; Hi := iHi; Mid := ia[(Lo+hi) shr 1]; repeat while ia[Lo] < Mid do Inc(Lo); while ia[Hi] > Mid do Dec(Hi); if Lo Hi; if Hi > iLo then QuickIntSort(ia,iLo,Hi); if Lo < iHi then QuickIntSort(ia,Lo,iHi); end; procedure TemplateList.Sort; begin if (FList <> nil) and (FCount > 0) then QuickIntSort(FList, 0, FCount - 1); end; class procedure TemplateList.Error(const Msg: string; Data: Integer); function ReturnAddr: Pointer; asm MOV EAX,[EBP+4] end; begin raise Exception.CreateFmt(Msg, [Data]) at ReturnAddr; end; class procedure TemplateList.Error(Msg: PResStringRec; Data: Integer); begin TemplateList.Error(LoadResString(Msg), Data); end; function TemplateList.Last: _DATA_TYPE_; begin Result := Get(FCount - 1); end; function TemplateList.First: _DATA_TYPE_; begin Result := Get(0); end; Теперь необходимо создать файл для так называемого "typedef" (Файл указания конкретного типа). На примере типа Currency (ImplCurrencyList.pas), для другого типа создайте еще один файл с другим названием, например (ImplIntegerList.pas)
Итак Currency:
unit ImplCurrencyList; interface uses windows, sysutils; {$H-} // длинные строки недопустимы type _DATA_TYPE_ = Currency; // здесь указывается настоящий тип {$H+} const MaxListSize = Maxint div (4*sizeof(_DATA_TYPE_)); type PIntList = ^TIntList; TIntList = array[0..MaxListSize - 1] of _DATA_TYPE_; {$I InterfaceTemp} // соответственно тип уже обозначен и реален type TCurrencyList = TemplateList; // здесь задается тип реального класса списка implementation uses Consts; {$I ImplementTemp} // соответственно тип уже обозначен и реален end.
Вот собственно и все. Теперь подключате модуль нужного типа uses ImplCurrencyList или ImplIntegerList.
И
var cyr: TCurrencyList или var intlist: TIntegerList; где то. (если вы создали два или более "typedef" файла для других типов byte, Extended).
Данный пример работает с обычными типами данных (не объектными). Для объектов можно завернуть "перегрузку операторов" Например: TBase = class // это пример для дальнейших обсуждений function doPlus(value: TBase): TBase; overload; // для выполнения оператора+ (если конечно договорится, что doPlus подразумеывает оператор +) function doPlus(value: integer): TBase; overload; function doPlus(value: real): TBase; overload; end; Соответсвенно где то в шаблоне (очень примитивный пример) function TemplateClass.Add(Item: _DATA_TYPE_): Integer; // например TemplateClass и _DATA_TYPE_ есть TBase; begin self.doPlus(Item); {для TBase} Result:=self.a; // какое-то внутреннее поле (пример) end; Вот такие дела в Delphi творятся. Любая критика и предложения принимаются.
Скачать пример (6.2K)
Регистрация приложения в SimpleService в Win9x
Функция регистрирует свое приложение (откуда вызвана) в SimpleService в Win9x (не будет видно в TaskManager и может работать до того, как пользователь вошел в систему, запуская из ключа реестра HKLM\Software\Microsoft\Windows\CurrentVersion\RunServices или HKLM\...\RunServicesOnce и продолжает работать после окончания сессии пользователя)
Булевый параметр - включение или выключение режима Возвращаемое значение - True в случае успеха
Особенность - функция не критична к операционной системе, программа запустится даже под WinNT (где такая функция не существует в принципе), а результат работы будет False.
function RegisterServiceProcessEx(Enable: boolean): boolean; type TRSP = function (H: THandle; K: dword): dword; stdcall; var RSP: TRSP; begin @RSP := GetProcAddress(GetModuleHandle(PChar('kernel32.dll')), PChar('RegisterServiceProcess')); Result := Assigned(@RSP); if Result then begin if Enable then Result := (RSP(0, 1) = 1) else Result := (RSP(0, 0) = 1); end; end;
Алексей Еремеев
Системное меню по произвольному событию в произвольном месте
Вот недавно хотел показать системное меню по произвольному событию в произвольном месте, читал хелп по WinAPI, поискал у вас - не нашел ответ, повозился и обнаружил что нас обманывают и TrackPopupMenu может возвращать не только LongBool (Windows.pas) или Return Values - If the function succeeds, the return value is nonzero. (Win32 Develo... Help) procedure TForm1.Button1Click(Sender: TObject); var LItem : LongWord; LMenu : HMENU; begin LMenu := GetSystemMenu(Handle,false); LItem := LongWord(Windows.TrackPopupMenu(LMenu, TPM_LEFTBUTTON or TPM_RIGHTBUTTON or TPM_RETURNCMD, 100, 100, 0 , Handle, nil)); if LItem>0 then SendMessage(Handle,WM_SYSCOMMAND,LItem,0); end;
Может кому пригодится....
Событие OnFilterRecord
Событие OnFilterRecord возникает при установке значения True в свойство Filtered. Обработчик события имеет два параметра: имя фильтруемого набора данных и переменную Accept, в которую программа должна поместить True, если текущая запись удовлетворяет условию фильтрации.
В отличие от критерия в строке Filtered, ограниченного рамками синтаксиса условного выражения, критерий, реализуемый в обработчике события OnFilterRecord, определяется синтаксисом Object Pascal и может организовать сложные алгоритмы фильтрации. Однако следует помнить, что в обработчие OnFilterrecord последовательно перебираются все записи БД, в то время как методы SetRange, ApplyRange и им сопутствующие методы компонента TTable используют индексно-последовательный метод, т.е. работаютс частью записей в физической БД. Это делает использование обработчика OnFilterRecord предпочтительным для фильтрации небольших объемов записей и сильно ограничивает его приминение при больших объемах данных.
Всякий раз, когда приложение обрабатывает событие OnFilterRecord, набор данных переводится из состояния dsBrowse в состояние dsFilter. Это предотвращает модификацию набора данных во время фильтрации. После завершения текущего вызова обработчика события ObFilterRecord набор данных переводится в состояние dsBrowse.
Пример: чтобы создать набор данных из тех записей базы данных, в которых поле "Должность" содержит значение "преподаватель", можно использовать такой обработчик:
|
|
| procedure TForm1. Table1FilterRecord(DataSet: TDataSet; var Accept: Boolena); begin Accept := DataSet['Должность'] = 'преподаватель'; end; |
Еще один пример: отфильтровать базу "Сотрудники" по условию "Отобрать всех, у кого табличный номер (поле "#") больше значения, вводимого пользователем в Edit1, и в поле "ФИО" есть подстрока символов, вводимых пользователм в Edit2":
|
|
| procedure TForm1.Table1FilterRecord(DataSet: TDataSet; var Accept: Boolena); begin Accept := (DataSet['#'] > Edit1.Text) and (Pos(Edit2.Text, DataSet['ФИО']) > 0); end; |
Если в строке Filter и обработчике события OnFilterRecord заданы разные критерии фильтрации, выполняются оба.
Сокровищница:
Секреты ListBox. | Раздел Сокровищница | Автор статьи Кейт Вуд, Delphi Developer 11/99, Перевод с английского: Владимир Татарчевский. дата публикации 01.12.99 |
Предисловие не от автора Предлагаемый материал показывает два способа, которыми можно представить данные в компоненте ListBox в виде нескольких колонок. Задача эта не представляется сложной и поэтому мне хотелось бы кое-что пояснить перед прочтением этой страницы. Статья с названием "Секреты ListBox" была опубликована в ноябрьском номере журнала "Delphi Developer". Помещая ее перевод в Сокровищницу мы преследовали две цели: во-первых ответить на вопрос о нескольких колонках и, во-вторых, желая повеселить наших жителей романтичным повествованием на тему "Хотелось ли вам когда-либо отображать табулированный список...?". Автор статьи представлен как независимый технический писатель и программист-аналитик. Оставим на его совести выражения типа "Другая малоизвестная особенность ListBox заключается в том, что он может отображать несколько колонок...", возможно эта особенность и малоизвестна, если не заглядывать в Help, не говоря уже о наличии свойства Columns прямо в ObjectInspector'е. Но несмотря ни на что, ответ на вопрос "как отображать в ListBox несколько колонок" эта статья дает исчерпывающий. Приятного чтения! Секреты ListBox
ListBox - скромный компонент, появившийся еще в Delphi 1.0. Он показывает список строк и позволяет вам выбрать одну или несколько из них. Однако, как показывает в этой статье Кейт Вуд (Keith Wood), этот компонент имеет редко используемые возможности, которые могут сделать интерфейс вашего приложения более информативным.
Хотелось ли вам когда-либо отображать табулированный список - несколько колонок текста? Как же нам сделать ровные колонки? Вы можете попробовать использовать пробелы, но такой метод не будет работать с пропорциональными шрифтами. Вы можете сделать owner-draw и сформировать колонки самостоятельно. Но вся эта работа не нужна! ListBox уже имеет свойство, которое позволит разершить данную ситуацию. Свойство TabWidth устанавливает табуляционные интервалы в ListBox. Они измеряются в единицах диалогового окна (dialog box unit), четыре единицы равны сердней ширине символа. Когда это свойство установлено в 0 (по умолчанию), интервалы табуляции в ListBox отсутствуют и символы табуляции отображаются в виде вертикальных линий. Установленное в ненулевое значение, это свойство определяет расстояние между табуляционными метками, расставленными по всей ширине ListBox. Колонки различной ширины
Что если вы захотите сделать колонки переменной ширины? Разумеется, нет свойства, позволяющего вам легко сделать это, но это легко делается с помощью кода. Все, что вам нужно сделать - это послать сообщение LB_SETTABSTOPS, передав количество табуляций и указатель на массив с их позициями:
const iStops : array [1..3] of Integer = ( 20, 60, 80 ); begin SendMessage( ListBox1.Handle, LB_SETTABSTOPS, High( iStops ), LongInt( @iStops )); end;
Функция High возвращает индекс последнего элемента в массиве. Если массив индексирован с единицы, вы можете увеличивать количество табуляций, просто добавляя к массиву новые элементы. Вы также должны установить свойство TabWidth в ненулевое значение, только в этом случае ListBox будет готов принять это сообщение. Для вставки символа табуляции в ваш текст просто используйте ASCII-символ 9, к примеру так:
ListBox1.Items.Add( Format( '%d'#9'%s'#9'%0:d'#9'%s', [i, Chr( i + Ord( 'A' ) - 1 )] ) ); Несколько колонок
Другая малоизвестная особенность ListBox заключается в том, что он может отображать несколько колонок, стоит вам установить их количество в свойстве Columns. Теперь строки в ListBox будут расположены по типу газетных столбцов, с горизонтальной полосой прокрутки, появляющейся при необходимости. Строки, не помещающиеся в отведенное для них место обрезаются.
Обе эти возможности просто изменяют вид ListBox. Все остальные функции компонента при этом работают как обычно.
Рисунок 1 показывает пример данных возможностей. Верхний ListBox имеет установленные табуляционные интервалы, нижний ListBox имеет несколько колонок.
Заключение
Итак, наш скромный ListBox имеет скрытые таланты. Мы увидели, как сделать табулированный список с помощью свойства TabWidth и сообщения LB_SETTABSTOPS. Мы также увидели, как создать мультиколонный список с помощью свойства Columns. Запомните это до следующего раза, когда вы будете использовать ListBox.
Кейт Вуд - австралиец, находящийся в данное время в США. Он программист-аналитик в фирме CCSC, базирующейся в Атланте и независимый технический писатель. Его опыт работы с продуктами Borland ведет свое начало с Turbo Pascal для CP/M.
Создание базы данных Interbase во время выполнения программы.
| Раздел Сокровищница | ров Алексей, дата публикации 01 августа 2002г. | Список используемых имен : DBCreationScript : TIBDataBase;Для создания базы данных необходим экземпляр TIBDataBase, причем Connected = false TSCreationScript : TIBTransaction;Этот экземпляр TIBTransaction необходимо связать с DBCreationScript DSCreationScript : TIBSql;С помощью TIBSql мы просто последовательно выполняем инструкции, перечисленные в SQLScript, фрагмент из которого смотрите в самом низу... SQLScript : TMemo;Старый добрый Memo, который содержит скрипт всей нашей базы данных... Procedure TIBCreationOrder.CreateNewDatabase(Path, User, Pass : String); Var InstructionsList : TStringList; Index, Jndex : Integer; Instruction, Params : String; Begin Screen.Cursor := crHourGlass; With DBCreationScript Do Begin {на этом этапе connected = false} Params.Clear; DataBaseName := Path; Params.Add('USER "' + User + '"'); Params.Add('PASSWORD "' + Pass + '"'); Params.Add('DEFAULT CHARACTER SET WIN1251;'); CreateDataBase; {тут база данных становится активной, опять необходим connected = false} If Connected Then Connected := False; Params.Clear; Params.Add('user_name=' + User); Params.Add('password=' + Pass); {мы создали каркас БД и определились с владельцем, кстати, не забудьте перед этим прописать его в системе} Connected := True; End; InstructionsList := TStringlist.Create; {Обычный TStringlist, каждый элемент которого - отдельная инструкция из SQLScript. В качестве разделителя я использовал #... } Params := Trim(SQLScript.Text); Jndex := 1; For Index := 1 To Length(Params) Do If Params[Index] = '#' Then Begin Instruction := Copy(Params, Jndex, Index - Jndex); InstructionsList.Append(Trim(Instruction)); Jndex := Index + 1; End; TSCreationScript.Active := True; {Активизируем транзакцию и начинаем последовательно создавать нашу БД. Кстати, следите за логикой в самом скрипте. Например, не объявляйте триггеры до создания таблицы :)} With DSCreationScript Do Begin For Index := 0 To InstructionsList.Count - 1 Do Begin {Выполняем каждую инструкцию отдельно. Очень полезно для отлавливания ошибок...} Close; SQL.Clear; SQL.Add(InstructionsList.Strings[Index]); ExecQuery; TSCreationScript.Commit; TSCreationScript.Active := True; {Каждую инструкцию надо подтвердить} End; End; TSCreationScript.Commit; InstructionsList.Free; If DBCreationScript.Connected Then DBCreationScript.Connected := False; Screen.Cursor := crDefault; {База данных со всей бизнес логикой готова} End; Отрывок из содержимого SQLScript : # - разделитель инструкций Sql; Можете ставить, какой нравится... CREATE TABLE MAILTREE( CODE INTEGER NOT NULL PRIMARY KEY, APARENT VARCHAR(255), ACURRENT VARCHAR(255));# CREATE TABLE MAILBASE( CODE INTEGER NOT NULL PRIMARY KEY, FOLDER VARCHAR(255), ISUNREAD SMALLINT DEFAULT 0, AUTHOR VARCHAR(255), SENDERNAME VARCHAR(1000), SENDERSTYLE VARCHAR(1000), RECIPIENT VARCHAR(1000), SUBJECT VARCHAR(255), MSGSIZE DECIMAL(10, 5), MSGRECIEVED VARCHAR(50), ATTACHMENTS SMALLINT, FILELIST VARCHAR(1000), MSGBODY BLOB);# CREATE GENERATOR MTCODE;# SET GENERATOR MTCODE TO 0;# CREATE GENERATOR MBCODE;# SET GENERATOR MBCODE TO 0;# CREATE TRIGGER ADD_MAILTREE FOR MAILTREE ACTIVE BEFORE INSERT AS BEGIN NEW.CODE = GEN_ID(MTCODE, 1); END;# CREATE TRIGGER ADD_MAILBASE FOR MAILBASE ACTIVE BEFORE INSERT AS BEGIN NEW.CODE = GEN_ID(MBCODE, 1); END;# CREATE TRIGGER UPDATE_MAILTREE FOR MAILTREE ACTIVE BEFORE UPDATE AS BEGIN IF (OLD.ACURRENT <> NEW.ACURRENT) THEN UPDATE MAILBASE SET FOLDER = NEW.ACURRENT WHERE FOLDER = OLD.ACURRENT; END;#
Создание системы голосовых сообщений из подручных средств
Смотря, очередной фантастический боевик, в котором “умная” система управления приятным женским или мужским голосом сообщает герою, что если он не выполнит какие-то важные действия то дальше ему (ей) придется передвигаться без удобств на своих двоих или вообще дожидаться завершения фильма в виде горстки атомов. Вы не раз задумывались, вот бы сделать себе подобную сообщалку о всяческих опасных ситуациях возникающих в вереной Вам компьютерной системе или сети. Наметим себе цели, которые мы хотим достичь: организовать проверку критических для нашей системы событий; голосовые сообщения о них; не очень перетрудится.
События, которые можно проверять ограничиваются исключительно Вашей фантазией. Можно организовать каждые 5 минут с 9:00 до 18:00 c перерывом на обед посылку сигнала PING на конечные компьютеры сети и если не получен ответ поднимать тревогу. Каждые пол часа проверять количество свободного места на HDD. Наличие одновременно включенного (и при этом работающего) комплекта аппаратных средств (сервера, мосты, печатающие устройства). Или организовать на компьютере шефа проверку базы данных с информацией о заработной плате, если она не увеличилась в течении месяца на оговоренное заранее число “президентов” сообщать шефу, мнение о нем как о руководителе и о фирме в целом. На простых пользователей производит неизгладимое впечатление, когда компьютер сообщает им о том, что они посмели запустить ПО на которое у них нет позволения. Практически у каждого уважающего себя производителя компьютерного железа в стандартной конфигурации присутствует звуковая карта, которая наиболее часто используются исключительно для воспроизведения audio CD и МР3 файлов. Дополнительно из техники нужно будет приобрести микрофон. Для облегчения себе задачи в вопросы синтезирования речи вникать не будем, а необходимые нам сообщения попросту запишем в файлы WAV при помощи стандартной программы Windows Звукозапись (понятно, что для этого нужно микрофон воткнуть в соответствующее гнездо звуковой карты). Как видно без необходимости контролировать время наступления события не обойтись поэтому для сокращения затрат сил и времени все задачи отслеживания времени для наступления проверки условий возложим на почему-то незаслуженно игнорируемую стандартную программу Windows планировщик заданий. Это такая маленькая пиктограмма справа внизу экрана .
Естественно никто не запрещает Вам написать эту часть кода программы, самим, увеличивая размер исполнительного модуля, но думаю программисты фирмы MICROSOFT сделали это лучше .
Для воспроизведения WAV файлов используем описанную почти во всех солидных учебниках по программированию на DELPHI функцию WIN API BOOL sndPlaySound(LPCSTR lpszSound, UINT fuSound);, полное описание которой находится в файле WIN32S.HLP
Откроем новый проект (да будет прощено мне WINDOWхульство) для уменьшения размера уничтожим в файле проекта код, отвечающий за создание окна
Application.Initialize; Application.CreateForm(TForm1, Form1); Application.Run;
Удаление этого кода даст нам дополнительный эффект: активное окно не будет терять фокус при воспроизведении звукового сообщения. В качестве примера проверим наличие файлов в указанной папке при помощи простого кода
| rez:=FindFirst(‘c:/*.*’, faAnyFile-16, SearchRec); FindClose(SearchRec); IF rez=0 then {--Возпроизведение звука---} sndplaysound(pchar(‘mysound.wav’),SND_SYNC); | Полученный после компиляции исполнительный файл регистрируем в планировщике заданий с заданными временными параметрами. Теперь продолжим освоения своей любимой игровой программы (надеюсь, простые пользователи об этом не знают ведь компьютерные небожители и, в игры не играют), при возникновении отслеживаемой ситуации Вы первый об этом услышите. Существует альтернативный путь, описать сообщения которым можно присвоить звуки в реестре WINDOWS вызывая их потом из программы. Но для этого нужно иметь познания в структуре реестра и WIN API.
Простой пример программы анализа наличия файлов в папке.
| program sexsot; uses Forms,Windows,SysUtils,MMsystem,inifiles,classes, Unit1 in 'Unit1.pas' {Form1}; {$R *.RES} var REZ,i:integer; erasefolder:Tstringlist; inifilefolder:tinifile; strpath,strmax,strcount,strsound:string; SearchRec: TSearchRec; begin if FileExists(paramstr(1)) then begin erasefolder:=Tstringlist.create; inifilefolder:=Tinifile.create(extractfilepath(paramstr(0))+paramstr(1)); inifilefolder.readsections(erasefolder); for i:=0 to erasefolder.count-1 do begin strpath:=inifilefolder.readstring(erasefolder[i],'Pathlook','C:\*.*'); strmax:=inifilefolder.readstring(erasefolder[i],'maxcount','1'); strcount:=inifilefolder.readstring(erasefolder[i],'count','0'); strsound:=inifilefolder.readstring(erasefolder[i],'filesound','1.wav'); {---- Проверка наличия файлов------} rez:=0; rez:=FindFirst(strpath, faAnyFile-16, SearchRec); FindClose(SearchRec); {---------------------------------------------} if rez=0 then begin inifilefolder.writestring(erasefolder[i],'count',inttostr(strtoint(strcount)+1)); end else begin inifilefolder.writestring(erasefolder[i],'count','0'); end; strcount:=inifilefolder.readstring(erasefolder[i],'count','0'); if strtoint(strcount)>=strtoint(strmax) then {--Возпроизведение звука---} sndplaysound(pchar(strsound),SND_SYNC); end; end; end. | Cтруктура файла INI
| [modem] PathLOOK=f:\mail\out\*.* filesound=nosend.wav maxcount=3 count=0 [email] PathLOOK=с:\unlx\out\*.* filesound=atasunlx.wav maxcount=2 count=0 | Виктор Ерко март 2003г.
| Для данного материала нет комментариев. |
Sqlw
| Техническая документация - список зарезервированных слов Local SQL BDE | | Версия: Все Платформа: Windows 3.1, Windows 95, Windows NT | | Список зарезервированных слов «Local SQL in the Borland Database Engine» в алфавитном порядке. ACTIVE, ADD, ALL, AFTER, ALTER, AND, ANY, AS, ASC, ASCENDING, AT, AUTO, AUTOINC, AVG BASE_NAME, BEFORE, BEGIN, BETWEEN, BLOB, BOOLEAN, BOTH, BY, BYTES CACHE, CAST, CHAR, CHARACTER, CHECK, CHECK_POINT_LENGTH, COLLATE, COLUMN, COMMIT, COMMITTED, COMPUTED, CONDITIONAL, CONSTRAINT, CONTAINING, COUNT, CREATE, CSTRING, CURRENT, CURSOR DATABASE, DATE, DAY, DEBUG, DEC, DECIMAL, DECLARE, DEFAULT, DELETE, DESC, DESCENDING, DISTINCT, DO, DOMAIN, DOUBLE, DROP ELSE, END, ENTRY_POINT, ESCAPE, EXCEPTION, EXECUTE, EXISTS, EXIT, EXTERNAL, EXTRACT FILE, FILTER, FLOAT, FOR, FOREIGN, FROM, FULL, FUNCTION GDSCODE, GENERATOR, GEN_ID, GRANT, GROUP, GROUP_COMMIT_WAIT_TIME HAVING, HOUR IF, IN, INT, INACTIVE, INDEX, INNER, INPUT_TYPE, INSERT, INTEGER, INTO, IS, ISOLATION JOIN KEY LONG, LENGTH, LOGFILE, LOWER, LEADING, LEFT, LEVEL, LIKE, LOG_BUFFER_SIZE MANUAL, MAX, MAXIMUM_SEGMENT, MERGE, MESSAGE, MIN, MINUTE, MODULE_NAME, MONEY, MONTH NAMES, NATIONAL, NATURAL, NCHAR, NO, NOT, NULL, NUM_LOG_BUFFERS, NUMERIC OF, ON, ONLY, OPTION, OR, ORDER, OUTER, OUTPUT_TYPE, OVERFLOW PAGE_SIZE, PAGE, PAGES, PARAMETER, PASSWORD, PLAN, POSITION, POST_EVENT, PRECISION, PROCEDURE, PROTECTED, PRIMARY, PRIVILEGES RAW_PARTITIONS, RDB$DB_KEY, READ, REAL, RECORD_VERSION, REFERENCES, RESERV, RESERVING, RETAIN, RETURNING_VALUES, RETURNS, REVOKE, RIGHT, ROLLBACK SECOND, SEGMENT, SELECT, SET, SHARED, SHADOW, SCHEMA, SINGULAR, SIZE, SMALLINT, SNAPSHOT, SOME, SORT, SQLCODE, STABILITY, STARTING, STARTS, STATISTICS, SUB_TYPE, SUBSTRING, SUM, SUSPEND TABLE, THEN, TIME, TIMESTAMP, TIMEZONE_HOUR, TIMEZONE_MINUTE, TO, TRAILING, TRANSACTION, TRIGGER, TRIM UNCOMMITTED, UNION, UNIQUE, UPDATE, UPPER, USER VALUE, VALUES, VARCHAR, VARIABLE, VARYING, VIEW WAIT, WHEN, WHERE, WHILE, WITH, WORK, WRITE YEAR OPERATORS: , -, *, /, <>, <, >, ,(comma), =, <=, >=, ~=, !=, ^=, (, ) |
| |
Странный Microsoft IIS или SSI своими руками
Server Side Include (SSI) полезная и удобная вещь. Только вот почему-то ограничены ее возможности в IIS всего несколькими директивами. И тем более странно ведет себя #exec если в качестве выполнимого скрипта подставить ISAPI написанную на Delphi. Ошибки выдаются разные, но смысл их один - Не могу выполнить скрипт, файл не найден :-( Данные мучения в связке IIS 5.0, Windows 2000 Prof, Delphi 6 Enterprise продолжались несколько дней, пока не родилась мысль написать свой SSI. Задача:Написать свой SSI. Условия:Имеем Microsoft IIS 5.0, Delphi Решение: ШАГ 1. Создание DLL которая служит интерпретатором SSI. Для этого в Delphi меню File/New/Other... Выбираем . В появившемся окне выбираем жмем кнопку OK.
В созданном WebModule добавляем одну Action Устанавливаем ее свойство Default:=True. Создаем обработчик события OnAction. Туда можно вставить например следующий код var i: TStringList; begin i:=TStringList.Create; i.LoadFromFile(Request.PathTranslated); Response.Content:= i.Text+ Request.PathTranslated; end; или такой var i: TStringList; begin Response.Content:= 'Пользователь запросил файл: ' + Request.PathTranslated; end; Компилируем модуль. Полученную DLL можно уложить в директорий C:\WINNT\system32\inetsrv\ ШАГ 2. Настройка IIS. Запускаем Internet Service Manager (для IIS 5.0) Щелкаем правой клавишей мышки на Web-сервере, выбираем Properties. В появившимся окне переходим на закладку HomeDirectory. Смотрим на раздел Apllication Settings. Если там доступна только кнопка Create, то нажимаем на нее. В противном случае только сверяем установки и при несоответствии с ними изменяем на необходимые. Выбираем в поле со списком Execute Permissions значение Scripts and Executables. Нажимаем на кнопку Configuration. Появляется окно, где необходимо в закладке App Mappings нажать кнопку Add. В поле Executable прописываем имя и путь к созданной dll. В поле Extension указываем расширение .html (или любое другое например .aaa) Устанавливаем группу переключателей Verbs в значение All Verbs. Устанавливаем флажок Script engine. Жмем кнопку Ok. Результат. Теперь при запросе у IIS файла с расширением .html (или которое вы указали выше) будет вызываться ваша DLL. А там уж что хотите с этим, то и делайте. Самое главное вызываемый файл может реально и не существовать.
С помощью свойств объекта Request можно получить локальное имя запрашиваемого файла: Request.PathTranslated (Например c:\winnt\demo.html). Также в свойствам данного объекта доступна куча другой информации (см. Help).
А где же SSI спросит Читатель? Так вот же! У Вас есть координаты запрашиваемого файла, с помощью средств Delphi обрабатывайте его и отсылайте клиенту.
Кстати используя данную технологию возможно создание очень интересной модели, когда файла самого на диске нет, а запрос пользователя используется только для поиска самого файла в базе данных. А если взглянуть на это чуть шире - ведь это путь создания динамического WWW-сайта с помощью одной DLL... ;-)
Пащенко Андрей (Big Bibigon) Архангельск, 2002.
Структура формул
Любая формула должна быть составлена с учетов некоторых правил, а также при ее составления необходимо знать из чего она может состоять. Функции, типы, а также другие составляющие приведены ниже: single: тип, означает вещественное 32 битное число double: тип, означает вещественное 64 битное число int64: тип, означает целое знаковое 64 битное число integer: тип, означает целое знаковое 32 битное число longword: тип, означает целое беззнаковое 32 битное число smallint: тип, означает целое знаковое 16 битное число word: тип, означает целое беззнаковое 16 битное число shortint: тип, означает целое знаковое 8 битное число byte: тип, означает целое беззнаковое 8 битное число bool: зарезервированное слово, обозначает логическое выражение. and : операнд, используется для связывания двух логических выражений. Аналогично логическому and в Delphi. or : операнд, используется для связывания двух логических выражений. Аналогично логическому or в Delphi. xor : операнд, используется для связывания двух логических выражений. Аналогично логическому xor в Delphi. not : операнд, меняет логическое значение на противоположное. > функция, если первое математическое выражение больше второго, то возвращает истину, в противном случае возвращает ложь. : функция, если первое математическое выражение меньше второго, то возвращает истину, в противном случае возвращает ложь. <>: функция, если первое математическое выражение не равно второму, то возвращает истину, в противном случае возвращает ложь. =>: функция, если первое математическое выражение больше или равно второму, то возвращает истину, в противном случае возвращает ложь. : функция, если первое математическое выражение меньше или равно второму, то возвращает истину, в противном случае возвращает ложь. =: функция, если первое математическое выражение равно второму, то возвращает истину, в противном случае возвращает ложь. true: функция. Возвращает истину. Это величина может принимать значение 1 false: функция. Возвращает ложь. Это величина может принимать значение 0 +: операнд, сложение -: операнд, вычитание *: функция, вычитание /: функция, деление sqrt: функция, возвращает квадратный корень числа div: функция, возвращает целочисленное деление mod: функция, возвращает остаток от деления int: функция, возвращает целая часть числа frac: функция, возвращает дробная часть числа random: функция, возвращает произвольное число в пределах от 0 до 1 trunc: функция, возвращает целую часть числа round: функция, округляет число arcsec: функция, возвращает арксеканс числа sec: функция, возвращает секанс числа arccsc: функция, возвращает арккосеканс числа csc: функция, возвращает косеканс числа arcsin: функция, возвращает арксинус числа sin: функция, возвращает синус числа arccos: функция, возвращает арккосинус числа cos: функция, возвращает косинус числа arctan: функция, возвращает арктангенс числа tan: функция, возвращает тангенс числа abs: функция, возвращает абсолютную величину числа ln: функция, возвращает натуральный логарифм числа lg: функция, возвращает десятичный логарифм числа log: функция, возвращает логарифм двух числа pi: функция, возвращает число Пи !: функция, возвращает факториал числа ^: функция, возвращает степень числа. Степень не может быть дробной. В любом случае логическая формула должна начинаться с зарезервированного слова "bool". Оно означает, что текущее выражение является логическим. В формуле можно использовать любое количество вложенных формул, которые представляют собой содержимое пары скобок, а содержимое каждой из этих пар скобок может быть как логическим выражением, так и математическим. Соответственно внутри скобок при обозначения логического выражения нужно также ставить зарезервированное слово "bool". Если его нет, то считается, что выражение является математическим. Например: "bool (2 log 4) = (4 sqrt 2) or (bool (2 * 2) = 4)". В формуле каждая функция должна быть заключена в круглые скобки. Что является функцией, а что нет можно узнать вышеприведенного списка. Как я уже сказал, логические выражения в некоторых случаях могут возвращать числовые значения. Это работает только в том случае, если выражение заключено в скобки, например: "bool (bool true) = 1". Логические выражения возвращающие истину принимают значение 1, а содержащие ложь - 0.
Структура сценария
Как уже было сказано, формула переводится в цифровой вид - сценарий. Существуют два вида сценариев - математический и логический. Математический сценарий может содержать внутри себя только математические сценарии, в то время как логический сценарий может содержать внутри себя как логические, так и математическии сценарии. Они по своей структуре очень близки друг к другу. У каждого сценария есть заголовок, который содерит некоторую начальную информацию: Сценарий начинается с результата. Это нужно для расчета вложенных сценариев. Тип помещаемого туда результата - тип Double. Кстати, в логическом сценарии результат занимает 4 байта и содержит логическое выражение. Это, конечно, очень нерационально тратить целых 4 байта на хранение логической переменной, но так проще. А основное внимание при создании этого модуля я уделял на достижении максимальной скорости вычисления формулы. Работа внутри модуля производится с вещественными числами, что, например, позволяет без проблем использовать тригонометрические функции. При расчете сценария сначала происходит расчет всех вложенных сценариев, в которых, в свою очередь, также происходит расчет вложенных сценариев и так далее. После расчета вложенного сценария в его начало записывается результат. Адреса вложенных сценариев задаются в байтах относительно начала содержащего их сценария. Сама формула при переводе в сценарий делится на части (единицы), которые определяются наличием положительго или отрицательного знака (в случае с логическим сценарием деление на единицы происходит несколько иначе, формула разбивается по логическим операндам xor, or, and). Единицы содержат в себе 3 составляющие: функции, числа и вложенные сценарии. Это также справедливо и для логического сценария. Функции классифицируются между собой. Они отличаются тем, что некоторые требуют до себя параметр (например факториал: "10!"), некоторые требуют после себя параметр (например косинус: "cos 0"), некоторые требуют и до и после себя параметры (например умножение: "2 * 2"), некоторые не требуют вообще никаких параметров (например число пи: "pi"). У них также есть общее свойство - они все возвращают какой-то результат. Это также касается и логических функций. Каждая единица начинается с заголовка: Заголовки логических и математических единиц идентичны, и те и другие имеют знак и тип. Если знак отрицательный ("not" в логических или "-" в математических единицах), то значение единицы инвертируется. В логических выражениях допускается использование операнда "not" любое количество раз. Тип единицы соответствует одному из типов в Delphi. Более подробно - чуть ниже. Вычисления будут производится соотвественно описанному типу. В логических единицах тип тоже присутствует, так как хоть они и не могут содержать математические выражения, но могут содержать числа, тип которых можно уточнить. В общей части находятся функции, числа, вложенные сценарии. Чтобы из можно было отличать друг от друга, перед каждым составляющим общей части единицы ставится идентификатор. Еще одно отличие логического сценария от математического состоит в том, что в логическом сценарии перед вложенным сценарием ставится идентификатор, уточняющий его тип. Это необходимо, так как логическая формула может содержать в себе как логические формулы, так и математические и при вычисленни их нужно отличать друг от друга.
Суть действий модуля
В памяти, соответствующей переменной типа "массив байтов", создается машинный код, соответствующий входной строке, после чего переменной типа "function:extended" присваивается адрес начала массива.
Свойство Filter
Свойство Filter компонента TTable позволяет задать критерий фильтрации. В этом случае база будет отфильтрована, как только свойство Filtered будет равно TRUE. Синтаксис описания критерия похож на синтаксис секции WHERE SQL-запроса с тем исключением, что имена переменных программы указывать нельзя, можно указывать имена полей и литералы (явно заданные значения); можно использовать обычные операции отношения и логические операторы AND, NOT и OR, например:
Эта запись фильтра оставит в таблице записи, в которых поля Doljnost='доцент' и TabNum больше 3000
|
|
| Filter:=([Doljnost]='доцент') and ([TabNum] > 3000); Filtered:=True; |
Строку критерия фильтрации можно ввести во время прогона программы или на этапе конструирования. Например, с помощью такого обработчика события OnChecked компонента CheckBox1 критерий фильтрации считывается из поля Edit1 и помещается в свойство Filter компонента Table1:
|
|
| procedure TForm1.CheckBox1Click(Sender: TObject); begin Table1.Filter := Edit1.Text; Table1.Filtered := CheckBox1.Checked; end; |
С помощью свойства
|
|
| type TFilterOption = (foCaseInsensitive, foNoPartialCompare); property FilterOptions: TFilterOptions; |
можно определить дополнительные свойства фильтрации строковых полей: foCaseInsensitive - фильтрация производится без учетра разницы регистра foNoPartialCompare - поиск производится на точное соответствие.
Так зачем же это нужно.
В силу своей огромной нескромности я полагаю, что кому-нибудь это все может быть интересно как пример непосредственного формирования кода в памяти и его исполнения.
Техническая документация - список ошибок BDE
| Раздел Сокровищница | источник информации: |
| Версия: Все Платформа: Windows 3.1, Windows 95, Windows NT | Этот документ содержит список всех ошибок, которые может возвращать BDE. Эта информация может быть получена из IDAPI.H (C++) или BDE.INT(C++ Builder and Delphi). Также можно посмотреть следующие документы, для получения дополнительной информации:
Примечание: Можно использовать DbiGetErrorString() для получения текста любой ошибки. Список ошибок: System Related (Fatal Error) 8449 : $2101 : Cannot open a system file. 8450 : $2102 : I/O error on a system file. 8451 : $2103 : Data structure corruption. 8452 : $2104 : Cannot find Engine configuration file. 8453 : $2105 : Cannot write to Engine configuration file. 8454 : $2106 : Cannot initialize with different configuration file. 8455 : $2107 : System has been illegally re-entered. 8456 : $2108 : Cannot locate IDAPI32 .DLL. 8457 : $2109 : Cannot load IDAPI32 .DLL. 8458 : $210A : Cannot load an IDAPI service library. 8459 : $210B : Cannot create or open temporary file. 8460 : $210C : Trying to load multiple IDAPIxx.DLL 8461 : $210D : Shared Memory Conflict Object of Interest not Found 8705 : $2201 : At beginning of table. 8706 : $2202 : At end of table. 8707 : $2203 : Record moved because key value changed. 8708 : $2204 : Record/Key deleted. 8709 : $2205 : No current record. 8710 : $2206 : Could not find record. 8711 : $2207 : End of BLOB. 8712 : $2208 : Could not find object. 8713 : $2209 : Could not find family member. 8714 : $220A : BLOB file is missing. 8715 : $220B : Could not find language driver. Physical Data Corruption 8961 : $2301 : Corrupt table/index header. 8962 : $2302 : Corrupt file - other than header. 8963 : $2303 : Corrupt Memo/BLOB file. 8965 : $2305 : Corrupt index. 8966 : $2306 : Corrupt lock file. 8967 : $2307 : Corrupt family file. 8968 : $2308 : Corrupt or missing .VAL file. 8969 : $2309 : Foreign index file format. I/O related error 9217 : $2401 : Read failure. 9218 : $2402 : Write failure. 9219 : $2403 : Cannot access directory. 9220 : $2404 : File Delete operation failed. 9221 : $2405 : Cannot access file. 9222 : $2406 : Access to table disabled because of previous error. Resource or Limit error 9473 : $2501 : Insufficient memory for this operation. 9474 : $2502 : Not enough file handles. 9475 : $2503 : Insufficient disk space. 9476 : $2504 : Temporary table resource limit. 9477 : $2505 : Record size is too big for table. 9478 : $2506 : Too many open cursors. 9479 : $2507 : Table is full. 9480 : $2508 : Too many sessions from this workstation. 9481 : $2509 : Serial number limit (Paradox). 9482 : $250A : Some internal limit (see context). 9483 : $250B : Too many open tables. 9484 : $250C : Too many cursors per table. 9485 : $250D : Too many record locks on table. 9486 : $250E : Too many clients. 9487 : $250F : Too many indexes on table. 9488 : $2510 : Too many sessions. 9489 : $2511 : Too many open databases. 9490 : $2512 : Too many passwords. 9491 : $2513 : Too many active drivers. 9492 : $2514 : Too many fields in Table Create. 9493 : $2515 : Too many table locks. 9494 : $2516 : Too many open BLOBs. 9495 : $2517 : Lock file has grown too large. 9496 : $2518 : Too many open queries. 9497 : $2519 : Too many threads for client. 9498 : $251A : Too many BLOBs. 9499 : $251B : File name is too long for a Paradox version 5.0 table. 9500 : $251C : Row fetch limit exceeded. 9501 : $251D : Long name not allowed for this tablelevel. 9502 : $251E : Insufficient shared memory available. Integrity Violation 9729 : $2601 : Key violation. 9730 : $2602 : Minimum validity check failed. 9731 : $2603 : Maximum validity check failed. 9732 : $2604 : Field value required. 9733 : $2605 : Master record missing. 9734 : $2606 : Master has detail records. Cannot delete or modify. 9735 : $2607 : Master table level is incorrect. 9736 : $2608 : Field value out of lookup table range. 9737 : $2609 : Lookup Table Open operation failed. 9738 : $260A : Detail Table Open operation failed. 9739 : $260B : Master Table Open operation failed. 9740 : $260C : Field is blank. 9741 : $260D : Link to master table already defined. 9742 : $260E : Master table is open. 9743 : $260F : Detail table(s) exist. 9744 : $2610 : Master has detail records. Cannot empty it. 9745 : $2611 : Self referencing referential integrity must be entered one at a time with no other changes to the table 9746 : $2612 : Detail table is open. 9747 : $2613 : Cannot make this master a detail of another table if its details are not empty. 9748 : $2614 : Referential integrity fields must be indexed. 9749 : $2615 : A table linked by referential integrity requires password to open. 9750 : $2616 : Field(s) linked to more than one master. 9751 : $2617 : Expression validity check failed. Invalid Request 9985 : $2701 : Number is out of range. 9986 : $2702 : Invalid parameter. 9987 : $2703 : Invalid file name. 9988 : $2704 : File does not exist. 9989 : $2705 : Invalid option. 9990 : $2706 : Invalid handle to the function. 9991 : $2707 : Unknown table type. 9992 : $2708 : Cannot open file. 9993 : $2709 : Cannot redefine primary key. 9994 : $270A : Cannot change this RINTDesc. 9995 : $270B : Foreign and primary key do not match. 9996 : $270C : Invalid modify request. 9997 : $270D : Index does not exist. 9998 : $270E : Invalid offset into the BLOB. 9999 : $270F : Invalid descriptor number. 10000 : $2710 : Invalid field type. 10001 : $2711 : Invalid field descriptor. 10002 : $2712 : Invalid field transformation. 10003 : $2713 : Invalid record structure. 10004 : $2714 : Invalid descriptor. 10005 : $2715 : Invalid array of index descriptors. 10006 : $2716 : Invalid array of validity check descriptors. 10007 : $2717 : Invalid array of referential integrity descriptors. 10008 : $2718 : Invalid ordering of tables during restructure. 10009 : $2719 : Name not unique in this context. 10010 : $271A : Index name required. 10011 : $271B : Invalid session handle. 10012 : $271C : invalid restructure operation. 10013 : $271D : Driver not known to system. 10014 : $271E : Unknown database. 10015 : $271F : Invalid password given. 10016 : $2720 : No callback function. 10017 : $2721 : Invalid callback buffer length. 10018 : $2722 : Invalid directory. 10019 : $2723 : Translate Error. Value out of bounds. 10020 : $2724 : Cannot set cursor of one table to another. 10021 : $2725 : Bookmarks do not match table. 10022 : $2726 : Invalid index/tag name. 10023 : $2727 : Invalid index descriptor. 10024 : $2728 : Table does not exist. 10025 : $2729 : Table has too many users. 10026 : $272A : Cannot evaluate Key or Key does not pass filter condition. 10027 : $272B : Index already exists. 10028 : $272C : Index is open. 10029 : $272D : Invalid BLOB length. 10030 : $272E : Invalid BLOB handle in record buffer. 10031 : $272F : Table is open. 10032 : $2730 : Need to do (hard) restructure. 10033 : $2731 : Invalid mode. 10034 : $2732 : Cannot close index. 10035 : $2733 : Index is being used to order table. 10036 : $2734 : Unknown user name or password. 10037 : $2735 : Multi-level cascade is not supported. 10038 : $2736 : Invalid field name. 10039 : $2737 : Invalid table name. 10040 : $2738 : Invalid linked cursor expression. 10041 : $2739 : Name is reserved. 10042 : $273A : Invalid file extension. 10043 : $273B : Invalid language Driver. 10044 : $273C : Alias is not currently opened. 10045 : $273D : Incompatible record structures. 10046 : $273E : Name is reserved by DOS. 10047 : $273F : Destination must be indexed. 10048 : $2740 : Invalid index type 10049 : $2741 : Language Drivers of Table and Index do not match 10050 : $2742 : Filter handle is invalid 10051 : $2743 : Invalid Filter 10052 : $2744 : Invalid table create request 10053 : $2745 : Invalid table delete request 10054 : $2746 : Invalid index create request 10055 : $2747 : Invalid index delete request 10056 : $2748 : Invalid table specified 10058 : $274A : Invalid Time. 10059 : $274B : Invalid Date. 10060 : $274C : Invalid Datetime 10061 : $274D : Tables in different directories 10062 : $274E : Mismatch in the number of arguments 10063 : $274F : Function not found in service library. 10064 : $2750 : Must use baseorder for this operation. 10065 : $2751 : Invalid procedure name 10066 : $2752 : The field map is invalid. Locking/Contention related 10241 : $2801 : Record locked by another user. 10242 : $2802 : Unlock failed. 10243 : $2803 : Table is busy. 10244 : $2804 : Directory is busy. 10245 : $2805 : File is locked. 10246 : $2806 : Directory is locked. 10247 : $2807 : Record already locked by this session. 10248 : $2808 : Object not locked. 10249 : $2809 : Lock time out. 10250 : $280A : Key group is locked. 10251 : $280B : Table lock was lost. 10252 : $280C : Exclusive access was lost. 10253 : $280D : Table cannot be opened for exclusive use. 10254 : $280E : Conflicting record lock in this session. 10255 : $280F : A deadlock was detected. 10256 : $2810 : A user transaction is already in progress. 10257 : $2811 : No user transaction is currently in progress. 10258 : $2812 : Record lock failed. 10259 : $2813 : Couldn't perform the edit because another user changed the record. 10260 : $2814 : Couldn't perform the edit because another user deleted or moved the record. Access Violation - Security related 10497 : $2901 : Insufficient field rights for operation. 10498 : $2902 : Insufficient table rights for operation. Password required. 10499 : $2903 : Insufficient family rights for operation. 10500 : $2904 : This directory is read only. 10501 : $2905 : Database is read only. 10502 : $2906 : Trying to modify read-only field. 10503 : $2907 : Encrypted dBASE tables not supported. 10504 : $2908 : Insufficient SQL rights for operation. Invalid context 10753 : $2A01 : Field is not a BLOB. 10754 : $2A02 : BLOB already opened. 10755 : $2A03 : BLOB not opened. 10756 : $2A04 : Operation not applicable. 10757 : $2A05 : Table is not indexed. 10758 : $2A06 : Engine not initialized. 10759 : $2A07 : Attempt to re-initialize Engine. 10760 : $2A08 : Attempt to mix objects from different sessions. 10761 : $2A09 : Paradox driver not active. 10762 : $2A0A : Driver not loaded. 10763 : $2A0B : Table is read only. 10764 : $2A0C : No associated index. 10765 : $2A0D : Table(s) open. Cannot perform this operation. 10766 : $2A0E : Table does not support this operation. 10767 : $2A0F : Index is read only. 10768 : $2A10 : Table does not support this operation because it is not uniquely indexed. 10769 : $2A11 : Operation must be performed on the current session. 10770 : $2A12 : Invalid use of keyword. 10771 : $2A13 : Connection is in use by another statement. 10772 : $2A14 : Passthrough SQL connection must be shared Os Error not handled by Idapi 11009 : $2B01 : Invalid function number. 11010 : $2B02 : File or directory does not exist. 11011 : $2B03 : Path not found. 11012 : $2B04 : Too many open files. You may need to increase MAXFILEHANDLE limit in IDAPI configuration. 11013 : $2B05 : Permission denied. 11014 : $2B06 : Bad file number. 11015 : $2B07 : Memory blocks destroyed. 11016 : $2B08 : Not enough memory. 11017 : $2B09 : Invalid memory block address. 11018 : $2B0A : Invalid environment. 11019 : $2B0B : Invalid format. 11020 : $2B0C : Invalid access code. 11021 : $2B0D : Invalid data. 11023 : $2B0F : Device does not exist. 11024 : $2B10 : Attempt to remove current directory. 11025 : $2B11 : Not same device. 11026 : $2B12 : No more files. 11027 : $2B13 : Invalid argument. 11028 : $2B14 : Argument list is too long. 11029 : $2B15 : Execution format error. 11030 : $2B16 : Cross-device link. 11041 : $2B21 : Math argument. 11042 : $2B22 : Result is too large. 11043 : $2B23 : File already exists. 11047 : $2B27 : Unknown internal operating system error. 11058 : $2B32 : Share violation. 11059 : $2B33 : Lock violation. 11060 : $2B34 : Critical DOS Error. 11061 : $2B35 : Drive not ready. 11108 : $2B64 : Not exact read/write. 11109 : $2B65 : Operating system network error. 11110 : $2B66 : Error from NOVELL file server. 11111 : $2B67 : NOVELL server out of memory. 11112 : $2B68 : Record already locked by this workstation. 11113 : $2B69 : Record not locked. Network related 11265 : $2C01 : Network initialization failed. 11266 : $2C02 : Network user limit exceeded. 11267 : $2C03 : Wrong .NET file version. 11268 : $2C04 : Cannot lock network file. 11269 : $2C05 : Directory is not private. 11270 : $2C06 : Directory is controlled by other .NET file. 11271 : $2C07 : Unknown network error. 11272 : $2C08 : Not initialized for accessing network files. 11273 : $2C09 : SHARE not loaded. It is required to share local files. 11274 : $2C0A : Not on a network. Not logged in or wrong network driver. 11275 : $2C0B : Lost communication with SQL server. 11277 : $2C0D : Cannot locate or connect to SQL server. 11278 : $2C0E : Cannot locate or connect to network server. Optional parameter related 11521 : $2D01 : Optional parameter is required. 11522 : $2D02 : Invalid optional parameter. Query related 11777 : $2E01 : obsolete 11778 : $2E02 : obsolete 11779 : $2E03 : Ambiguous use of ! (inclusion operator). 11780 : $2E04 : obsolete 11781 : $2E05 : obsolete 11782 : $2E06 : A SET operation cannot be included in its own grouping. 11783 : $2E07 : Only numeric and date/time fields can be averaged. 11784 : $2E08 : Invalid expression. 11785 : $2E09 : Invalid OR expression. 11786 : $2E0A : obsolete 11787 : $2E0B : bitmap 11788 : $2E0C : CALC expression cannot be used in INSERT, DELETE, CHANGETO and SET rows. 11789 : $2E0D : Type error in CALC expression. 11790 : $2E0E : CHANGETO can be used in only one query form at a time. 11791 : $2E0F : Cannot modify CHANGED table. 11792 : $2E10 : A field can contain only one CHANGETO expression. 11793 : $2E11 : A field cannot contain more than one expression to be inserted. 11794 : $2E12 : obsolete 11795 : $2E13 : CHANGETO must be followed by the new value for the field. 11796 : $2E14 : Checkmark or CALC expressions cannot be used in FIND queries. 11797 : $2E15 : Cannot perform operation on CHANGED table together with a CHANGETO query. 11798 : $2E16 : chunk 11799 : $2E17 : More than 255 fields in ANSWER table. 11800 : $2E18 : AS must be followed by the name for the field in the ANSWER table. 11801 : $2E19 : DELETE can be used in only one query form at a time. 11802 : $2E1A : Cannot perform operation on DELETED table together with a DELETE query. 11803 : $2E1B : Cannot delete from the DELETED table. 11804 : $2E1C : Example element is used in two fields with incompatible types or with a BLOB. 11805 : $2E1D : Cannot use example elements in an OR expression. 11806 : $2E1E : Expression in this field has the wrong type. 11807 : $2E1F : Extra comma found. 11808 : $2E20 : Extra OR found. 11809 : $2E21 : One or more query rows do not contribute to the ANSWER. 11810 : $2E22 : FIND can be used in only one query form at a time. 11811 : $2E23 : FIND cannot be used with the ANSWER table. 11812 : $2E24 : A row with GROUPBY must contain SET operations. 11813 : $2E25 : GROUPBY can be used only in SET rows. 11814 : $2E26 : Use only INSERT, DELETE, SET or FIND in leftmost column. 11815 : $2E27 : Use only one INSERT, DELETE, SET or FIND per line. 11816 : $2E28 : Syntax error in expression. 11817 : $2E29 : INSERT can be used in only one query form at a time. 11818 : $2E2A : Cannot perform operation on INSERTED table together with an INSERT query. 11819 : $2E2B : INSERT, DELETE, CHANGETO and SET rows may not be checked. 11820 : $2E2C : Field must contain an expression to insert (or be blank). 11821 : $2E2D : Cannot insert into the INSERTED table. 11822 : $2E2E : Variable is an array and cannot be accessed. 11823 : $2E2F : Label 11824 : $2E30 : Rows of example elements in CALC expression must be linked. 11825 : $2E31 : Variable name is too long. 11826 : $2E32 : Query may take a long time to process. 11827 : $2E33 : Reserved word or one that can't be used as a variable name. 11828 : $2E34 : Missing comma. 11829 : $2E35 : Missing ). 11830 : $2E36 : Missing right quote. 11831 : $2E37 : Cannot specify duplicate column names. 11832 : $2E38 : Query has no checked fields. 11833 : $2E39 : Example element has no defining occurrence. 11834 : $2E3A : No grouping is defined for SET operation. 11835 : $2E3B : Query makes no sense. 11836 : $2E3C : Cannot use patterns in this context. 11837 : $2E3D : Date does not exist. 11838 : $2E3E : Variable has not been assigned a value. 11839 : $2E3F : Invalid use of example element in summary expression. 11840 : $2E40 : Incomplete query statement. Query only contains a SET definition. 11841 : $2E41 : Example element with ! makes no sense in expression. 11842 : $2E42 : Example element cannot be used more than twice with a ! query. 11843 : $2E43 : Row cannot contain expression. 11844 : $2E44 : obsolete 11845 : $2E45 : obsolete 11846 : $2E46 : No permission to insert or delete records. 11847 : $2E47 : No permission to modify field. 11848 : $2E48 : Field not found in table. 11849 : $2E49 : Expecting a column separator in table header. 11850 : $2E4A : Expecting a column separator in table. 11851 : $2E4B : Expecting column name in table. 11852 : $2E4C : Expecting table name. 11853 : $2E4D : Expecting consistent number of columns in all rows of table. 11854 : $2E4E : Cannot open table. 11855 : $2E4F : Field appears more than once in table. 11856 : $2E50 : This DELETE, CHANGE or INSERT query has no ANSWER. 11857 : $2E51 : Query is not prepared. Properties unknown. 11858 : $2E52 : DELETE rows cannot contain quantifier expression. 11859 : $2E53 : Invalid expression in INSERT row. 11860 : $2E54 : Invalid expression in INSERT row. 11861 : $2E55 : Invalid expression in SET definition. 11862 : $2E56 : row use 11863 : $2E57 : SET keyword expected. 11864 : $2E58 : Ambiguous use of example element. 11865 : $2E59 : obsolete 11866 : $2E5A : obsolete 11867 : $2E5B : Only numeric fields can be summed. 11868 : $2E5C : Table is write protected. 11869 : $2E5D : Token not found. 11870 : $2E5E : Cannot use example element with ! more than once in a single row. 11871 : $2E5F : Type mismatch in expression. 11872 : $2E60 : Query appears to ask two unrelated questions. 11873 : $2E61 : Unused SET row. 11874 : $2E62 : INSERT, DELETE, FIND, and SET can be used only in the leftmost column. 11875 : $2E63 : CHANGETO cannot be used with INSERT, DELETE, SET or FIND. 11876 : $2E64 : Expression must be followed by an example element defined in a SET. 11877 : $2E65 : Lock failure. 11878 : $2E66 : Expression is too long. 11879 : $2E67 : Refresh exception during query. 11880 : $2E68 : Query canceled. 11881 : $2E69 : Unexpected Database Engine error. 11882 : $2E6A : Not enough memory to finish operation. 11883 : $2E6B : Unexpected exception. 11884 : $2E6C : Feature not implemented yet in query. 11885 : $2E6D : Query format is not supported. 11886 : $2E6E : Query string is empty. 11887 : $2E6F : Attempted to prepare an empty query. 11888 : $2E70 : Buffer too small to contain query string. 11889 : $2E71 : Query was not previously parsed or prepared. 11890 : $2E72 : Function called with bad query handle. 11891 : $2E73 : QBE syntax error. 11892 : $2E74 : Query extended syntax field count error. 11893 : $2E75 : Field name in sort or field clause not found. 11894 : $2E76 : Table name in sort or field clause not found. 11895 : $2E77 : Operation is not supported on BLOB fields. 11896 : $2E78 : General BLOB error. 11897 : $2E79 : Query must be restarted. 11898 : $2E7A : Unknown answer table type. 11926 : $2E96 : Blob cannot be used as grouping field. 11927 : $2E97 : Query properties have not been fetched. 11928 : $2E98 : Answer table is of unsuitable type. 11929 : $2E99 : Answer table is not yet supported under server alias. 11930 : $2E9A : Non-null blob field required. Can't insert records 11931 : $2E9B : Unique index required to perform changeto 11932 : $2E9C : Unique index required to delete records 11933 : $2E9D : Update of table on the server failed. 11934 : $2E9E : Can't process this query remotely. 11935 : $2E9F : Unexpected end of command. 11936 : $2EA0 : Parameter not set in query string. 11937 : $2EA1 : Query string is too long. 11946 : $2EAA : No such table or correlation name. 11947 : $2EAB : Expression has ambiguous data type. 11948 : $2EAC : Field in order by must be in result set. 11949 : $2EAD : General parsing error. 11950 : $2EAE : Record or field constraint failed. 11951 : $2EAF : When GROUP BY exists, every simple field in projectors must be in GROUP BY. 11952 : $2EB0 : User defined function is not defined. 11953 : $2EB1 : Unknown error from User defined function. 11954 : $2EB2 : Single row subquery produced more than one row. 11955 : $2EB3 : Expressions in group by are not supported. 11956 : $2EB4 : Queries on text or ascii tables is not supported. 11957 : $2EB5 : ANSI join keywords USING and NATURAL are not supported in this release. 11958 : $2EB6 : SELECT DISTINCT may not be used with UNION unless UNION ALL is used. 11959 : $2EB7 : GROUP BY is required when both aggregate and non-aggregate fields are used in result set. 11960 : $2EB8 : INSERT and UPDATE operations are not supported on autoincrement field type. 11961 : $2EB9 : UPDATE on Primary Key of a Master Table may modify more than one record. 11962 : $2EBA : Queries on MS ACCESS tables are not supported by local query engines. 11963 : $2EBB : Preparation of field-level constraint failed. 11964 : $2EBC : Preparation of field default failed. 11965 : $2EBD : Preparation of record-level constraint failed. 11972 : $2EC4 : Constraint Failed. Expression: Version Mismatch Category 12033 : $2F01 : Interface mismatch. Engine version different. 12034 : $2F02 : Index is out of date. 12035 : $2F03 : Older version (see context). 12036 : $2F04 : .VAL file is out of date. 12037 : $2F05 : BLOB file version is too old. 12038 : $2F06 : Query and Engine DLLs are mismatched. 12039 : $2F07 : Server is incompatible version. 12040 : $2F08 : Higher table level required Capability not supported 12289 : $3001 : Capability not supported. 12290 : $3002 : Not implemented yet. 12291 : $3003 : SQL replicas not supported. 12292 : $3004 : Non-blob column in table required to perform operation. 12293 : $3005 : Multiple connections not supported. 12294 : $3006 : Full dBASE expressions not supported. 12295 : $3007 : Nested transactions not supported. System configuration error 12545 : $3101 : Invalid database alias specification. 12546 : $3102 : Unknown database type. 12547 : $3103 : Corrupt system configuration file. 12548 : $3104 : Network type unknown. 12549 : $3105 : Not on the network. 12550 : $3106 : Invalid configuration parameter. Warnings 12801 : $3201 : Object implicitly dropped. 12802 : $3202 : Object may be truncated. 12803 : $3203 : Object implicitly modified. 12804 : $3204 : Should field constraints be checked? 12805 : $3205 : Validity check field modified. 12806 : $3206 : Table level changed. 12807 : $3207 : Copy linked tables? 12809 : $3209 : Object implicitly truncated. 12810 : $320A : Validity check will not be enforced. 12811 : $320B : Multiple records found, but only one was expected. 12812 : $320C : Field will be trimmed, cannot put master records into PROBLEM table. Miscellaneous 13057 : $3301 : File already exists. 13058 : $3302 : BLOB has been modified. 13059 : $3303 : General SQL error. 13060 : $3304 : Table already exists. 13061 : $3305 : Paradox 1.0 tables are not supported. 13062 : $3306 : Update aborted. Compatibility related 13313 : $3401 : Different sort order. 13314 : $3402 : Directory in use by earlier version of Paradox. 13315 : $3403 : Needs Paradox 3.5-compatible language driver. Data Repository related 13569 : $3501 : Data Dictionary is corrupt 13570 : $3502 : Data Dictionary Info Blob corrupted 13571 : $3503 : Data Dictionary Schema is corrupt 13572 : $3504 : Attribute Type exists 13573 : $3505 : Invalid Object Type 13574 : $3506 : Invalid Relation Type 13575 : $3507 : View already exists 13576 : $3508 : No such View exists 13577 : $3509 : Invalid Record Constraint 13578 : $350A : Object is in a Logical DB 13579 : $350B : Dictionary already exists 13580 : $350C : Dictionary does not exist 13581 : $350D : Dictionary database does not exist 13582 : $350E : Dictionary info is out of date - needs Refresh 13584 : $3510 : Invalid Dictionary Name 13585 : $3511 : Dependent Objects exist 13586 : $3512 : Too many Relationships for this Object Type 13587 : $3513 : Relationships to the Object exist 13588 : $3514 : Dictionary Exchange File is corrupt 13589 : $3515 : Dictionary Exchange File Version mismatch 13590 : $3516 : Dictionary Object Type Mismatch 13591 : $3517 : Object exists in Target Dictionary 13592 : $3518 : Cannot access Data Dictionary 13593 : $3519 : Cannot create Data Dictionary 13594 : $351A : Cannot open Database Driver related 15873 : $3E01 : Wrong driver name. 15874 : $3E02 : Wrong system version. 15875 : $3E03 : Wrong driver version. 15876 : $3E04 : Wrong driver type. 15877 : $3E05 : Cannot load driver. 15878 : $3E06 : Cannot load language driver. 15879 : $3E07 : Vendor initialization failed. 15880 : $3E08 : Your application is not enabled for use with this driver. | |
ТЕХНОЛОГИЯ DCOMНастройка системы безопасности DCOM сервера Как я понял, основная проблема в DCOM, с которой сталкиваются разработчики - настройка системы безопасности. Далее описано, как были сделаны настройки безопасности у меня.
Создана группа, в которую включены пользователи, которым нужен доступ к данному DCOM серверу (назовем ее DCOM_DEBUG).
В DCOMCNFG : (это было добавлено и на сервере и на клиенте) DefaultSecurity -> Default Access Permissions DCOM_DEBUG: Allow Access SYSTEM: Allow Access Everyone: Allow Access 2.DefaultSecurity -> Default Launch Permissions DCOM_DEBUG: Allow Launch SYSTEM: Allow Launch INTERACTIVE: Allow Launch Everyone: Allow Launch 3.DefaultSecurity -> Default Configuration Permissions SYSTEM: Full Control DCOM_DEBUG: Full Control Everyone: Full Control Установка этих параметров необходима, по-моему, потому, что контекст безопасности интерфейса передаваемого на сервер для нотификации клиента берется из установок по умолчанию.
Только на сервере.
Установки параметров безопасности для объекта были установлены точно такие же, за исключением того, что Everyone включена не была. На вкладке Identity был выбран пользователь, от имени которого запускается COM сервер. Одна тонкость: у пользователя, от имени которого запускается COM сервер должно быть право "Log on as batch job", иначе сервер не запустится (это право было дано всей группе DCOM_DEBUG). Если выбрать Interactive User, то сервер не запустится, в том случае если пользователь делает Logoff. В случае с Launching User происходили какие-то невнятные проблемы (видимо это было связано со спецификой решаемой мной задачи - DCOM сервер с поддержкой множественных клиентских соединений).
Алексей Вуколов
Статьи по теме:
TExcelManager
Компонент предназначен для работы с таблицами Excel. Он позволяет находить любые таблицы в любом месте документа Excel и импортировать их в таблицы компонента TTable. Можно также экспортировать таблицы из TTable в документы Excel. Существует две версии компонента - для Microsoft Office 2000 и для Microsoft Office XP.
TPrintService
Комментарий Дмитрия Васильева: Как было уже сказано: Компонент предназначен для реализации всех функций, связанных с выводом на печать: выбор принтера, его настройка, предварительный просмотр и собственно печать. Ключевым событием для TPrintService является OnDraw(Sender: TObject; Canvas: TCanvas; PageNumber: Integer; DrawTarget: TDrawTarget), где TDrawTarget = (dtPreview, dtPrint). Именно в этом событии производится определение содержимого документа. В минимальном варианте использования компонента пользователю достаточно определить только это событие. При выводе должны использоваться свойства PageWidth и PageHeight объекта Printer для определения ширины и высоты листа. Преобразование координат для предварительного просмотра происходит без участия пользователя. Все, что нужно сделать, это вывести изображение на передаваемую Canvas в масштабе принтера. Шрифты масштабируются автоматически (это уже дело Windows), поэтому, вне зависимости от модели принтера и установленного разрешения, шрифт размером, скажем, 10, будет выглядеть одинаково при печати из CorelDraw, Word97 и PrintService. Следует использовать именно размер шрифта (Size), т.к. высота (Heigth) изменяется в зависимости от текущего разрешения принтера. В PageNumber передается номер страницы. При многостраничной печати пользователь может определить вывод для всех страниц с номерами 1..PageCount. В DrawTarget содержится информация о том, куда в данный момент производится вывод - в окно предварительного просмотра или на принтер. Эта информация, вообще говоря, не является необходимой, НО, вдруг кому-нибудь захочется проанализировать количество цветов принтера и сделать черно-белый вывод на черно-белый принтер при цветном изображении в окне предварительного просмотра?
Комментарий Алексея Румянцева: Короче говоря, в OnDraw, вы сами определяете то что выводится на принтер (или в окно предварительного просмотра), просто рисуя это на канве.
TRyMenu — собственная отрисовка меню
Перестала мне тут на днях нравиться борландовская прорисовка меню... Вот вобщем-то и все что можно сказать о представленном Вашему вниманию классе TRyMenu.
Никаких дочерних классов, вешаемся на OnAdvancedDrawItem и далее чисто рисование по канве.
Ну а художественные фантазии они у каждого свои и чуть напрягшись на ней(на канве) можно и переливы и пейзаж нарисовать, но кто этим балуется наверняка уже свои классы имеет, а это так минимальный набор для прорисовки меню в новом стиле. Демо прилагается.
Написано на Delphi5. Тестировалось на Win98. WinXP. В случае обнаружения ошибки или несовместимости с другими версиями Delphi и Windows, просьба сообщить автору.
Ваши вопросы и замечания присылайте. | |
Скачать проект (10K) 22.04.02
Алексей Румянцев . Специально для
TRyPrintService
Основное отличие от TPrintService - это наличие "буфера печати", т.е. вам остается заполнить его содержимым (линиями, прямоугольниками, текстом, картинками...) причем не связывая себя какими-либо рамками (в частности размером и положением или вообще отсутствием необходимого элемента в данном конкретном месте отчета), т.е. каждый лист отчета может быть оформлен по своим правилам или без правил. Весь результат работы хранится в этом "буфере" откуда может быть предворительно просмотрен в окне предварительного просмотра, распечатан, скопирован, сохранен, экспортирован (надеюсь вскоре добраться до этой функции) и т.п. Для этого были созданы специальные объекты (RptRect, RptLine, RptEdit, RptBitmap, список легко может быть расширен), параметры (property) которых заполняются пользовательскими значениями (Left, Top..., Color..., Text и т.д.) в соответствии с которыми будет меняться их положение на странице, цвет, текст и т.д. RptOбъекты создаются только один раз, после чего у них меняются лишь значения параметров и затем отправляются с новыми значениями в очередь на печать, где и дожидаются своего звездного часа. RptОбъекты могут отправляться в буфер отчета в любой последовательности, в любом кол-ве и с любым положением на странице. В демонстрационном примере показано, как создавать новый отчет, заполнить его некоторой информацией (прямоугольники, текст, картинки); сохранять в файле и загружать из него отчет, а также как вызывать предварительный просмотр и печатать.
Скачать: (193 K)
С уважением, Алексей Румянцев. Специально для
TRySharedSream — класс упрощающий работу с файлом подкачки
TSharedStream (версия 1). Когда-то (кажется год назад) на страницах "королевства" я прочитал статью об использовании файла подкачки как о временном хранилище данных. ( Имеется ввиду статья Дмитрия Логинова ) После этой статьи я заинтересовался работой с Swap'ом. Некотое время в работе я пользовался чисто FileMappingFun'кциями, что оказалось нудно и трудоемко (не так чтобы очень, но согласитесь, что легче хранить всю информацию в одном месте[классе], чем иметь несколько переменных и помнить когда и как их надо использовать). Написал первую версию класса-обертки над FileMappingFun'кциями и все как-будто было нормально, но убивало одно НО - не было возможности изменять размер области["страницы"] под данные выделенной при ее создании, т.е. надо было заранее знать размер информации, которую вы собираетесь в нее записать. В TSharedStream я решил эту проблему, плохо или хорошо трудно сказать - по сравнению с невозможностью изменить размер - хорошо, а по качеству реализации - не очень. Подробнее ...
Прошло н-ное кол-во времени, появилось желание сделать работу класса правильней, действенней, качественней (нужного слова не подобрать).
TRySharedStream(версия 2)
TRySharedStream(версия 2) - полностью переписанная версия TSharedStream. Пользовательская сторона работы с классом осталась неизменной (единственное был переименован сам класс и его юнит), а внутреннее содержание притерпело изменения. Не бойтесь, работа файла подкачки не изменилась :o), а вот работа TSharedStream меня устраивать перестала - пересоздание бОльших по объему страниц и перемещение данных из одной в другую по несколько раз хоть и работает быстро, но выглядело по скобарски.
Для решения этой проблемы рассматривались альтернативные варианты, которые особо не улудшали ситуацию, так например вариант с созданием одной, но большой страницы проблему лишь временно скрывало, но не решало ее. Результатом же раздумий стал многостраничный вариант, т.е. группа маленьких страниц, хранящих информацию, при необходимости добавляются новыми страницами в которые и дозаписываются данные, в результате а. страница в файле подкачки становится как бы резиновой. б. винт не занимается бессмысленной работой. в. место на диске (в Swap'е) расходуется экономично(экономично или нет будет зависеть уже только от вас - сколько вы туда запишите :o)) г. скорость (скорость вас должна порадовать и поэтому этот пункт можно назвать не "г" а "с" - от слова "свист", т.е. работать будет со свистом. Хотя и здесь есть двусмыслица: с одной стороны если программа работает со свистом, то это хорошо, а если винт работает с подозрительным свистом, то это плохо. :o)).
Результатом так же стало разделением TSharedStream на два класса TRySharedMem и TRySharedStream. TRySharedMem - сам по себе независимый класс, потомок TObject, не тянущий за собой Forms, TApplication, TComponent и т.п.; является чисто оберткой над FileMappingFunctions, но скрывающий все сложности обращения с ними; позволяет создавать объект файлового отображения (как страничного swap-файла, так и обычного файла); позволяет разделять одну область отображения между различными процессами(программами); имеет дополнительные функции Read/Write (аналогичные TStream.Read/TStream.Write). TRySharedStream - Потомок TStream, не тянущий за собой Forms, TApplication, TComponent и т.п. базируется на работе TRySharedMem, аналог временным файлам и постоянным страхам нехватки памяти - т.е. аналог TFileStream и TMemoryStream; расширяет возможности работы с файлом подкачки - размер записываемых данных ограничивается толь местом на диске.
Единственное сейчас TRySharedStream не поддерживает разделения области отображения между различными процессами(программами) как в TRySharedMem, но в следующей версии, скорей всего, эта возможность будет доступна (мысль как это сделать уже есть).
TSelectableTree - TTreeView с возможностью MultiSelect'а
TSelectableTree - наследник от TCustomTreeView, обладает возможностью множественного выбора ( свойство MultiSelect ). Соответственно дополнительные методы - procedure SelectAll; procedure UnSelectAll; procedure InvertSelection; Свойство DefaultPopup = True назначает для дерева PopUp-меню (по правой кнопке мыши) со следующими пунктами: Отметить все Снять все пометки Инверсия выделения И еще всякие полезные мелочи. Например, очень удобная процедура для обработки каждой ветки дерева: procedure TraverseTree(TreeView: TCustomTreeView; Node: TTreeNode; ATraverseTreeEvent : TTVTraverseEvent; AInfo : Pointer); var CNode: TTreeNode; begin if Assigned(ATraverseTreeEvent) then begin if Node = nil then CNode := TTreeView(TreeView).Items.GetFirstNode else CNode := Node; repeat ATraverseTreeEvent(CNode, AInfo); CNode := CNode.GetNext; until (CNode = nil) or (not CNode.HasAsParent(Node)); end; end;
Скачать исходный код (4 K) Сергей Королев
TSharedSream — класс упрощающий работу с файлом подкачки
Когда-то (кажется год назад) на страницах "королевства" я прочитал статью об использовании файла подкачки как временном хранилище данных. ( Имеется ввиду статья Дмитрия Логинова ) После этой статьи я заинтересовался Swap'ом. Некотое время я пользовался чисто File Mapping Func'циями, что оказалось нудно и трудоемко(не так чтобы очень, но согласитесь, что легче хранить всю информацию в одном месте[классе], чем иметь несколько переменных и помнить когда и как их надо использовать). Написал первую версию класса-обертки над File Mapping Func'циями и все как-будто было нормально, но убивало одно НО - не было возможности изменять размер области["страницы"] под данные выделенной при ее создании, т.е. надо было заранее знать размер информации, которую вы собираетесь в нее записать - в данной версии я считаю что мне удалось обойти это недоразумение (подробности в исходнике).
Итак... Класс TSharedSream — класс упрощающий работу с файлом подкачки. Более того, скрывающий все сложности обращения к File Mapping Func'циям. Этот класс является потомком TStream, следовательно он наследует его поведение, а изучение работы с ним сводится к внимательному прочтению хелпа по TStream'у.
Описание: Реализует и упрощает процесс создания и работу с файлом подкачки. Расширяет возможности работы с объектом отображения данных. Может рассматриваться как альтернатива TFileStream, TMemoryStream. Близкие темы : Глава 12 из книги ми версиями Delphi и Windows, просьба сообщить ется демонстрационный пример использования TSharedStream : (6.2K)
TVertGrid — TStringGrid с возможностью заполнения в design-time
| Раздел Сокровищница | нов, дата публикации 13 февраля 2002г. |
Компонент TVertGrid представляет собой модифицированный TStringGrid.
В стандартный компонент добавлена возможность в режиме Design-time заполнять первую колонку (property Labels) и первую строку (property Titles) грида.
Если набранных строк в Labels больше, чем задано количество строк самого TVertGrid, то они будут автоматически добавлены. Аналогично и с количеством колонок (Titles). При уменьшении строк в свойствах Labels и Titles, количество строк и колонок самого грида не будет уменьшаться.
На скриншоте показано редактирование списка заголовков колонок. Количество строк в Titles это количество заполненных колонок первой строки.
Компонент очень прост и вы можете модифицировать его по своему собственному желанию. | |
Скачать (1K) Исходный код компонента: | unit VertGrid; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, Grids, stdctrls; type TVertGrid = class(TStringGrid) protected { Protected declarations } procedure SetLines(Value: TStrings); function GetLines : TStrings; procedure SetTitles(Value: TStrings); function GetTitles : TStrings; public constructor Create(AOwner: TComponent); override; published // Первая колонка property Labels: TStrings read GetLines write SetLines; // Первая строка property Titles: TStrings read GetTitles write SetTitles; end; procedure Register; implementation procedure Register; begin RegisterComponents('Samples', [TVertGrid]); end; constructor TVertGrid.Create(AOwner: TComponent); begin inherited Create(AOwner); ColCount := 2; DefaultRowHeight := 16; end; procedure TVertGrid.SetLines(Value: TStrings); begin if Value.Count > RowCount then RowCount := Value.Count; Cols[0].Assign( Value ); end; procedure TVertGrid.SetTitles(Value: TStrings); begin if Value.Count > ColCount then ColCount := Value.Count; Rows[0].Assign( Value ); end; function TVertGrid.GetLines : TStrings; begin result := Cols[0]; end; function TVertGrid.GetTitles : TStrings; begin result := Rows[0]; end; end. |
ULogs.pas
Скачать (2.3 K) unit uLogs; interface uses Classes,Controls,StdCtrls,ComCtrls,Forms; {-------раздел для генерации сообщений----------} {всего одна процедура} procedure (Channel:byte;Mes:String); {номер канала (0 - 255) и текст сообщения} {----------раздел для обработки сообщений----------} Type TChannels = set of byte; TLog = class {абстрактный базовый класс} protected FChannels:TChannels; {множество обрабатываемых каналов} procedure (Channel:byte;Mes:String);virtual;abstract; {InternalToLog - для каждого наследника определяет способ обработки.} {Номер канала передается, чтобы его можно было учесть при обработке,} {например, при выводе диагностических сообщений менять цвет текста} {в зависимости от номера канала} public procedure (Channel:byte;Status:boolean); procedure (Channel:byte;Mes:String);{общий механизм проверки номера канала} constructor Create(AChannels:TChannels); {множество обрабатываемых каналов задается при создании лога} property Channels:TChannels read FChannels write FChannels; {множество обрабатываемых каналов доступно в процессе работы} end; TStringsLog = class(TLog) {добавление строки к содержимому любого наследника абстрактного базового класса TStrings} FStrings:TStrings; {ссылка на объект в который будет добавлена строка} procedure (Channel:byte;Mes:String);override; constructor Create(AChannels:TChannels;AStrings:TStrings); end; TFileLog = class(TLog) {добавление строки к содержимому файла} FFile:Text; {имя файла} procedure (Channel:byte;Mes:String);override; constructor Create(AChannels:TChannels;FileName:String); end; TCaptionLog = class(TLog) {вывод текста сообщения в Caption любого компонента - наследника TControl} FControl:TControl; {компонент для отображения} procedure (Channel:byte;Mes:String);override; constructor Create(AChannels:TChannels;AControl:TControl); end; TStatusBarLog = class(TLog){вывод текста сообщения в StatusPanel} FStatusPanel:TStatusPanel; procedure (Channel:byte;Mes:String);override; constructor Create(AChannels:TChannels;AStatusPanel:TStatusPanel); end; TMessageBoxLog = class(TLog) {вывод текста сообщения в MessageBox} fCaption:String; fFlags: Longint; procedure (Channel:byte;Mes:String);override; constructor Create(AChannels:TChannels;Caption:String;Flags: Longint); end; function (ALog:TLog):word; {Регистрация нового лога в списке} function (ALog:TLog;Name:string):integer; {Регистрация нового лога в списке под заданным именем, чтобы можно было его удалить} function (Name:string):integer; {Удаление из списка лога с заданным именем} implementation uses Windows; var LogList:TStrings; lLog:TLog; procedure ToLog(Channel:byte;Mes:String); var i:integer; Begin if LogList.Countthen exit; for i:=0 to LogList.Count-1 do TLog(LogList.Objects[i]).toLog(Channel,Mes); End; function SetLog(ALog:TLog):word;{Регистрация нового лога в списке} Begin result:=LogList.AddObject('',ALog); End; function SetNamedLog(ALog:TLog;Name:string):integer; {Регистрация нового лога в списке под заданным именем, чтобы можно было его удалить} var i:integer; Begin result:=-1; {лог под таким именем уже есть} i:=LogList.IndexOf(Name); if i=-1 then result:=LogList.AddObject(Name,ALog); End; function ReSetNamedLog(Name:string):integer; {Удаление из списка лога с заданным именем} var i:integer; Begin result:=LogList.IndexOf(Name); { -1 если лога под таким именем нет} if result<>-1 then LogList.Delete(result); End; { TLog } constructor TLog.Create(AChannels: TChannels); begin FChannels:=AChannels; end; procedure TLog.SetChannel(Channel: byte; Status: boolean); begin if status then FChannels:=FChannels+[channel] else FChannels:=FChannels-[channel]; end; procedure TLog.toLog(Channel: byte; Mes: String); begin if (Channel in FChannels) then InternalToLog(Channel,Mes); end; { TStringsLog } constructor TStringsLog.Create(AChannels: TChannels; AStrings: TStrings); begin inherited Create(AChannels); FStrings:=AStrings; end; procedure TStringsLog.InternalToLog(Channel: byte; Mes: String); begin FStrings.Add(Mes); end; { TFileLog } constructor TFileLog.Create(AChannels: TChannels; FileName: String); begin inherited Create(AChannels); AssignFile(FFile,FileName); Rewrite(FFile); Writeln(FFile, 'Log file start'); CloseFile(FFile); end; procedure TFileLog.InternalToLog(Channel: byte; Mes: String); begin Append(fFile); Writeln(fFile, mes); Flush(fFile); CloseFile(fFile); end; { TCaptionLog } constructor TCaptionLog.Create(AChannels: TChannels; AControl: TControl); begin inherited Create(AChannels); FControl:=AControl; end; procedure TCaptionLog.InternalToLog(Channel: byte; Mes: String); begin TLabel(FControl).Caption:=Mes; // FControl.Caption:=Mes; end; { TStatusBarLog } constructor TStatusBarLog.Create(AChannels: TChannels; AStatusPanel: TStatusPanel); begin inherited Create(AChannels); FStatusPanel:=AStatusPanel; end; procedure TStatusBarLog.InternalToLog(Channel: byte; Mes: String); begin FStatusPanel.text:=Mes; end; { TMassageBoxLog } constructor TMessageBoxLog.Create(AChannels: TChannels; Caption: String; Flags: Integer); begin inherited create(AChannels); fCaption:=Caption; fFlags:=Flags; end; procedure TMessageBoxLog.InternalToLog(Channel: byte; Mes: String); begin Application.MessageBox(PChar(Mes), PChar(fCaption), fFlags); end; initialization LogList:=TStringList.create; lLog:=TMessageBoxLog.Create([0..5],'Ошибка инициализации', MB_OK); // Каналы с 0 по 5 зарезервированы для ошибок инициализации модулей SetLog(lLog); finalization LogList.free; end.
Управление чужим приложением средствами WinAPI
Просмотрев в королевстве на данную тему множество вопросов, оставшихся без ответов, сам нашел решение проблеммы управления чужим приложением. Данный код ищет чужое приложение,запускает в нем 2 пункт 7 подпункт меню, в появившемся диалоге выбора файла ищет класс Edit c текстом '', вводит в класс Edit строку с именем файла и отсылает команду Enter. Далее ищет появление диалогового окна с предложением подтвердить загрузку. После ищет кнопку "Да" и отсылает команду Enter.
Напомню, что все диалоговые окна — это главные окна, которые ищутся FindWindow, а всякие Edit,Button,ComboBox и т.д. - это дочерние окна, которые ищутся функцией FindWindowEx.
| procedure TForm1.Button9Click(Sender: TObject); Var Men :HMenu; Hnd,HndDialog,HndControl,HndAsc,HndBtn :HWnd; HndMen,HndSMen :HMenu; StrClass :PChar; StrBase :string; IdBtn,idMen :integer; begin Hnd:=FindWindow(nil, 'Конфигуратор - 2345'); if Hnd <>0 then begin //фокусируем: windows.SetForegroundWindow(Hnd); //или можно так:windows.BringWindowToTop(Hnd); //Работа с меню: //=============================================================================== HndMen:=GetMenu(Hnd);//получили описатель главного меню окна. HndSMen:=GetSubMenu(HndMen,1);//получили описатель второго пункта главного меню (0 -первый пункт) //получили идентификатор 7 пункта подменюменю (черты в меню - это также пункты) idMen:=GetMenuItemID(HndSMen,6); //в данном случае idMen это word(33206) if idMen<>0 then begin //запускаем пункт меню. Именно PostMessage, SendMessage - не работает. PostMessage(Hnd,WM_COMMAND,idMen,0); //=============================================================================== //Инициализируем переменные диалогов: HndDialog:=0; HndControl:=0; HndBtn:=0; HndAsc:=0; IdBtn:=0; //=============================================================================== //поищем диалог ввода до тех пор пока не найдем: While HndDialog=0 do HndDialog:= FindWindow(nil, 'Открыть файл конфигурации'); if HndDialog<>0 then begin StrClass:='Edit'+#0;//на всякий случай вставим завершающий ноль //Ищем класс Edit среди подчиненных HndDialog окон HndControl:=FindWindowEx(HndDialog,0,StrClass,''); if HndControl<>0 then begin StrBase:='D:\md\zik2345\1Cv7.MD'; Sleep(1000);//а вот без этого ну ни как не хочет работать. SendMessage(HndControl, WM_Settext,0,Integer(StrBase));//все, текст переменной StrBase введен. //жмем Enter SendMessage(HndDialog,WM_Command,MakeWParam(1,$0f),HndControl); //здесь 1 это значит что мы передаем на выполнение акселератор //строки, а $0f - это событие "(Enter)" этому акселератору //в MSDN смотрим WM_Command. //MakeWParam - функция которая два Word слова помещает : //первое в верхние 16 bit, второе в нижние 16 bit, 32 битного(LongInt) //параметра WParam (аналог MakeLParam ). //=============================================================================== //ищем диалог пока не найдем: while HndAsc=0 do HndAsc:= FindWindow(nil, 'Конфигуратор'); if HndAsc<>0 then begin //ищем кнопку в диалоге: //обращаем внимание на знак & - если на кнопку завязана комбинация клавиш //(это когда буква в кнопке подчеркнута) //то надо к имени добавлять перед этой буквой & а то кнопочка не найдется если их несколько. while HndBtn=0 do HndBtn:=FindWindowEx(HndAsc,0,'Button','&Да'); if HndBtn<>0 then begin IdBtn:=GetDlgCtrlID(HndBtn); if IdBtn<>0 then begin //ну и наконец жмем кнопку '&Да': SendMessage(HndAsc,WM_Command,MakeWParam(IdBtn,BN_CLICKED),HndBtn); //а хелп MSDN по BN_CLICKED или WM_Command //здесь верхнее слово WParam это идентификатор контрола, а нижнее - код BN_CLICKED end; end; end; //================================================================================ end; end; end; end; end; |
Userunit
| | "Knowledge itself is power" F.Bacon |
Функция для представления числа прописью
|
|
// Владимир Папаев // Скачать этот пример Тестировалось ТОЛЬКО под Delphi 4 !!! ПРАВИЛЬНОЕ СКЛОНЕНИЕ !!! function MoneyToString(S:Currency;kpk:boolean;usd:boolean):string; // если KOP:=TRUE - печать копеек цифрой, иначе прописью // если USD:=TRUE - печать суммы в долларах Пример: m:=123.45; str:=MoneyToString(m,true,false); str = 'сто двадцать три рубля 43 копейки' m:=123.45; str:=MoneyToString(m,false,false); str = 'сто двадцать три рубля сорок три копейки' m:=123.45; str:=MoneyToString(m,true,true); str = 'сто двадцать три доллара 43 цента США' m:=123.45; str:=MoneyToString(m,false,true); str = 'сто двадцать три доллара соро три цента США'
Внедрение и линковка компонентов. Пример.
| Раздел Сокровищница | рбань С.В., дата публикации 18 марта 2002г. |
Модуль демонстрирует возможности по "Внедрению" и "Сцепке" компонентов. В основном все д/б понятно из подстрочных комментариев. Для чего нужно: Задача - содать специализированный LightWeight вариант TChart. Работа ведется несколькими программистами. ВСЕ элементы д/б объектами, а по возможности и самостоятельными компонентами. Например - полоса скроллинга по данным. Она должна быть либо "встроенной" (принадлежать базовому компоненту) либо внешней. Причем при работе (в приложении) различий быть не должно...
Первый маленький элемент - полоса скроллинга по данным и контейнер для нее. Компонент вполне самостоятельный и вполне может быть полезен Вне контекста задачи. Примечания: 1. В первую очередь проект предназначен для обучения. В том числе и меня :-)) Поэтому "не стреляйте в пианиста...". Если есть лучшее решение - ДАВАЙТЕ ЕГО СЮДА!!!->>> Fox1225@Mail.ru 2. Весь код приведенный здесь может использоваться As Is и все такое... Я не силен в лицензионных соглашениях. Просто берите и пользуйтесь. На свой страх и риск, разумеется :-)) 3. Все Ваши комментарии можно мылить по адресу: Fox1225@Mail.ru} Глюкобаги: 1. Гляньте в конструктор. Там есть вопросик... 2. Есть БОЛЬШАЯ бяка - смотрите TModContainer.CreateComponent unit AltChartMain; interface {Заранее извиняюсь за цветовую гамму... Делайте как кому нравится :-)} {ВНИМАНИЕ!!!! Пример тестировался под D6, и меня предупредили, что в D5 нет SetSubComponent. Самому проверить негде, так что будте внимательны!} uses Windows, Messages, SysUtils, Classes, Controls, StdCtrls, ExtCtrls, Graphics, Math, MyMath; resourcestring SMinMaxError = 'Max ДОЛЖЕН быть больше Min. EMinMaxError.'+Chr(13)+Chr(13); type EMinMaxError = class(Exception); //Попытка задать Min > Max TGraphScrollKind = (skHorizontal, skVertical); TGraphScrollLayout = (slTop, slCenter, slBottom); //Полоса скроллинга по данным TGraphScroll = class(TGraphicControl) private FLineWidth: Integer; FLineColor: TColor; FSliderWidth: Integer; FSliderLength: Integer; FSliderColor: TColor; FHSC: Integer; //Horisontal Slider Center. Для ускорения отрисовки. FVSC: Integer; //Vertical Slider Center. Для ускорения отрисовки. FPosition: Integer; FSliderRect: TRect; //Это чтобы по быстрому определить, ткнули мы мышом по слайдеру или нет... FMin: Integer; FMax: Integer; FSliderCaptured: Boolean; FGraphScrollKind: TGraphScrollKind; //Слайдер зацепили мышом... FBegDragCoord: TPoint; //Коорд. мыша в момент "зацепа" FBegDragPos: Integer; //Position в момент "зацепа" FGraphScrollLayout: TGraphScrollLayout; procedure (const Index, Value: Integer); procedure (const Index: Integer; const Value: TColor); procedure (AMin, AMax, APosition: Integer); procedure ; procedure (const Value: Integer); procedure (const Value: Integer); procedure (const Index, Value: Integer); procedure (const Value: TGraphScrollKind); procedure (const Value: TGraphScrollLayout); protected procedure ; override; procedure ; override; procedure (Button: TMouseButton; Shift: TShiftState; X, Y: Integer); override; procedure (Shift: TShiftState; X, Y: Integer); override; procedure (Button: TMouseButton; Shift: TShiftState; X, Y: Integer); override; procedure (var MinWidth, MinHeight, MaxWidth, MaxHeight: Integer); override; procedure ; override; function (var NewWidth, NewHeight: Integer): Boolean; override; public constructor Create(AOwner: TComponent); override; published property Anchors; property Align; property AutoSize; property LineColor: TColor index 0 read FLineColor write SetColor; property SliderColor: TColor index 1 read FSliderColor write SetColor; property LineWidth: Integer index 0 read FLineWidth write SetGeometry; property SliderWidth: Integer index 1 read FSliderWidth write SetGeometry; property SliderLength: Integer index 2 read FSliderLength write SetGeometry; property Position: Integer index 0 read FPosition write SetPosition; property Min: Integer read FMin write SetMin; property Max: Integer read FMax write SetMax; property Kind: TGraphScrollKind read FGraphScrollKind write SetGraphScrollKind; property Layout: TGraphScrollLayout read FGraphScrollLayout write SetGraphScrollLayout; end; //Компонент - контейнер TModContainer = class(TPanel) private FComponent: TGraphScroll; procedure ; procedure (const Value: TGraphScroll); protected procedure (AComponent: TComponent; Operation: TOperation); override; public constructor Create(AOwner: TComponent); override; published property Component: TGraphScroll read FComponent write SetComponent; end; procedure ; implementation procedure Register; begin RegisterComponents('Samples', [TGraphScroll, TModContainer]); end; { TGraphScroll } constructor TGraphScroll.Create(AOwner: TComponent); begin Inherited Create(AOwner); //"сетапим" компонент... FLineWidth:=3; FLineColor:=clNavy; FSliderWidth:=7; FSliderLength:=40; FSliderColor:=clTeal; FMax:=100; FPosition:=30; Width:=200; Height:=11; //Странно, но значения меньше 10 НЕ принимаются! Почему? Кто объяснит дремучему? Align:=alBottom; RecalcGeometry; end; procedure TGraphScroll.MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); begin inherited; If InRect(X, Y, FSliderRect) Then begin FSliderCaptured:=True; FBegDragCoord.X:=X; FBegDragCoord.Y:=Y; FBegDragPos:=Position; end; end; procedure TGraphScroll.MouseMove(Shift: TShiftState; X, Y: Integer); begin inherited; If FSliderCaptured Then If Kind = skHorizontal Then Position:=FBegDragPos+Round((X-FBegDragCoord.X)*(Max-Min)/Width) Else Position:=FBegDragPos+Round((Y-FBegDragCoord.Y)*(Max-Min)/Height); end; procedure TGraphScroll.MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); begin inherited; FSliderCaptured:=False; Refresh; end; procedure TGraphScroll.RecalcGeometry; Var WorkZone: Integer; begin //Гммм... если кто-нибудь сможет упростить эти монструозные формулы - буду благодарен... //Однако будте внимательны! //If по Kind'у меня уже достал... Нужно как-то более гибко... If Kind = skHorizontal Then begin WorkZone:=Width - SliderLength - SliderWidth - 3; //Левый край FSliderRect.Left:=Round(WorkZone*(FPosition-FMin)/(FMax-FMin))+SliderWidth div 2 + 2; //Правый край FSliderRect.Right:=FSliderRect.Left+SliderLength; //Горизонтальный центр слайдера (нужен для рисования риски) FHSC:=EnsureRange(FSliderRect.Left+Floor(SliderLength / 2), 0, Width-1); //"Вертикальные" параметры. Зависят от Layout. Case Layout of //ВНИМАНИЕ!!!! Может кому пригодится! У нас есть св-во Max, а нам нужна ф-ия Max из //модуля Math. Поэтому - Math.Max. Вроде-бы просто, но какую я шишку год назад набил на этом... slTop: FVSC:=Math.Max(SliderWidth, LineWidth) div 2; slCenter: FVSC:=Height div 2; slBottom: FVSC:=Height - Math.Max(SliderWidth, LineWidth) div 2 - 2; End; //Верх бегунка FSliderRect.Top:=FVSC - SliderWidth div 2; //Низ бегунка FSliderRect.Bottom:=FSliderRect.Top+SliderWidth; end Else begin WorkZone:=Height - SliderLength - SliderWidth - 3; //Верх бегунка FSliderRect.Top:=Round(WorkZone*(FPosition-FMin)/(FMax-FMin))+SliderLength div 2 + 2; //Низ бегунка FSliderRect.Bottom:=FSliderRect.Top+SliderLength; //Горизонтальный центр (при skVertical становится Вертикальным Центром) слайдера (нужен для рисования риски) FHSC:=EnsureRange(FSliderRect.Top+Floor(SliderLength / 2), 0, Height-1); //"Вертикальные" параметры. Зависят от Layout. Case Layout of //ВНИМАНИЕ!!!! Может кому пригодится! У нас есть св-во Max, а нам нужна ф-ия Max из //модуля Math. Поэтому - Math.Max. Вроде-бы просто, но какую я шишку год назад набил на этом... slTop: FVSC:=Math.Max(SliderWidth, LineWidth) div 2; slCenter: FVSC:=Width div 2; slBottom: FVSC:=Width - Math.Max(SliderWidth, LineWidth) div 2 - 2; End; //Левый край бегунка FSliderRect.Left:=FVSC - SliderWidth div 2; //Правый край бегунка FSliderRect.Right:=FSliderRect.Left+SliderWidth; end; end; procedure TGraphScroll.Paint; Var LWD2: Integer; //LineWidth div 2// begin //Предложения по "украшательству" компонента принимаются с радостью, но только не в ущерб СКОРОСТИ //Предложения, как избавиться от мерцания, принимаются ВНЕ очереди! //С удовольствием выслушаю предложения, как избавиться от If'ов по Kind'у. Уж больно громоздко... LWD2:=LineWidth div 2 + 1; //При рисовании толстой линии ее концы скругляются "наружу", чтобы их НЕ //подрезать (красиво выглядит), даем для них отступ... With Canvas do begin //Рисуем линию. Без комментариев... Pen.Width:=LineWidth; Pen.Color:=LineColor; If Kind = skHorizontal Then begin MoveTo(LWD2, FVSC);//0 + ширина линии | Так получаются скругленные концы LineTo(Width-LWD2-1, FVSC); //ширина - ширина линии | end Else begin MoveTo(FVSC, LWD2); //0 + ширина линии | Так получаются скругленные концы LineTo(FVSC, Height-LWD2-1); //ширина - ширина линии | end; //Рисуем "слайдер" (бегунок, он же ползунок, по буржуйски - Slider). Без комментариев... Pen.Width:=SliderWidth; Pen.Color:=SliderColor; If Kind = skHorizontal Then begin MoveTo(FSliderRect.Left, FVSC); LineTo(FSliderRect.Right, FVSC); end Else begin MoveTo(FVSC, FSliderRect.Top); LineTo(FVSC, FSliderRect.Bottom); end; //Рисуем центральную риску на бегунке. Pen.Width:=1; If FSliderCaptured Then //Если бегунок "захвачен" (двигается мышом...) Pen.Color:=clRed //Рисуем красным цветом Else Pen.Color:=clBlack; //Если нет - черным... If Kind = skHorizontal Then begin MoveTo(FHSC, FSliderRect.Top); LineTo(FHSC, FSliderRect.Bottom); end Else begin MoveTo(FSliderRect.Left, FHSC); LineTo(FSliderRect.Right, FHSC); end; end; end; procedure TGraphScroll.Resize; begin //При изменении размера надо пересчитать все переменные, используемы для отрисовки компонента... inherited Resize; RecalcGeometry; Refresh; end; procedure TGraphScroll.SetColor(const Index: Integer; const Value: TColor); begin //Все стандартно... Case Index of 0: FLineColor := Value; 1: FSliderColor:=Value; End; Refresh; end; procedure TGraphScroll.SetGeometry(const Index, Value: Integer); begin //Тоже стандартно... Case Index of 0: FLineWidth:=Value; 1: FSliderWidth:=Value; 2: FSliderLength:=Value; End; RecalcGeometry; Refresh; end; procedure TGraphScroll.SetGraphScrollKind(const Value: TGraphScrollKind); Var Tmp: Integer; begin If FGraphScrollKind <> Value then //Если НЕ текущее значение begin FGraphScrollKind:=Value; //Присвоим новое... If not (csLoading in ComponentState) and //Если не в состоянии загрузки И //Выравнивание alNone или alCustom или alClient ((Align = alNone) or (Align = alCustom) or (Align = alClient)) then begin //"Переворачиваем" компонент (меняем местами высоту и ширину...) Tmp:=Height; Height:=Width; Width:=Tmp; end; end; RecalcGeometry; Refresh; end; procedure TGraphScroll.SetGraphScrollLayout( const Value: TGraphScrollLayout); begin //Процедура смены Layout'а. Все просто... Что такое Layout - смотри TLabel FGraphScrollLayout:=Value; RecalcGeometry; Refresh; end; procedure TGraphScroll.SetMax(const Value: Integer); begin SetValues(FMin, Value, FPosition); end; procedure TGraphScroll.SetMin(const Value: Integer); begin SetValues(Value, FMax, FPosition); end; procedure TGraphScroll.SetPosition(const Index, Value: Integer); begin SetValues(FMin, FMax, Value); end; procedure TGraphScroll.SetValues(AMin, AMax, APosition: Integer); begin If AMax < AMin then //Максимум ДОЛЖЕН быть больше минимума raise EMinMaxError.Create(SMinMaxError+'TGraphScroll.SetValues'); FMin:=AMin; FMax:=AMax; FPosition:=EnsureRange(APosition, FMin, FMax); RecalcGeometry; Refresh; end; procedure TGraphScroll.ConstrainedResize(var MinWidth, MinHeight, MaxWidth, MaxHeight: Integer); //Перекрыв этот метод TControl можно задать мин и макс. р-ры компонента. //В нашем случае - компонент не может быть ниже ширины Math.Max(LineWidth, SliderWidth); //И уже MinWidth:=SliderLength+2*LineWidth+2*SliderWidth; //ЕСЛИ вертикально расположенный - наоборот... begin If Kind = skHorizontal Then begin MinWidth:=SliderLength+2*LineWidth+2*SliderWidth; MinHeight:=Math.Max(LineWidth, SliderWidth); end Else begin MinWidth:=Math.Max(LineWidth, SliderWidth); MinHeight:=SliderLength+2*LineWidth+2*SliderWidth; end; end; procedure TGraphScroll.RequestAlign; begin Inherited; //Меняем тип Kind'а при изменении выравнивания. If ((Align = alTop) or (Align = alBottom)) and (Kind <> skHorizontal) Then Kind:=skHorizontal; If ((Align = alLeft) or (Align = alRight)) and (Kind <> skVertical) Then Kind:=skVertical; end; function TGraphScroll.CanAutoSize(var NewWidth, NewHeight: Integer): Boolean; begin //Перекрываем унаследованную "автосайзилку". Код слизан с TImage и поэтому работает :-) Result:=True; if not (csDesigning in ComponentState) or (LineWidth > 0) and (SliderWidth > 0) then begin if (Align in [alNone, alLeft, alRight]) and (Kind = skVertical) then NewWidth:=Math.Max(LineWidth, SliderWidth); if (Align in [alNone, alTop, alBottom]) and (Kind <> skVertical) then NewHeight:=Math.Max(LineWidth, SliderWidth); end; end; { TModContainer } constructor TModContainer.Create(AOwner: TComponent); begin inherited Create(AOwner); //Ну, это святое... Width:=400; Height:=150; CreateComponent; //Создание к-та собрано в процедуру, так как используется еще и в SetComponent end; procedure TModContainer.CreateComponent; begin FComponent:=TGraphScroll.Create(Self); //Создаем к-т FComponent.Name:='IntCnt'; //Даем ему имя (необязательно...) FComponent.SetSubComponent(True); //Устанавливаем флаг "SubComponent" FComponent.FreeNotification(Self); //Хотим получать уведомление об уничтожении FComponent.Parent:=Self; //ВАЖНО!!!! Ставим себя "Родителем" FComponent.Width:=Width-20; //Располагаем и образмериваем... FComponent.Top:=Height-20; // ------//------- FComponent.Left:=10; // ------//------- // FComponent.Anchors:=[akBottom, akLeft, akRight]; //А вот с якорями пока решения нету. //Ставим "ручками" в DesignTime //Суть прикола такова - "якоря" цепляются раньше, чем загружаются размеры контейнерного компонента //из файла формы. (ВСЕ креэйты отрабатваю раньше загрузки). Как я понял: контейнерный компонент создается //с размерами Width:=400; Height:=150; , на нем создается FComponent, который цепляется якорями, а затем //читаются данные из файла формы, например Width:=800; - Результат - внедренные к-ты с установленными akLeft+akRight или //akTop+akBottom растягиваются (сжимаются) при КАЖДОЙ загрузке формы в Design Time. //В Ран тайм все нормально... но... end; procedure TModContainer.Notification(AComponent: TComponent; Operation: TOperation); //*Fox* Процедура отслеживающая удаление встроенных объектов //См. справку "Creating properties for subcomponents" begin inherited Notification(AComponent, Operation); //Ну, это святое... //Если "наш" компонент и его удаляют If (AComponent = FComponent) and (Operation = opRemove) Then FComponent:=nil; //Обнулим линк на него... end; procedure TModContainer.SetComponent(const Value: TGraphScroll); //*Fox* Процедура ответственная за "линковку" FComponent //Если линкуем внешний скроллер - внутренний высвобождается //Если удаляем внешний (присваиваем nil) - создается внутрений //См. справку "Creating properties for subcomponents" begin If Value <> FComponent Then //Если предлагают НЕ то, что уже есть... begin If Value <> nil Then //Если линкуем внешний begin If (FComponent <> nil) and (FComponent.Owner = Self) Then //Если сейчас НЕ пустой и Свой FComponent.Free; //Удалим его FComponent:=Value; //Прицепим то, что предлагают... FComponent.FreeNotification(Self); //Хотим получать уведомление об уничтожении end Else //Если удаляем внешний (присв. nil) begin If FComponent.Owner <> Self Then //Если убрали внешний - создадим внутренний CreateComponent; end; end; end; end.
Скачать пример: (11 K) Этот код является плодом обсуждения проблемы на Круглом столе между рем Шевченко.
Горбань С.В. Специально для
Возможности
Проверяется корректность введенного выражения. Вычисляются правильно составленные выражения, содержащие бинарные операции +, -, *, /, ^, любые скобки, функции sin, cos, tg, ctg, exp, ln, lg, числовые константы типа extended (с точкой в качестве десятичного разделителя), и переменные произвольной длины, состоящие из букв любого алфавита и цифр. Одинаковые символы в разных регистрах считаются идентичными. Если аргументом функции является переменная либо константа, то их не обязательно заключать в скобки. Пример: -(x+cosy)/Exp[z]+LN {sin пеРеменная1-tg 3.14}
Возможные проблемы при работе с TCanvas больших размеров
| Рздел Сокровищница | ренко, дата публикации 08 января 2002г. |
Проблема. Так получилось, что передо мной встала задачи работы с канвой (TCanvas) больших размеров (от 2000 и более точек в одном измерении). Через достаточно короткое время работы я обнаружил, что методы TCanvas иногда ведут себя некорректно. Некорректность поведения заключалась в том, что при определенных условиях графические примитивы, например, прямые линии, либо отображались неправильно, либо просто исчезали. Проверка и перепроверка текста программы ничего не дала. Попытка найти какую-либо информацию о возможных особенностях работы с канвой таких размеров также ни дала положительного результата (может быть, просто плохо искал). Пришлось разбираться самому, а затем и обратиться за советом к некоторым жителям Королевства.
Результат. В ходе работы удалось некоторым образом локализовать условия возникновения изложенной выше ситуации. 1. Проблемы возникают только под Win9x. Под Windows NT или 2000 подобные ошибки обнаружить не удалось. 2. Графические примитивы могут отображаться неправильно, если их размер в одном измерении более 1000 точек. Например, при отрисовки линии: … MoveTo(0, 0); LineTo(0, 2000); … 3. Самый надежный метод TCanvas - Rectangle, рисуется корректно всегда. Менее надежные - методы рисования прямых линий, например, PolyLine или MoveTo, LineTo. Поскольку большое значение имеет платформа, а именно Windows 9x, возникло предположение, что возникающие проблемы являются не глюком или не ошибкой TCanvas. Просто именно под этой платформой возможности графики ограничены.
Напрашивающиеся выводы по использованию TCanvas больших размеров. 1. Не пытайтесь рисовать все сразу, а отображайте только то что, действительно необходимо. Если по каким-либо причинам это невозможно, и вам просто необходимо перерисовывать сразу весь TCanvas, используйте графические примитивы, принудительно ограничивая их размер, допустим 1500 точек. 2. Может быть, воспользоваться советом Рустама Кафарова: "Итак, решение одно (во всяком случае, одно я нашел, может решений больше) - используйте платформу NT. Под Windows 2000 все работает НАМНОГО ЛУЧШЕ! Советую просто поменять систему. Если в вашей программе будет ремарка "разработана специально под NT", то это не будет минусом для программы"
P.S. В качестве примера я предлагаю вашему вниманию небольшой проект , в котором возникают изложенные выше проблемы. Суть проекта - отрисовка разными методами сетки с шагом 40 точек.
К сожалению, мне не удалось подобрать такой режим, чтобы проблема возникало на любой машине, на которой стоит Windows 9x. Поэтому я хотел бы попросить сразу не забрасывать меня помидорами тех, у кого под Windows 9x все будет работать корректно. Возможно, что все вышеописанное является неким частным случаем, и дарность за оказанную помощь.
Специально для
Выключение компьютера в заданное время
| Раздел Сокровищница | Агранович, дата публикации 13 июня 2002г. |
Программа для выключения компьютера в заданное время. Если запустить с параметром, указав время, то программа запустится скрытно и выключит компьютер в указанное время. Проверенно на Windows XP. Для выключения используется процедура: Procedure ShutdownComputer; var ph:THandle; tp,prevst:TTokenPrivileges; rl:DWORD; begin OpenProcessToken(GetCurrentProcess,TOKEN_ADJUST_PRIVILEGES or TOKEN_QUERY,ph); LookupPrivilegeValue(Nil,'SeShutdownPrivilege',tp.Privileges[0].Luid); tp.PrivilegeCount:=1; tp.Privileges[0].Attributes:=2; AdjustTokenPrivileges(ph,FALSE,tp,SizeOf(prevst),prevst,rl); ExitWindowsEx(EWX_SHUTDOWN or EWX_POWEROFF,0); end; Скачать (обновление от 01.07.02): Исполняемый файл (161 K) Исходные коды (6 K)
WinAPIFAQ
| DELPHI WinAPI FAQ Перевод с английского |
Подборку, перевод и адаптацию материала подготовил Aziz(JINX) специально для Королевства Дельфи. Скачать (27 K) для просмотра в off-line.
Вопрос: Как программно выключить монитор?
Ответ: Программно можно отключить монитор совместимый со стандартом EnergyStar.
Отправьте сообщение wm_SysCommand с параметром WParam = SC_MonitorPower и LParam = 0 для отключения монитора LParam = 1 для включения монитора В приведенном примере монитор отключается на 10 секунд.
Пример: type TForm1 = class(TForm) Button1: TButton; Timer1: TTimer; procedure FormCreate(Sender: TObject); procedure Timer1Timer(Sender: TObject); procedure Button1Click(Sender: TObject); private { Private declarations } public MonitorOff : bool; { Public declarations } end; var Form1: TForm1; implementation {$R *.DFM} procedure TForm1.FormCreate(Sender: TObject); begin Timer1.Enabled := false; Timer1.Interval := 10000; MonitorOff := false; end; procedure TForm1.Timer1Timer(Sender: TObject); begin if MonitorOff then begin MonitorOff := false; SendMessage(Application.Handle, wm_SysCommand, SC_MonitorPower, -1); Timer1.Enabled := false; end; end; procedure TForm1.Button1Click(Sender: TObject); begin MonitorOff := true; Timer1.Enabled := true; SendMessage(Application.Handle, wm_SysCommand, SC_MonitorPower, 0); end; Вопрос:
Как создать мигающий заголовок окна (пиктограмму)? Ответ: Можно воспользоваться функцией API FlashWindow(): Пример: var Flash : bool; procedure TForm1.Timer1Timer(Sender: TObject); begin FlashWindow(Form1.Handle, Flash); FlashWindow(Application.Handle, Flash); Flash := not Flash; end; procedure TForm1.FormCreate(Sender: TObject); begin Flash := False; end; Вопрос:
Иногда всплывающее меню моего приложения system tray не исчезает когда оно теряет фокус. Как закрыть его? Ответ: При показе всплывающего меню установите foreground window, затем пошлите сообщение WM_NULL после показа меню. procedure TForm1.WndProc(var Msg : TMessage); var p : TPoint; begin case Msg.Msg of WM_USER + 1: case Msg.lParam of WM_RBUTTONDOWN: begin SetForegroundWindow(Handle); GetCursorPos(p); PopupMenu1.Popup(p.x, p.y); PostMessage(Handle, WM_NULL, 0, 0); end; end; end; inherited; end; Вопрос:
Как узнать текущие время и дату по Гринвичу Ответ: Используя API фукцию GetSystemTime. Пример: procedure TForm1.Button1Click(Sender: TObject); var lt : TSYSTEMTIME; st : TSYSTEMTIME; begin GetLocalTime(lt); GetSystemTime(st); Memo1.Lines.Add('LocalTime = ' + IntToStr(lt.wmonth) + '/' + IntToStr(lt.wDay) + '/' + IntToStr(lt.wYear) + ' ' + IntToStr(lt.wHour) + ':' + IntToStr(lt.wMinute) + ':' + IntToStr(lt.wSecond)); Memo1.Lines.Add('UTCTime = ' + IntToStr(st.wmonth) + '/' + IntToStr(st.wDay) + '/' + IntToStr(st.wYear) + ' ' + IntToStr(st.wHour) + ':' + IntToStr(st.wMinute) + ':' + IntToStr(st.wSecond)); end; Вопрос:
Какой самый быстрый способ для очистки canvasа? Ответ: Windows API функция PatBlt(). Пример: procedure TForm1.Button1Click(Sender: TObject); begin PatBlt(Form1.Canvas.Handle, 0, 0, Form1.ClientWidth, Form1.ClientHeight, WHITENESS); end; Вопрос: При изменении размеров формы мне необходимо чтобы перерисовывалась вся ее поверхность. Но свойство Canvas.ClipRect у формы - только для чтения. Ответ: На событии Resize вызовите Windows API функцию InvalidateRect(). Если передать nil в качестве второго параметра приведет к тому, что перерисовываться будет вся клиентская область окна. Третий параметр указывает будет ли перерисовываться фон формы. Пример: procedure TForm1.FormResize(Sender: TObject); begin InvalidateRect(Form1.Handle, nil, false); end; Вопрос: Как использовать процедуру mouse_event() для имитации событий мыши? Ответ: Приведенный пример демонстрирует использование API функции mouse_event() для имитации событий мыши. При нажатии кнопки Button2 программа перемещает курсор мыши на кнопку Button1 и щелкает по ней. Положение курсора мыши задается в "абсолютных" координатах ("Mickeys"), где 65535 "Mickeys" равно ширине экрана. procedure TForm1.Button1Click(Sender: TObject); begin ShowMessage('Button 1 clicked'); end; procedure TForm1.Button2Click(Sender: TObject); var Pt : TPoint; begin {Позволим кнопке Button2 перерисоваться} Application.ProcessMessages; {Найдем координаты центра button 1} Pt.x := Button1.Left + (Button1.Width div 2); Pt.y := Button1.Top + (Button1.Height div 2); {Преобразуем Pt к координатам экрана} Pt := ClientToScreen(Pt); {Преобразуем Pt к "mickeys" (аболютным координатам курсора мышки} Pt.x := Round(Pt.x * (65535 / Screen.Width)); Pt.y := Round(Pt.y * (65535 / Screen.Height)); {Переместим курсор мыши} Mouse_Event(MOUSEEVENTF_ABSOLUTE or MOUSEEVENTF_MOVE, Pt.x, Pt.y, 0, 0); {Имитируем нажатие левой кнопки мыши} Mouse_Event(MOUSEEVENTF_ABSOLUTE or MOUSEEVENTF_LEFTDOWN, Pt.x, Pt.y, 0, 0);; {Имитируем отпускание левой кнопки мыши} Mouse_Event(MOUSEEVENTF_ABSOLUTE or MOUSEEVENTF_LEFTUP, Pt.x, Pt.y, 0, 0);; end; Вопрос: Как программно закрыть другое приложение? Ответ: Отправьте этому приложению сообщение WM_QUIT Пример: PostMessage(FindWindow(Nil, 'Заголовок окна'), WM_QUIT, 0, 0); Где "Заголовок окна" - заголовок окна, которому Вы посылаете сообщение. Вопрос: Форматирование диска в Win32 Ответ: ShellAPI функция ShFormatDrive(). Пример: const SHFMT_DRV_A = 0; const SHFMT_DRV_B = 1; const SHFMT_ID_DEFAULT = $FFFF; const SHFMT_OPT_QUICKFORMAT = 0; const SHFMT_OPT_FULLFORMAT = 1; const SHFMT_OPT_SYSONLY = 2; const SHFMT_ERROR = -1; const SHFMT_CANCEL = -2; const SHFMT_NOFORMAT = -3; function SHFormatDrive(hWnd : HWND; Drive : Word; fmtID : Word; Options : Word) : Longint stdcall; external 'Shell32.dll' name 'SHFormatDrive'; procedure TForm1.Button1Click(Sender: TObject); var FmtRes : longint; begin try FmtRes:= ShFormatDrive(Handle, SHFMT_DRV_A, SHFMT_ID_DEFAULT, SHFMT_OPT_QUICKFORMAT); case FmtRes of SHFMT_ERROR : ShowMessage('Error formatting the drive'); SHFMT_CANCEL : ShowMessage('User canceled formatting the drive'); SHFMT_NOFORMAT : ShowMessage('No Format') else ShowMessage('Disk has been formatted'); end; except end; end; Вопрос: Как спрятать и отключить кнопку "Пуск"? Ответ: Приведенный пример прячет и показывает кнопку "Пуск", а также разрешает и запрещает ее. Пример: procedure TForm1.Button1Click(Sender: TObject); var Rgn : hRgn; begin {Cпрятать кнопку "Пуск"} Rgn := CreateRectRgn(0, 0, 0, 0); SetWindowRgn(FindWindowEx(FindWindow('Shell_TrayWnd', nil), 0, 'Button', nil), Rgn, true); end; procedure TForm1.Button2Click(Sender: TObject); begin {Показать кнопку "Пуск"} SetWindowRgn(FindWindowEx(FindWindow('Shell_TrayWnd', nil), 0, 'Button', nil), 0, true); end; 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 Вопрос: Как временно отключить перерисовку окна? Ответ: Вызовите функцию WinAPI LockWindowUpdate передав ей дескриптор окна, которое необходимо не обновлять. Передайте ноль в качестве параметра для восстановления нормального обновления. LockWindowUpdate(Memo1.Handle); . . LockWindowUpdate(0); Вопрос: Моя программа использует дравер принтера. Возможно ли потихоньку установить драйвер принтера без вмешательства пользователя? Ответ: Приведенный пример устанавливает драйвер принтера. Вам необходимо скопировать файлы с драйвером принтера в каталог Windows\System и внести необходимые изменения в файл Win.Ini. Примечание: DriverName = Имя драйвера; DRVFILE - имя файла с драйвером без расширения (".drv" - по умолчанию). Пример: procedure TForm1.Button1Click(Sender: TObject); var s : array[0..64] of char; begin WriteProfileString('PrinterPorts', 'DriverName', 'DRVFILE,FILE:,15,45'); WriteProfileString('Devices', 'DriverName', 'DRVFILE,FILE:'); StrCopy(S, 'PrinterPorts'); SendMessage(HWND_BROADCAST, WM_WININICHANGE, 0, LongInt(@S)); StrCopy(S, 'Devices'); SendMessage(HWND_BROADCAST, WM_WININICHANGE, 0, LongInt(@S)); end; Вопрос: Как набрать номер с помощью модема в Win32? Ответ: Используйте функцию Windows API CreateFile() чтобы получить дескриптор порта, и стандартные функции ввода-вывода для связи с полученным портом. Пример: var hCommFile : THandle; procedure TForm1.Button1Click(Sender: TObject); var PhoneNumber : string; CommPort : string; NumberWritten : LongInt; begin PhoneNumber := 'ATDT 1-555-555-1212' + #13 + #10; CommPort := 'COM2'; {Open the comm port} hCommFile := CreateFile(PChar(CommPort), GENERIC_WRITE, 0, nil, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); if hCommFile=INVALID_HANDLE_VALUE then begin ShowMessage('Unable to open '+ CommPort); exit; end; {Dial the phone} NumberWritten:=0; if WriteFile(hCommFile, PChar(PhoneNumber)^, Length(PhoneNumber), NumberWritten, nil) = false then begin ShowMessage('Unable to write to ' + CommPort); end; end; procedure TForm1.Button2Click(Sender: TObject); begin {Close the port} CloseHandle(hCommFile); end; Вопрос: Как использовать TAPI для голосового звонка? Ответ: См пример. Пример: {tapi Errors} const TAPIERR_CONNECTED = 0; const TAPIERR_DROPPED = -1; const TAPIERR_NOREQUESTRECIPIENT = -2; const TAPIERR_REQUESTQUEUEFULL = -3; const TAPIERR_INVALDESTADDRESS = -4; const TAPIERR_INVALWINDOWHANDLE = -5; const TAPIERR_INVALDEVICECLASS = -6; const TAPIERR_INVALDEVICEID = -7; const TAPIERR_DEVICECLASSUNAVAIL = -8; const TAPIERR_DEVICEIDUNAVAIL = -9; const TAPIERR_DEVICEINUSE = -10; const TAPIERR_DESTBUSY = -11; const TAPIERR_DESTNOANSWER = -12; const TAPIERR_DESTUNAVAIL = -13; const TAPIERR_UNKNOWNWINHANDLE = -14; const TAPIERR_UNKNOWNREQUESTID = -15; const TAPIERR_REQUESTFAILED = -16; const TAPIERR_REQUESTCANCELLED = -17; const TAPIERR_INVALPOINTER = -18; {tapi size constants} const TAPIMAXDESTADDRESSSIZE = 80; const TAPIMAXAPPNAMESIZE = 40; const TAPIMAXCALLEDPARTYSIZE = 40; const TAPIMAXCOMMENTSIZE = 80; const TAPIMAXDEVICECLASSSIZE = 40; const TAPIMAXDEVICEIDSIZE = 40; function tapiRequestMakeCallA(DestAddress : PAnsiChar; AppName : PAnsiChar; CalledParty : PAnsiChar; Comment : PAnsiChar) : LongInt; stdcall; external 'TAPI32.DLL'; function tapiRequestMakeCallW(DestAddress : PWideChar; AppName : PWideChar; CalledParty : PWideChar; Comment : PWideChar) : LongInt; stdcall; external 'TAPI32.DLL'; function tapiRequestMakeCall(DestAddress : PChar; AppName : PChar; CalledParty : PChar; Comment : PChar) : LongInt; stdcall; external 'TAPI32.DLL'; procedure TForm1.Button1Click(Sender: TObject); var DestAddress : string; CalledParty : string; Comment : string; begin DestAddress := '1-555-555-1212'; CalledParty := 'Frank Borland'; Comment := 'Calling Frank'; tapiRequestMakeCall(pChar(DestAddress), PChar(Application.Title), pChar(CalledParty), PChar(Comment)); end; end. Вопрос: Как показать иконку, ассоциированной с данным типом файла? Ответ: ShellApi функция ExtractAssociatedIcon() Пример: uses ShellApi; procedure TForm1.Button1Click(Sender: TObject); var Icon : hIcon; IconIndex : word; begin IconIndex := 1; Icon := ExtractAssociatedIcon(HInstance, Application.ExeName, IconIndex); DrawIcon(Canvas.Handle, 10, 10, Icon); end; Вопрос: Как определение нажатия определенной клавиши во время загрузки приложения? Ответ: Используйту WinAPI функцию GetKeyState() для определения нажатия клавиши в тексте проекта. Для того чтобы увидеть текст файла проекта в главном меню Delphi 3 выберите "View">>"ProjectSource" в Delphi 4 "Project">>"View Source". Пример: program Project1; uses Windows, Forms, Unit1 in 'Unit1.pas' {Form1}; {$R *.RES} begin if GetKeyState(vk_F8) < 1 then MessageBox(0, 'F8 was pressed during startup', 'MyApp', mb_ok); Application.Initialize; Application.CreateForm(TForm1, Form1); Application.Run; end. Вопрос: Как заставить пикнуть динамик несколько раз с небольшой задержкой между сигналами, не зависящей от тактовой частоты процессора? Ответ: См. пример. Пример: procedure Delay(ms : longint); {$IFNDEF WIN32} var TheTime : LongInt; {$ENDIF} begin {$IFDEF WIN32} Sleep(ms); {$ELSE} TheTime := GetTickCount + ms; while GetTickCount < TheTime do Application.ProcessMessages; {$ENDIF} end; procedure TForm1.Button1Click(Sender: TObject); begin MessageBeep(word(-1)); Delay(200); MessageBeep(word(-1)); Delay(200); MessageBeep(word(-1)); end; Вопрос: Можно ли отключить кнопку закрытия любого окна? Ответ: Да, приведенный пример отключает кнопку закрытия и пункт "закрыть" ситсемного меню заданного окна. 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; Вопрос: Как узнать путь к каталогам Windows? Ответ: Следующий пример получает полный список каталогов по умолчанию (Favorites, Desktop, Programs, Fonts, SendTo, Start, Menu, Templates, Startup, Recent and NetHood) Windows и заносит его в Memo. Пример: uses Registry; procedure TForm1.Button1Click(Sender: TObject); var reg : TRegistry; ts : TStrings; i : integer; begin reg := TRegistry.Create; reg.RootKey := HKEY_CURRENT_USER; reg.LazyWrite := false; reg.OpenKey( 'Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders', false); ts := TStringList.Create; reg.GetValueNames(ts); for i := 0 to ts.Count -1 do begin Memo1.Lines.Add(ts.Strings[i] + ' = ' + reg.ReadString(ts.Strings[i])); end; ts.Free; reg.CloseKey; reg.free; end; Вопрос: Как узнать полный путь и имя файла загруженной 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; Вопрос: Как вызвать диалог 'Найти файлы и паки' проводника? Ответ: Приведенный пример показывает использование DDE для вызова диалога 'Найти файлы и паки' Explorerа. Диалог открывается на каталоге "C:\Download". procedure TForm1.Button1Click(Sender: TObject); begin with TDDEClientConv.Create(Self) do begin ConnectMode := ddeManual; ServiceApplication := 'explorer.exe'; SetLink( 'Folders', 'AppProperties'); OpenLink; ExecuteMacro('[FindFolder(, C:\DOWNLOAD)]', False); CloseLink; Free; end; end; Вопрос: Как сделать родительское окно с фоновым рисунком в клиентской области? Ответ: Для того чтобы сделать это выполните следующие шаги: Срздайте новый проект. Установите FormStyle формы в fsMDIForm Разместите Image на форме и загрузите в него картинку. Найдите { Private Declarations } в обьявлении формы и добаьте следующие строки: FClientInstance : TFarProc; FPrevClientProc : TFarProc; procedure ClientWndProc(var Message: TMessage); Добаьте следующие строки в разделе implementation: procedure TMainForm.ClientWndProc(var Message: TMessage); var Dc : hDC; Row : Integer; Col : Integer; begin with Message do case Msg of WM_ERASEBKGND: begin Dc := TWMEraseBkGnd(Message).Dc; for Row := 0 to ClientHeight div Image1.Picture.Height do for Col := 0 to ClientWidth div Image1.Picture.Width do BitBlt(Dc, Col * Image1.Picture.Width, Row * Image1.Picture.Height, Image1.Picture.Width, Image1.Picture.Height, Image1.Picture.Bitmap.Canvas.Handle, 0, 0, SRCCOPY); Result := 1; end; else Result := CallWindowProc(FPrevClientProc, ClientHandle, Msg, wParam, lParam); end; end; В методе формы OnCreate добавьте: FClientInstance := MakeObjectInstance(ClientWndProc); FPrevClientProc := Pointer(GetWindowLong(ClientHandle, GWL_WNDPROC)); SetWindowLong(ClientHandle, GWL_WNDPROC, LongInt(FClientInstance)); Добавьте к проекту новую форму и установите ее свойство FormStyle в fsMDIChild. У Вас получился MDI-проект с "обоями" в клиентской области MDI формы. Вопрос: Как глобально перехватить нажатие кнопки PrintScreen? Ответ: В примере для глобального перехвата нажатия клавиши printscreen регистрируется горячая клавиша (hot key). Пример: type TForm1 = class(TForm) procedure FormCreate(Sender: TObject); procedure FormDestroy(Sender: TObject); private { Private declarations } procedure WMHotKey(var Msg : TWMHotKey); message WM_HOTKEY; public { Public declarations } end; var Form1: TForm1; implementation {$R *.DFM} const id_SnapShot = 101; procedure TForm1.WMHotKey (var Msg : TWMHotKey); begin if Msg.HotKey = id_SnapShot then ShowMessage('GotIt'); end; procedure TForm1.FormCreate(Sender: TObject); begin RegisterHotKey(Form1.Handle, id_SnapShot, 0, VK_SNAPSHOT); end; procedure TForm1.FormDestroy(Sender: TObject); begin UnRegisterHotKey (Form1.Handle, id_SnapShot); end; Вопрос: Существует ли способ для определение числа заданий spoolerа печати? Ответ: Spoolerа печати Windows посылает WM_SPOOLERSTATUS каждый раз при добавлении и удалении заданий в очереди печати. В следующем примере показано как перехватить это сообщение Пример: type TForm1 = class(TForm) Label1: TLabel; private { Private declarations } procedure WM_SpoolerStatus(var Msg : TWMSPOOLERSTATUS); message WM_SPOOLERSTATUS; public { Public declarations } end; var Form1: TForm1; implementation {$R *.DFM} procedure TForm1.WM_SpoolerStatus(var Msg : TWMSPOOLERSTATUS); begin Lable1.Caption := IntToStr(msg.JobsLeft) + ' Jobs currenly in spooler'; msg.Result := 0; end; Вопрос: Как определить имена установленых Com-портов? Ответ: Из реестра. См. пример. Пример: uses Registry; procedure TForm1.Button1Click(Sender: TObject); var reg : TRegistry; ts : TStrings; i : integer; begin reg := TRegistry.Create; reg.RootKey := HKEY_LOCAL_MACHINE; reg.OpenKey('hardware\devicemap\serialcomm', false); ts := TStringList.Create; reg.GetValueNames(ts); for i := 0 to ts.Count -1 do begin Memo1.Lines.Add(reg.ReadString(ts.Strings[i])); end; ts.Free; reg.CloseKey; reg.free; end; Вопрос: Извлечение пиктограммы из exe, dll или ico-файла Ответ: Функция SHELLAPI ExtractIconEx: Обратите внимание - в примере функции обьявленны иначе, чем в модуле ShellAPI type ThIconArray = array[0..0] of hIcon; type PhIconArray = ^ThIconArray; function ExtractIconExA(lpszFile: PAnsiChar; nIconIndex: Integer; phiconLarge : PhIconArray; phiconSmall: PhIconArray; nIcons: UINT): UINT; stdcall; external 'shell32.dll' name 'ExtractIconExA'; function ExtractIconExW(lpszFile: PWideChar; nIconIndex: Integer; phiconLarge: PhIconArray; phiconSmall: PhIconArray; nIcons: UINT): UINT; stdcall; external 'shell32.dll' name 'ExtractIconExW'; function ExtractIconEx(lpszFile: PAnsiChar; nIconIndex: Integer; phiconLarge : PhIconArray; phiconSmall: PhIconArray; nIcons: UINT): UINT; stdcall; external 'shell32.dll' name 'ExtractIconExA'; procedure TForm1.Button1Click(Sender: TObject); var NumIcons : integer; pTheLargeIcons : phIconArray; pTheSmallIcons : phIconArray; LargeIconWidth : integer; SmallIconWidth : integer; SmallIconHeight : integer; i : integer; TheIcon : TIcon; TheBitmap : TBitmap; begin NumIcons := ExtractIconEx('C:\Program Files\Borland\Delphi 3\BIN\delphi32.exe', -1, nil, nil, 0); if NumIcons > 0 then begin LargeIconWidth := GetSystemMetrics(SM_CXICON); SmallIconWidth := GetSystemMetrics(SM_CXSMICON); SmallIconHeight := GetSystemMetrics(SM_CYSMICON); GetMem(pTheLargeIcons, NumIcons * sizeof(hIcon)); GetMem(pTheSmallIcons, NumIcons * sizeof(hIcon)); FillChar(pTheLargeIcons^, NumIcons * sizeof(hIcon), #0); FillChar(pTheSmallIcons^, NumIcons * sizeof(hIcon), #0); ExtractIconEx('C:\Program Files\Borland\Delphi 3\BIN\delphi32.exe', 0, pTheLargeIcons, pTheSmallIcons, numIcons); {$IFOPT R+} {$DEFINE CKRANGE} {$R-} {$ENDIF} for i := 0 to (NumIcons - 1) do begin DrawIcon(Form1.Canvas.Handle, i * LargeIconWidth, 0, pTheLargeIcons^[i]); TheIcon := TIcon. Create; TheBitmap := TBitmap.Create; TheIcon.Handle := pTheSmallIcons^[i]; TheBitmap.Width := TheIcon.Width; TheBitmap.Height := TheIcon.Height; TheBitmap.Canvas.Draw(0, 0, TheIcon); TheIcon.Free; Form1.Canvas.StretchDraw(Rect(i * SmallIconWidth, 100, (i + 1) * SmallIconWidth, 100 + SmallIconHeight), TheBitmap); TheBitmap.Free; end; {$IFDEF CKRANGE} {$UNDEF CKRANGE} {$R+} {$ENDIF} FreeMem(pTheLargeIcons, NumIcons * sizeof(hIcon)); FreeMem(pTheSmallIcons, NumIcons * sizeof(hIcon)); end; end; end. Вопрос: как заставить Рабочий Стола Windows обновится? Ответ: См. пример. Пример: procedure TForm1.Button1Click(Sender: TObject); begin SendMessage(FindWindow('Progman', 'Program Manager'), WM_COMMAND, $A065, 0); end; Вопрос: Перерисовка canvasf моей формы занимает довольно много времени. Как определить установлен ли у пользователя режим перерисовки всего окна при перемещении чтобы временно отключить перерисовку моего окна? Ответ: В приведенном примере определяется включен ли режим "Full Window Drag" (перерисовки всего окна при перемещении) Пример: procedure TForm1.Button1Click(Sender: TObject); var b : bool; begin SystemParametersInfo(SPI_GETDRAGFULLWINDOWS, 0, @b, 0); if not b then ShowMessage('Full Window Drag is not enabled') else ShowMessage('Full Window Drag is enabled'); end; Вопрос: Как уступить выделенный моей программе квант процессорного времени другим приложениям? Ответ: Вызовите функцию Windows API Sleep() передав ноль в качестве параметра. Вопрос: Как запускать мою программу на каждом старте Windows? Ответ: Пример работает и для Win32и для Win16. uses Registry, {For Win32} IniFiles; {For Win16} {$IFNDEF WIN32} const MAX_PATH = 144; {$ENDIF} {For Win32} procedure TForm1.Button1Click(Sender: TObject); var reg: TRegistry; begin reg := TRegistry.Create; reg.RootKey := HKEY_LOCAL_MACHINE; reg.LazyWrite := false; reg.OpenKey('Software\Microsoft\Windows\CurrentVersion\Run', false); reg.WriteString('My App', Application.ExeName); reg.CloseKey; reg.free; end; {For Win16} procedure TForm1.Button2Click(Sender: TObject); var WinIni : TIniFile; WinIniFileName : array[0..MAX_PATH] of char; s : string; begin GetWindowsDirectory(WinIniFileName, sizeof(WinIniFileName)); StrCat(WinIniFileName, '\win.ini'); WinIni := TIniFile.Create(WinIniFileName); s := WinIni.ReadString('windows', 'run', ''); if s = '' then s := Application.ExeName else s := s + ';' + Application.ExeName; WinIni.WriteString('windows', 'run', s); WinIni.Free; end; Вопрос: Как увеличить процессорное время, выделяемого программе? Ответ: Следующий пример изменяет приоритет приложения. Изменение приоритета следует использовать с осторожностью - т.к. присвоение слишком высокого приоритета может привети к медленной работе остальных программ и системы в целом. См. Win32 help for SetThreadPriority() function. Пример: 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; Вопрос: Я хочу определить момент окончания изменения размера или перемещения окна. Перехватываю сообщения WM_SIZE и WM_MOVE но я получаю много таких сообщений а мне нужно узнать когда именно пользователь закончил перенос или изменение размеров окна. Возможно ли это? Ответ: В следующем примере показан перехват сообщения WM_EXITSIZEMOVE Хотя сообщение документированно только для Windows NT оно работает точно так же и под Windows 95. Обратите внимание что Вы можите перехватить сообщение WM_ENTERSIZEMOVEдля определения момента начала пользователем операции изменения размера или перемещения окна. Пример: type TForm1 = class(TForm) private { Private declarations } public procedure WMEXITSIZEMOVE(var Message: TMessage); message WM_EXITSIZEMOVE; { Public declarations } end; var Form1: TForm1; implementation {$R *.DFM} procedure TForm1.WMEXITSIZEMOVE(var Message: TMessage); begin Form1.Caption := 'Finished Moving and sizing'; end; Вопрос: Как определить время последнего доступа к файлу? Ответ: См пример. Примечание: не все файловые системы поддерживают время последнего доступа к файлу. Пример: procedure TForm1.Button1Click(Sender: TObject); var SearchRec : TSearchRec; Success : integer; DT : TFileTime; ST : TSystemTime; begin Success := SysUtils.FindFirst('C:\autoexec.bat', faAnyFile, SearchRec); if (Success = 0) and (( SearchRec.FindData.ftLastAccessTime.dwLowDateTime <> 0) or ( SearchRec.FindData.ftLastAccessTime.dwHighDateTime <> 0)) then begin FileTimeToLocalFileTime(SearchRec.FindData.ftLastAccessTime,DT); FileTimeToSystemTime(DT,ST); Memo1.Lines.Clear; Memo1.Lines.Add('AutoExec.Bat was last accessed at:'); Memo1.Lines.Add('Year := ' + IntToStr(st.wYear)); Memo1.Lines.Add('Month := ' + IntToStr(st.wMonth)); Memo1.Lines.Add('DayOfWeek := ' + IntToStr(st.wDayOfWeek)); Memo1.Lines.Add('Day := ' + IntToStr(st.wDay)); Memo1.Lines.Add('Hour := ' + IntToStr(st.wHour)); Memo1.Lines.Add('Minute := ' + IntToStr(st.wMinute)); Memo1.Lines.Add('Second := ' + IntToStr(st.wSecond)); Memo1.Lines.Add('Milliseconds := ' + IntToStr(st.wMilliseconds)); end; SysUtils.FindClose(SearchRec); end; Вопрос: Как использовать функцию Shell API SHBrowseForFolder чтобы позволить пользователю выбрать каталог? Ответ: См. пример Пример: uses ShellAPI, ShlObj; procedure TForm1.Button1Click(Sender: TObject); var TitleName : string; lpItemID : PItemIDList; BrowseInfo : TBrowseInfo; DisplayName : array[0..MAX_PATH] of char; TempPath : array[0..MAX_PATH] of char; begin FillChar(BrowseInfo, sizeof(TBrowseInfo), #0); BrowseInfo.hwndOwner := Form1.Handle; BrowseInfo.pszDisplayName := @DisplayName; TitleName := 'Please specify a directory'; BrowseInfo.lpszTitle := PChar(TitleName); BrowseInfo.ulFlags := BIF_RETURNONLYFSDIRS; lpItemID := SHBrowseForFolder(BrowseInfo); if lpItemId <> nil then begin SHGetPathFromIDList(lpItemID, TempPath); ShowMessage(TempPath); GlobalFreePtr(lpItemID); end; end; Вопрос: Как получить дескриптора окна Window, сожержащего DOS программу или программу консольного режима? Ответ: В следуещем примере используется функция 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; Вопрос: Возможно ли определить факта изменения системного времени другим приложением? Ответ: Следующий прмер перехватывает событие WM_TIMECHANGE. примечание: Приложение , изменяющее системное время должно посылать сообщение WM_TIMECHANGE всем окнам. type TForm1 = class(TForm) private { Private declarations } procedure WMTIMECHANGE(var Message: TWMTIMECHANGE); message WM_TIMECHANGE; public { Public declarations } end; var Form1: TForm1; implementation {$R *.DFM} procedure TForm1.WMTIMECHANGE(var Message: TWMTIMECHANGE); begin Form1.Caption := 'Time Changed'; end; Вопрос: Как очистить пункт документы меню кнопки Пуск Ответ: Вызовите Windows API функцию SHAddToRecentDocs() передав nil вместо имени файла в качестве параметра. Пример: uses ShlOBJ; procedure TForm1.Button1Click(Sender: TObject); begin SHAddToRecentDocs(SHARD_PATH, nil); end; Вопрос: Как опеределить состояние модема под Win32? Ответ: См. пример Пример: procedure TForm1.Button1Click(Sender: TObject); var CommPort : string; hCommFile : THandle; ModemStat : DWord; begin CommPort := 'COM2'; {Open the comm port} hCommFile := CreateFile(PChar(CommPort), GENERIC_READ, 0, nil, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); if hCommFile = INVALID_HANDLE_VALUE then begin ShowMessage('Unable to open '+ CommPort); exit; end; {Get the Modem Status} if GetCommModemStatus(hCommFile, ModemStat) <> false then begin if ModemStat and MS_CTS_ON <> 0 then ShowMessage('The CTS (clear-to-send) is on.'); if ModemStat and MS_DSR_ON <> 0 then ShowMessage('The DSR (data-set-ready) is on.'); if ModemStat and MS_RING_ON <> 0then ShowMessage('The ring indicator is on.'); if ModemStat and MS_RLSD_ON <> 0 then ShowMessage('The RLSD (receive-line-signal-detect) is on.'); end; {Close the comm port} CloseHandle(hCommFile); end; Вопрос: Как добавить пункт к системному меню приложения? Пример: type TForm1 = class(TForm) procedure FormCreate(Sender: TObject); private { Private declarations } procedure WMSysCommand(var Msg: TWMSysCommand); message WM_SYSCOMMAND; public { Public declarations } end; var Form1: TForm1; implementation {$R *.DFM} const SC_MyMenuItem = WM_USER + 1; procedure TForm1.FormCreate(Sender: TObject); begin AppendMenu(GetSystemMenu(Handle, FALSE), MF_SEPARATOR, 0, ''); AppendMenu(GetSystemMenu(Handle, FALSE), MF_STRING, SC_MyMenuItem, 'My Menu Item'); end; procedure TForm1.WMSysCommand(var Msg: TWMSysCommand); begin if Msg.CmdType = SC_MyMenuItem then ShowMessage('Got the message') else inherited; end; Вопрос: Как создание нестандартную процедуру разбиения слов при переносах для TEdit, TMemo или TRichEdit? Ответ: В следующем примере создается процедура разбиения слов при переносах для TMemo. Заметьте, что реализованная процедура просто всегда разрешает перенос. Для дополнительной информации см.таже документацию к сообщению EM_SETWORDBREAKPROC. var OriginalWordBreakProc : pointer; NewWordBreakProc : pointer; function MyWordBreakProc(LPTSTR : pchar; ichCurrent : integer; cch : integer; code : integer) : integer {$IFDEF WIN32} stdcall; {$ELSE} ; export; {$ENDIF} begin result := 0; end; procedure TForm1.FormCreate(Sender: TObject); begin OriginalWordBreakProc := Pointer( SendMessage(Memo1.Handle, EM_GETWORDBREAKPROC, 0, 0)); {$IFDEF WIN32} NewWordBreakProc := @MyWordBreakProc; {$ELSE} NewWordBreakProc := MakeProcInstance(@MyWordBreakProc, hInstance); {$ENDIF} SendMessage(Memo1.Handle, EM_SETWORDBREAKPROC, 0, longint(NewWordBreakProc)); end; procedure TForm1.FormDestroy(Sender: TObject); begin SendMessage(Memo1.Handle, EM_SETWORDBREAKPROC, 0, longint(@OriginalWordBreakProc)); {$IFNDEF WIN32} FreeProcInstance(NewWordBreakProc); {$ENDIF} end; Вопрос: Можно ли скопировать группу файлов, используя стандартный диалог с анимацией Копирование Файлов, который использует "Проводник" (Explorer)? Ответ: В следующем примере используется функция SHFileOperation для копирования группы файлов и показа анимированного диалога. Вы можете использовать также следующие флаги для копирования, удаления, переноса и переименования файлов. TO_COPY FO_DELETE FO_MOVE FO_RENAME Примечание: буфер, содержащий имена файлов для копирования должен заканчиваться двумя нулевыми символами. Пример: uses ShellAPI; procedure TForm1.Button1Click(Sender: TObject); var Fo : TSHFileOpStruct; buffer : array[0..4096] of char; p : pchar; begin FillChar(Buffer, sizeof(Buffer), #0); p := @buffer; p := StrECopy(p, 'C:\DownLoad\1.ZIP') + 1; p := StrECopy(p, 'C:\DownLoad\2.ZIP') + 1; p := StrECopy(p, 'C:\DownLoad\3.ZIP') + 1; StrECopy(p, 'C:\DownLoad\4.ZIP'); FillChar(Fo, sizeof(Fo), #0); Fo.Wnd := Handle; Fo.wFunc := FO_COPY; Fo.pFrom := @Buffer; Fo.pTo := 'D:\'; Fo.fFlags := 0; if ((SHFileOperation(Fo) <> 0) or (Fo.fAnyOperationsAborted <> false)) then ShowMessage('Cancelled') end; Вопрос: Как узнать серийный номер диска Ответ: procedure TForm1.Button1Click(Sender: TObject); var VolumeName, FileSystemName : array [0..MAX_PATH-1] of Char; VolumeSerialNo : DWord; MaxComponentLength, FileSystemFlags : Integer; begin GetVolumeInformation('C:\',VolumeName,MAX_PATH,@VolumeSerialNo, MaxComponentLength,FileSystemFlags, FileSystemName,MAX_PATH); Memo1.Lines.Add('VName = '+VolumeName); Memo1.Lines.Add('SerialNo = $'+IntToHex(VolumeSerialNo,8)); Memo1.Lines.Add('CompLen = '+IntToStr(MaxComponentLength)); Memo1.Lines.Add('Flags = $'+IntToHex(FileSystemFlags,4)); Memo1.Lines.Add('FSName = '+FileSystemName); end; Вопрос: Как узнать является диск CD-диском,сетевым диском, виртуальным диском или сьемным диском? Ответ: Windows API функция GetDriveType(). Пример: procedure TForm1.Button1Click(Sender: TObject); begin case GetDriveType('C:\') of 0 : ShowMessage('The drive type cannot be determined'); 1 : ShowMessage('The root directory does not exist'); DRIVE_REMOVABLE:ShowMessage('The disk can be removed'); DRIVE_FIXED : ShowMessage('The disk cannot be removed'); DRIVE_REMOTE : ShowMessage('The drive is remote (network) drive'); DRIVE_CDROM : ShowMessage('The drive is a CD-ROM drive'); DRIVE_RAMDISK : ShowMessage('The drive is a RAM disk'); end; end; Вопрос: Как проверить готовность диска без появления окна ошибки Windows? Ответ: Вы можете использовать функцию Windows API SetErrorMode() для отключения диалога Window's critical Error. Пример: function IsDriveReady(DriveLetter : char) : bool; var OldErrorMode : Word; OldDirectory : string; begin OldErrorMode := SetErrorMode(SEM_NOOPENFILEERRORBOX); GetDir(0, OldDirectory); {$I-} ChDir(DriveLetter + ':\'); {$I+} if IoResult <> 0 then Result := False else Result := True; ChDir(OldDirectory); SetErrorMode(OldErrorMode); end; procedure TForm1.Button1Click(Sender: TObject); begin if not IsDriveReady('A') then ShowMessage('Drive Not Ready') else ShowMessage('Drive is Ready'); end; Вопрос: Использование FindFirst для поиска файлов. Ответ: begin Result := SysUtils.FindFirst(Path, Attr, SearchRec); while Result = 0 do begin ProcessSearchRec(SearchRec); Result := SysUtils.FindNext(SearchRec); end; SysUtils.FindClose(SearchRec); end; Вопрос: Как получить дескриптор окна другого приложения и сделать его активным? Ответ: Использование фуекции Windows API FindWindow() - простейший способ нахождение окна, при условии, что известен его заголовок или имя оконного класса. Если Вам известна только часть заголовка окна (например 'Netscape - ' + 'какой-то неизвестный URL'), Вам нужно использовать функцию EnumWindows() для получения всех окон, затем вызывать функцию GetWindowsText() и GetClassName для поиска нужного окна. Следующий пример находит первое окно, содержащее совпадающую часть заголовка окна и полностью совпадающее название оконного класса (если он задан) и делает это окно активным. type PFindWindowStruct = ^TFindWindowStruct; TFindWindowStruct = record Caption : string; ClassName : string; WindowHandle : THandle; end; function EnumWindowsProc(hWindow : hWnd; lParam : LongInt) : Bool {$IFDEF Win32} stdcall; {$ELSE} ; export; {$ENDIF} var lpBuffer : PChar; WindowCaptionFound : bool; ClassNameFound : bool; begin GetMem(lpBuffer, 255); Result := True; WindowCaptionFound := False; ClassNameFound := False; try if GetWindowText(hWindow, lpBuffer, 255) > 0 then if Pos(PFindWindowStruct(lParam).Caption, StrPas(lpBuffer)) > 0 then WindowCaptionFound := true; if PFindWindowStruct(lParam).ClassName = '' then ClassNameFound := True else if GetClassName(hWindow, lpBuffer, 255) > 0 then if Pos(PFindWindowStruct(lParam).ClassName, StrPas(lpBuffer)) > 0 then ClassNameFound := True; if (WindowCaptionFound and ClassNameFound) then begin PFindWindowStruct(lParam).WindowHandle := hWindow; Result := False; end; finally FreeMem(lpBuffer, sizeof(lpBuffer^)); end; end; function FindAWindow(Caption : string; ClassName : string) : THandle; var WindowInfo : TFindWindowStruct; begin with WindowInfo do begin Caption := Caption; ClassName := ClassName; WindowHandle := 0; EnumWindows(@EnumWindowsProc, LongInt(@WindowInfo)); FindAWindow := WindowHandle; end; end; procedure TForm1.Button1Click(Sender: TObject); var TheWindowHandle : THandle; begin TheWindowHandle := FindAWindow('Netscape - ', ''); if TheWindowHandle = 0 then ShowMessage('Window Not Found!') else BringWindowToTop(TheWindowHandle); end; Вопрос: Как написать программу не имеющую ни одной формы? Ответ: Создайте новое приложение, затем удалите из проекта все unitы - (Delphi 3 - View - Project Manager) (Delphi 4 - Project - Remove from project) Откройте файл проекта (Delphi 3 - View - Project Source) (Delphi 3 - Project - View Source) и отредактируйте его так как приведино ниже. Пример: program Project1; {$R *.RES} uses SysUtils; var f : TextFile; begin AssignFile(f, 'TestFile.Txt'); ReWrite(f); Writeln(f, 'Test'); Close(f); end. Вопрос: Почему возникает ошибка при передаче параметров типа boolean равного True в некоторые внешней функции Ответ: В Delphi 3 значение "True" для типов ByteBool, WordBool LongBool представляется как -1 для совместимости с Microsoft Visual Basic. Многие компиляторы представляют "True" как либо "не нуль" либо 1. При передаче параметров в не Visual Basic-приложения Вам следует придерживаться следующей техники во избежание несовместимости: LongBool(Abs(True)); При приеме значений типа boolean из внешних программ Вам следует всегда проверять его на значение "False". Эта техника всегда работает, поскольку "False" всегда представляется нулем. if BoolValPassed <> False then DoSomething. Вопрос: Как получить длинное имя файла или каталога, зная короткое имя? Ответ: Используйте Win32_Find_Data поле TSearchRec. Пример: procedure TForm1.Button1Click(Sender: TObject); var SearchRec : TSearchRec; Success : integer; begin Success := SysUtils.FindFirst('C:\DownLoad\dial-u~1.htm', faAnyFile, SearchRec); if Success = 0 then begin ShowMessage(SearchRec.FindData.CFileName); end; SysUtils.FindClose(SearchRec); end; Вопрос: Как временно отключить 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; Вопрос: Как получить имя файла и путь локальной таблицы? Ответ: Следующий пример взят из файла BDE32.HLP Borland/CommonFiles/BDE directory: implementation {$R *.DFM} uses DbiTypes, DbiProcs; function fDbiFormFullName(Tbl: TTable): String; var Props: CurProps; Buffer1 : array[0..DBIMAXPATHLEN] of char; Buffer2 : array[0..DBIMAXPATHLEN] of char; begin Check(DbiGetCursorProps(Tbl.Handle,Props)); StrPCopy(Buffer1, Tbl.TableName); Check(DbiFormFullName(Tbl.DBHandle, @Buffer1, Props.szTableType, @Buffer2)); Result := StrPas(Buffer2); end; procedure TForm1.Button1Click(Sender: TObject); begin Memo1.Lines.Add(fDbiFormFullName(Table1)); end; Примечание: Таблица должна быть открытой. Работает с локальными таблицами. Вопрос: Как получить дескриптор панели задач (TaskBar)? Ответ: hTaskbar := FindWindow('Shell_TrayWnd', Nil ); Вопрос: Как из программы запустить Screen Saver? Ответ: Представленная ниже функция демонстрирует как это сделать function TurnScreenSaverOn : bool; var b : bool; begin result := false; if SystemParametersInfo(SPI_GETSCREENSAVEACTIVE, 0, @b, 0) <> true then exit; if not b then exit; PostMessage(GetDesktopWindow, WM_SYSCOMMAND, SC_SCREENSAVE, 0); result := true; end; Вопрос: Как выяснить установлены ли в системе шрифты TrueType? Ответ: function IsTrueTypeAvailable : bool; var {$IFDEF WIN32} rs : TRasterizerStatus; {$ELSE} rs : TRasterizer_Status; {$ENDIF} begin result := false; if not GetRasterizerCaps(rs, sizeof(rs)) then exit; if rs.WFlags and TT_AVAILABLE <> TT_AVAILABLE then exit; if rs.WFlags and TT_ENABLED <> TT_ENABLED then exit; result := true; end; Вопрос: Как переслать файл в Мусорную Корзину? Ответ: Используйте функцию SHFileOperation(). uses ShellAPI; procedure SendToRecycleBin(FileName: string); var SHF: TSHFileOpStruct; begin with SHF do begin Wnd := Application.Handle; wFunc := FO_DELETE; pFrom := PChar(FileName); fFlags := FOF_SILENT or FOF_ALLOWUNDO; end; SHFileOperation(SHF); end; procedure TForm1.Button1Click(Sender: TObject); begin SendToRecycleBin('c:\DownLoad\Test.gif'); end; Вопрос: Как изменить обои Windows програмно? Ответ: Изменить обои можно функцией SystemParametersInfo()Б переслав ей в качестве параметров константу SPI_SETDESKWALLPAPER и имя нового файла обоев. Пример: SystemParametersInfo(SPI_SETDESKWALLPAPER, 0, PChar('C:\SOMEPATH\SOME.BMP'), SPIF_SENDWININICHANGE); Вопрос: Как выяснить запущен ли Delphi / C++ Builder? Ответ: Используйте функцию FindWindow. (Класс главного окна Delphi / C++ Builder - TAppBuilder) if FindWindow('TAppBuilder', Nil) <> 0 Then ShowMessage('Delphi and or C++ Builder is running'); Вопрос: Как програмно выяснить версию Windows? Ответ: {$IFDEF WIN32} function GetVersionEx(lpOs : pointer) : BOOL; stdcall; external 'kernel32' name 'GetVersionExA'; {$ENDIF} procedure GetWindowsVersion(var Major : integer; var Minor : integer); var {$IFDEF WIN32} lpOS, lpOS2 : POsVersionInfo; {$ELSE} l : longint; {$ENDIF} begin {$IFDEF WIN32} GetMem(lpOS, SizeOf(TOsVersionInfo)); lpOs^.dwOSVersionInfoSize := SizeOf(TOsVersionInfo); while getVersionEx(lpOS) = false do begin GetMem(lpos2, lpos^.dwOSVersionInfoSize + 1); lpOs2^.dwOSVersionInfoSize := lpOs^.dwOSVersionInfoSize + 1; FreeMem(lpOs, lpOs^.dwOSVersionInfoSize); lpOS := lpOs2; end; Major := lpOs^.dwMajorVersion; Minor := lpOs^.dwMinorVersion; FreeMem(lpOs, lpOs^.dwOSVersionInfoSize); {$ELSE} l := GetVersion; Major := LoByte(LoWord(l)); Minor := HiByte(LoWord(l)); {$ENDIF} end; procedure TForm1.Button1Click(Sender: TObject); var Major : integer; Minor : integer; begin GetWindowsVersion(Major, Minor); Memo1.Lines.Add(IntToStr(Major)); Memo1.Lines.Add(IntToStr(Minor)); end; Вопрос: Как узнать переменные окружения (environment variable) DOS, например path? Ответ: Windows API - функция GetDOSEnvironment() для Win16 и GetEnvironmentStrings() для Win32. Пример: procedure TForm1.Button1Click(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; Вопрос: Как рисовать непосредственно на Рабочем столе? Ответ: Пример: procedure TForm1.Button1Click(Sender: TObject); var dc : hdc; begin dc := GetDc(0); MoveToEx(Dc, 0, 0, nil); LineTo(Dc, 300, 300); ReleaseDc(0, Dc); end; Вопрос: Как определить каталог Windows? Ответ: Вызовите функцию GetWindowsDirectory(). Если Вас интересует каталог System, вызовите функцию GetSystemDirectory(). Пример: {$IFNDEF WIN32} const MAX_PATH = 144; {$ENDIF} procedure TForm1.Button1Click(Sender: TObject); var a : Array[0..MAX_PATH] of char; begin GetWindowsDirectory(a, sizeof(a)); ShowMessage(StrPas(a)); GetSystemDirectory(a, sizeof(a)); ShowMessage(StrPas(a)); end; Вопрос: Как определить размер рабочего стола без Тaskbar'а? Ответ: Воспользуйтесь функцией SystemParametersInfo(), переслав ей в качестве параметров - SPI_GETWORKAREA и адрес структуры типа TRect, куда будут передан полученный результат. Пример: procedure TForm1.Button1Click(Sender: TObject); var r : TRect; begin SystemParametersInfo(SPI_GETWORKAREA, 0, @r, 0); Memo1.Lines.Add(IntToStr(r.Top)); Memo1.Lines.Add(IntToStr(r.Left)); Memo1.Lines.Add(IntToStr(r.Bottom)); Memo1.Lines.Add(IntToStr(r.Right)); end; Вопрос: Как закрыть CD програмно? Ответ: Вызовите функцию mciSendCommand (из библиотекиMMSystem) передав ей параметр MCI_SET_DOOR_CLOSED. Пример: uses MMSystem; procedure CloseCD(Drive : char); var mp : TMediaPlayer; begin result := false; Application.ProcessMessages; mp := TMediaPlayer.Create(nil); mp.Visible := false; mp.Parent := Application.MainForm; mp.Shareable := true; mp.DeviceType := dtCDAudio; mp.FileName := Drive + ':'; mp.Open; Application.ProcessMessages; mciSendCommand(mp.DeviceID, MCI_SET, MCI_SET_DOOR_CLOSED, 0); Application.ProcessMessages; mp.Close; Application.ProcessMessages; mp.free; result := true; end; procedure TForm1.Button1Click(Sender: TObject); begin CloseCD('D'); end; Вопрос: Как определить свободное дисковое пространство на дисках размером больше 2 ГБ? Ответ: Вызовите функцию GetDiskFreeSpaceEx(). Возвращаемый функцией результат типа integers конвертируйте в doubles. Пример: function GetDiskFreeSpaceEx(lpDirectoryName: PAnsiChar; var lpFreeBytesAvailableToCaller : Integer; var lpTotalNumberOfBytes: Integer; var lpTotalNumberOfFreeBytes: Integer) : bool; stdcall; external kernel32 name 'GetDiskFreeSpaceExA'; 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; Вопрос: Как спрятать Панель Задач Windows (Task Bar)? Ответ: Вначале необходимо вызвать функцию FindWindow(), чтобы определить handle TaskBar. Затем вызвите функцию ShowWindow(), передав ей в качестве параметра костанту SW_HIDE. Пример: procedure TForm1.Button1Click(Sender: TObject); var hTaskBar : THandle; begin hTaskbar := FindWindow('Shell_TrayWnd', Nil); ShowWindow(hTaskBar, SW_HIDE); end; procedure TForm1.Button2Click(Sender: TObject); var hTaskBar : THandle; begin hTaskbar := FindWindow('Shell_TrayWnd', Nil); ShowWindow(hTaskBar, SW_SHOWNORMAL); end; Вопрос: Как определить подключен ли компюетер к сети. Ответ: Воспользуйтесь функцией GetSystemMetrics(), переслав ей флаг SM_NETWORK. Пример: procedure TForm1.Button1Click(Sender: TObject); begin if GetSystemMetrics(SM_NETWORK) AND $01 = $01 then ShowMessage('Machine is attached to network') else ShowMessage('Machine is not attached to network'); end; Вопрос: Как добавить документ в меню ПУСК - ДОКУМЕНТЫ? Ответ: Используйте функцию SHAddToRecentDocs. Пример: uses ShlOBJ; procedure TForm1.Button1Click(Sender: TObject); var s : string; begin s := 'C:\DownLoad\ntkfaq.html'; SHAddToRecentDocs(SHARD_PATH, pChar(s)); end; Вопрос: Как программно изменить текущий порт принтера? Ответ: Используйте метод SetPrinter класса TPrinter. Пример: uses Printers; {$IFNDEF WIN32} const MAX_PATH = 144; {$ENDIF} procedure TForm1.Button1Click(Sender: TObject); var pDevice : pChar; pDriver : pChar; pPort : pChar; hDMode : THandle; PDMode : PDEVMODE; begin if PrintDialog1.Execute then begin GetMem(pDevice, cchDeviceName); GetMem(pDriver, MAX_PATH); GetMem(pPort, MAX_PATH); Printer.GetPrinter(pDevice, pDriver, pPort, hDMode); Printer.SetPrinter(pDevice, PDriver, 'FILE:', hDMode); FreeMem(pDevice, cchDeviceName); FreeMem(pDriver, MAX_PATH); FreeMem(pPort, MAX_PATH); Printer.BeginDoc; Printer.Canvas.TextOut(100, 100, 'Delphi Is RAD!'); Printer.EndDoc; end; end; Вопрос: Как корректно определить изменения в оборудовании PlugNPlay? Ответ: Пример: type TForm1 = class(TForm) Button1: TButton; private { Private declarations } procedure WMDeviceChange(var Message: TMessage); message WM_DEVICECHANGE; public { Public declarations } end; var Form1: TForm1; implementation {$R *.DFM} const DBT_DEVICEARRIVAL = $8000; const DBT_DEVICEQUERYREMOVE = $8001; const DBT_DEVICEQUERYREMOVEFAILED = $8002; const DBT_DEVICEREMOVEPENDING = $8003; const DBT_DEVICEREMOVECOMPLETE = $8004; const DBT_DEVICETYPESPECIFIC = $8005; const DBT_CONFIGCHANGED = $0018; procedure TForm1.WMDeviceChange(var Message: TMessage); var s : string; begin {Do Something here} case Message.wParam of DBT_DEVICEARRIVAL : s := 'A device has been inserted and is now available'; DBT_DEVICEQUERYREMOVE: begin s := 'Permission to remove a device is requested'; ShowMessage(s); {True grants premission} Message.Result := integer(true); exit; end; DBT_DEVICEQUERYREMOVEFAILED : s := 'Request to remove a device has been canceled'; DBT_DEVICEREMOVEPENDING : s := 'Device is about to be removed'; DBT_DEVICEREMOVECOMPLETE : s := 'Device has been removed'; DBT_DEVICETYPESPECIFIC : s := 'Device-specific event'; DBT_CONFIGCHANGED : s:= 'Current configuration has changed' else s := 'Unknown Device Message'; end; ShowMessage(s); inherited; end; Вопрос: Как после записи в ini-файл сбросить cache на диск, чтоб задействовать изменения? Ответ: Вызовите функцию WriteProfileString() или WritePrivateProfileString(), передав ей в качестве параметров секции, ключа и строки - nil. Пример: WriteProfileString(nil, nil, nil); WritePrivateProfileString(nil, nil, nil, FileName); Вопрос: Как с помощью Проводника открыть конкретный каталог? Ответ: Пример: uses ShellApi; procedure TForm1.Button1Click(Sender: TObject); begin ShellExecute(0, 'explore', 'C:\WINDOWS', nil, nil, SW_SHOWNORMAL); end; Вопрос: Как запустить аплет Панели управления? Ответ: Запустить аплет Панели управления можно вызвав функцию WinExec, для выполнения файла control.exe, которому передано имя аплета. Обычно аплеты панели управления расположены в каталоге System Windows и имеют расширение .cpl. Пример: procedure TForm1.Button1Click(Sender: TObject); begin WinExec('C:\WINDOWS\CONTROL.EXE TIMEDATE.CPL', sw_ShowNormal); WinExec('C:\WINDOWS\CONTROL.EXE MOUSE', sw_ShowNormal); WinExec('C:\WINDOWS\CONTROL.EXE PRINTERS', sw_ShowNormal); end; Вопрос: Как печатать в цвете? Ответ: Обычно нет необходимости переводить принтер в режим цветной печати, если он установлен в этот режим. Windows автоматически переведет цветную печать в черно-белую, если принтер не поддерживает цветной печати. Если Вам необходимо програмно изменить режим цвета, Вы можете обратится к структуре DevMode драйвера принтера. Пример: uses Printers; procedure TForm1.Button1Click(Sender: TObject); var Device : array[0..255] of char; Driver : array[0..255] of char; Port : array[0..255] of char; hDMode : THandle; PDMode : PDEVMODE; begin with Printer do begin PrinterIndex := PrinterIndex; GetPrinter(Device, Driver, Port, hDMode); if hDMode <> 0 then begin pDMode := GlobalLock(hDMode); if pDMode <> nil then begin pDMode.dmFields := pDMode.dmFields or dm_Color; pDMode.dmColor := DMCOLOR_COLOR; GlobalUnlock(hDMode); end; end; PrinterIndex := PrinterIndex; BeginDoc; Canvas.Font.Color := clRed; Canvas.TextOut(100,100, 'Red As A Rose!'); EndDoc; end; end; Вопрос: Как открыть URL браузером, установленным по умолчанию? Ответ: Используйте функцию ShellExecute. Пример: uses ShellAPI; procedure TForm1.Button1Click(Sender: TObject); begin ShellExecute(Form1.Handle, nil, 'http://www.borland.com', nil, nil, SW_SHOWNORMAL); end; Вопрос: Как стереть ехе-файл во время его исполнения? Ответ: Это не возможно. Вы можете стереть его во время следующего запуска Windows, добавив ключ RunOnce: HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\RunOnce Пример: uses Registry; procedure TForm1.Button1Click(Sender: TObject); var reg: TRegistry; begin reg := TRegistry.Create; with reg do begin RootKey := HKEY_LOCAL_MACHINE; LazyWrite := false; OpenKey('Software\Microsoft\Windows\CurrentVersion\RunOnce', false); WriteString('Delete Me!','command.com /c del FILENAME.EXT'); CloseKey; free; end; end; Вопрос: Как програмноинсталировать шрифты TrueType? Ответ: Скопируйте файл шрифта в директорию Windows\Fonts, и добавьте строку с именем шрифта и его расположением в разделе "'Software\Microsoft\Windows\CurrentVersion\Fonts". Вызовите функцию AddFontRecource(), а затем передайте системе сообщение WM_FONTCHANGE. И наконец, для удоления установленного шрифта, вызовите функцию RemoveFontRecource(), а затем передайте системе сообщение WM_FONTCHANGE. Пример: uses Registry; procedure TForm1.Button1Click(Sender: TObject); var reg: TRegistry; b : bool; begin CopyFile('C:\DOWNLOAD\FP000100.TTF', 'C:\WINDOWS\FONTS\FP000100.TTF', b); reg := TRegistry.Create; reg.RootKey := HKEY_LOCAL_MACHINE; reg.LazyWrite := false; reg.OpenKey('Software\Microsoft\Windows\CurrentVersion\Fonts', false); reg.WriteString('TESTMICR (TrueType)','FP000100.TTF'); reg.CloseKey; reg.free; {Add the font resource} AddFontResource('c:\windows\fonts\FP000100.TTF'); SendMessage(HWND_BROADCAST, WM_FONTCHANGE, 0, 0); {Remove the resource lock} RemoveFontResource('c:\windows\fonts\FP000100.TTF'); SendMessage(HWND_BROADCAST, WM_FONTCHANGE, 0, 0); end; Вопрос: Как получить список часовых поясов? Ответ: Пример: uses Registry; procedure TForm1.Button1Click(Sender: TObject); var reg : TRegistry; ts : TStrings; i : integer; begin reg := TRegistry.Create; reg.RootKey := HKEY_LOCAL_MACHINE; reg.OpenKey( 'SOFTWARE\Microsoft\Windows\CurrentVersion\Time Zones', false); if reg.HasSubKeys then begin ts := TStringList.Create; reg.GetKeyNames(ts); reg.CloseKey; for i := 0 to ts.Count -1 do begin reg.OpenKey( 'SOFTWARE\Microsoft\Windows\CurrentVersion\Time Zones\' + ts.Strings[i], false); Memo1.Lines.Add(ts.Strings[i]); Memo1.Lines.Add(reg.ReadString('Display')); Memo1.Lines.Add(reg.ReadString('Std')); Memo1.Lines.Add(reg.ReadString('Dlt')); Memo1.Lines.Add('----------------------'); reg.CloseKey; end; ts.Free; end else reg.CloseKey; reg.free; end; Вопрос: Какие значения возвращает функция GetTimeZoneInformation()? Ответ: const TIME_ZONE_ID_UNKNOWN = 0; const TIME_ZONE_ID_STANDARD = 1; const TIME_ZONE_ID_DAYLIGHT = 2; Вопрос: Как сделать прозрачным фон текста? Ответ: Используйте функцию SetBkMode(). Пример: procedure TForm1.Button1Click(Sender: TObject); var OldBkMode : integer; begin with Form1.Canvas do begin Brush.Color := clRed; FillRect(Rect(0, 0, 100, 100)); Brush.Color := clBlue; TextOut(10, 20, 'Not Transparent!'); OldBkMode := SetBkMode(Handle, TRANSPARENT); TextOut(10, 50, 'Transparent!'); SetBkMode(Handle, OldBkMode); end; end; Вопрос: Как получить информацию о версии файла? Ответ: Для этого необходимо вызвать несколько функций API. В приведеннном ниже примере проверяется версия shell32.dll. Функция возвращает значение True - если версия DLL больше или равна 4.71 function TForm1.CheckShell32Version: Boolean; procedure GetFileVersion(FileName: string; var Major1, Major2, Minor1, Minor2: Integer); { Helper function to get the actual file version information } var Info: Pointer; InfoSize: DWORD; FileInfo: PVSFixedFileInfo; FileInfoSize: DWORD; Tmp: DWORD; begin // Get the size of the FileVersionInformatioin InfoSize := GetFileVersionInfoSize(PChar(FileName), Tmp); // If InfoSize = 0, then the file may not exist, or // it may not have file version information in it. if InfoSize = 0 then raise Exception.Create('Can''t get file version information for ' + FileName); // Allocate memory for the file version information GetMem(Info, InfoSize); try // Get the information GetFileVersionInfo(PChar(FileName), 0, InfoSize, Info); // Query the information for the version VerQueryValue(Info, '\', Pointer(FileInfo), FileInfoSize); // Now fill in the version information Major1 := FileInfo.dwFileVersionMS shr 16; Major2 := FileInfo.dwFileVersionMS and $FFFF; Minor1 := FileInfo.dwFileVersionLS shr 16; Minor2 := FileInfo.dwFileVersionLS and $FFFF; finally FreeMem(Info, FileInfoSize); end; end; var tmpBuffer: PChar; Shell32Path: string; VersionMajor: Integer; VersionMinor: Integer; Blank: Integer; begin tmpBuffer := AllocMem(MAX_PATH); // Get the shell32.dll path try GetSystemDirectory(tmpBuffer, MAX_PATH); Shell32Path := tmpBuffer + '\shell32.dll'; finally FreeMem(tmpBuffer); end; // Check to see if it exists if FileExists(Shell32Path) then begin // Get the file version GetFileVersion(Shell32Path, VersionMajor, VersionMinor, Blank, Blank); // Do something, such as require a certain version // (such as greater than 4.71) if (VersionMajor >= 4) and (VersionMinor >= 71) then Result := True else Result := False; end else Result := False; end; Вопрос: Как создать иконку из bitmap'а? Ответ: Нужно создать два bitmap'а: bitmap-маску ("AND" bitmap) и bitmap-картинку (XOR bitmap). Потом передать дескрипторы "AND" и "XOR" bitmap-ов API функции CreateIconIndirect() Пример: procedure TForm1.Button1Click(Sender: TObject); var IconSizeX : integer; IconSizeY : integer; AndMask : TBitmap; XOrMask : TBitmap; IconInfo : TIconInfo; Icon : TIcon; begin {Get the icon size} IconSizeX := GetSystemMetrics(SM_CXICON); IconSizeY := GetSystemMetrics(SM_CYICON); {Create the "And" mask} AndMask := TBitmap.Create; AndMask.Monochrome := true; AndMask.Width := IconSizeX; AndMask.Height := IconSizeY; {Draw on the "And" mask} AndMask.Canvas.Brush.Color := clWhite; AndMask.Canvas.FillRect(Rect(0, 0, IconSizeX, IconSizeY)); AndMask.Canvas.Brush.Color := clBlack; AndMask.Canvas.Ellipse(4, 4, IconSizeX - 4, IconSizeY - 4); {Draw as a test} Form1.Canvas.Draw(IconSizeX * 2, IconSizeY, AndMask); {Create the "XOr" mask} XOrMask := TBitmap.Create; XOrMask.Width := IconSizeX; XOrMask.Height := IconSizeY; {Draw on the "XOr" mask} XOrMask.Canvas.Brush.Color := ClBlack; XOrMask.Canvas.FillRect(Rect(0, 0, IconSizeX, IconSizeY)); XOrMask.Canvas.Pen.Color := clRed; XOrMask.Canvas.Brush.Color := clRed; XOrMask.Canvas.Ellipse(4, 4, IconSizeX - 4, IconSizeY - 4); {Draw as a test} Form1.Canvas.Draw(IconSizeX * 4, IconSizeY, XOrMask); {Create a icon} Icon := TIcon.Create; IconInfo.fIcon := true; IconInfo.xHotspot := 0; IconInfo.yHotspot := 0; IconInfo.hbmMask := AndMask.Handle; IconInfo.hbmColor := XOrMask.Handle; Icon.Handle := CreateIconIndirect(IconInfo); {Destroy the temporary bitmaps} AndMask.Free; XOrMask.Free; {Draw as a test} Form1.Canvas.Draw(IconSizeX * 6, IconSizeY, Icon); {Assign the application icon} Application.Icon := Icon; {Force a repaint} InvalidateRect(Application.Handle, nil, true); {Free the icon} Icon.Free; end; Вопрос: Как преобразовать RGB-цвет в оттенки серого? Ответ: В приведенном примере для преобразования RGB-цвета используются коэффициенты, принятые в телевидении: function RgbToGray(RGBColor : TColor) : TColor; var Gray : byte; begin Gray := Round((0.30 * GetRValue(RGBColor)) + (0.59 * GetGValue(RGBColor)) + (0.11 * GetBValue(RGBColor ))); Result := RGB(Gray, Gray, Gray); end; procedure TForm1.FormCreate(Sender: TObject); begin Shape1.Brush.Color := RGB(255, 64, 64); Shape2.Brush.Color := RgbToGray(Shape1.Brush.Color); end; Вопрос: Как держать приложение в минимизированном виде? Ответ: Установите свойство WindowState в Minimized. Создайте обработчик сообщения WM_QueryOpen. Пример: {Place this code in the private section of the Form declaration} procedure WMQueryOpen(VAR Msg : TWMQueryOpen); message WM_QUERYOPEN; {Place this code in the Form implementation section} procedure TForm1.WMQueryOpen(VAR Msg : TWMQueryOpen); begin Msg.Result := 0; end; Вопрос: при вызове функции RegisterClass я получаю ошибку: "Incompatible types: 'TPersistantClass' and 'TWndClassA'" Ответ: Функция RegisterClass() обьявлена в модулях Classes и Windows unit. Чтобы вызвать функцию из модуля Windows просто добавте префикс "Windows." Пример: procedure TForm1.Button1Click(Sender: TObject); wc : TWndClass; begin Windows.RegisterClass(wc) end; Вопрос: Как принять файлы, брошенные на мою форму по drag & drop Ответ: Нужно сообщить Windows, что ваша форма принимает файлы по drag & drop с помощью функции Shell API DragAcceptFiles.(в обработчике события form create) Затем нужно реагироавть на сообытия drag & drop чтобы принять файлы. (см. пример) unit Unit1; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls; type TForm1 = class(TForm) Memo1: TMemo; procedure FormCreate(Sender: TObject); private procedure WMDROPFILES(var Message: TWMDROPFILES); message WM_DROPFILES; { Private declarations } public { Public declarations } end; var Form1: TForm1; implementation {$R *.DFM} uses ShellApi; procedure TForm1.FormCreate(Sender: TObject); begin {Let Windows know we accept dropped files} DragAcceptFiles(Form1.Handle, True); end; procedure TForm1.WMDROPFILES(var Message: TWMDROPFILES); var NumFiles : longint; i : longint; buffer : array[0..255] of char; begin {How many files are being dropped} NumFiles := DragQueryFile(Message.Drop, -1, nil, 0); {Accept the dropped files} for i := 0 to (NumFiles - 1) do begin DragQueryFile(Message.Drop, i, @buffer, sizeof(buffer)); Form1.Memo1.Lines.Add(buffer); end; end; end. Вопрос:
Как создать задержку не подвешивая систему без компонента TTimer ? Ответ: В примере используется вызов Application.ProcessMessages для того, чтобы Windows обрабатывал сообщения во время цикла задержки. procedure Delay(ms : longint); var TheTime : LongInt; begin TheTime := GetTickCount + ms; while GetTickCount < TheTime do Application.ProcessMessages; end; procedure TForm1.Button1Click(Sender: TObject); begin ShowMessage('Start Test'); Delay(2000); ShowMessage('End Test'); end; Вопрос:
Как програмно перезагрузить Windows? Ответ: Используйте функцию ExitWindows(). В качестве первого параметра ей передается она из трех констант: EW_RESTARTWINDOWS EW_REBOOTSYSTEM EW_EXITANDEXECAPP Второй параметр используется для перезагрузки компьютера в режиме эмуляции MS DOS. Пример: ExitWindows(EW_RESTARTWINDOWS, 0 );
(c) 1999 . Last Modified Friday, 06-Aug-99 11:12:04 PST. Translated & Adapted by 17-Aug-1999
Заготовки для сборщика мусора.
Для использования мониторинга, модуль GCSystem.pas нужно включить первым в проект. В каталоге программы создастся файл log.txt, в котором будут все выделения и освобождения памяти, а также создание/уничтожение объектов.
Файл log.txt можно обработать анализатором (analog.bat). При этом создадутся файлы log1.txt с неосвобождённой памятью и log2.txt с неудалёнными объектами.
GCSystem.pas работает как при сборке с пакетами, так и без.
Примеры логов:
Log1.txt: | +16, Address: 13241272 +16, Address: 13244248 ReallocMem, +32 Address Source: 13244248 Address Dest: 13244248 +16, Address: 13244644 +16, Address: 13244664 ReallocMem, +48 Address Source: 13244248 Address Dest: 13244732 +16, Address: 13244840 +16, Address: 13244876 +16, Address: 13244912 +16, Address: 13244952 +16, Address: 13244996 +128, Address: 13245016 ReallocMem, +32 Address Source: 13244664 Address Dest: 13246016 ReallocMem, +48 Address Source: 13246016 Address Dest: 13246080 +16, Address: 13244664 ReallocMem, +112 Address Source: 13246080 Address Dest: 13262008 | Log2.txt: | +16, Address: 13241252 Create: TIntConst, Address: 13241252 +16, Address: 13241292 Create: TIntConst, Address: 13241292 +48, Address: 13242948 Create: THelpManager, Address: 13242948 +20, Address: 13243000 Create: TObjectList, Address: 13243000 +20, Address: 13243024 Create: TObjectList, Address: 13243024 +20, Address: 13243048 Create: TObjectList, Address: 13243048 +16, Address: 13244112 Create: TIntConst, Address: 13244112 +20, Address: 13244132 Create: TRegGroup, Address: 13244132 +16, Address: 13244156 Create: TList, Address: 13244156 +48, Address: 13244176 Create: TStringList, Address: 13244176 +16, Address: 13244228 Create: TList, Address: 13244228 +36, Address: 13244784 Create: TWinHelpViewer, Address: 13244784 +16, Address: 13244932 Create: TList, Address: 13244932 +28, Address: 13260600 Create: TCriticalSection, Address: 13260600 | Скачать: — Тестовый проект (Delphi 6) — Модуль для Delphi 5
"Живой Desktop" — вариант использования Shell
Что это:Прикольное расширение Shellа. Назначение:Разовое применение с целью разрушить устоявшееся представление индивидума о незыблемости иконок на рабочем столе. Показания: WinNT4/Win2000 (для других не проверялось); Непосредственный доступ к жертве; D6 +/- 3 версии я думаю.
Внимание - это демонстрация, содержит как минимум одну ошибку приводящую к завершению работы Explorerа без сохранения данных через ~ 20 мин. Предыстория: Работает у нас один парень все ничего вот только у него странная тяга к иконкам на рабочем столе что выражается в их не мерянном количестве и особо структурированном распределении (сложном и непонятном с полпинка). Как то раз, с утречка он включает комп и... О БОЖЕ !?!?!, по неизвестной причине, ОНИ (иконки - прядка 30~40 штук) были упорядочены!!! и выровнены!!! стандартным образом... что тут началось... (вырезано по требованию правозащитных организаций ) прям конец света :) в общем стены устояли. Парень наотрез отказался работать до тех пор пока не расставит все иконки в только ему ведомом порядке и в соответствии с распределением космических сил - ушел в нирвану на пол дня. Ну и я, под впечатлением от силы воздействия иконок, решил написать прогу по их своеобразному упрядовачиванию в (как говорится) real-time :) Как сделано: После взвешивания цели и возможных средств доставки было выбрано - повесить на получение контекстного меню (Explorer файл/папка) дллку в которой собственно и осуществляется вся работа. В качестве основы был взят пример \Borland\Delphi6\Demos\ActiveX\ShellExt\.. Реализовано три алгоритма поведения иконок - черви (Worms), частицы (Atoms) и мышь серая (Mouse). Worms: черви в виде цепочек иконок бегают по рабочему столу поедая друг друга увеличиваясь в длине. Atoms: мечутся по экрану с учетом связей между собой. Mouse: избегают курсора мыши. Содержание: ContextM.pas - реализация IContextMenu DeskHelp.pas - получение хендла ListView рабочего стола UthDeskIcon.pas - алгоритмы по управлению иконками fsc.reg - регистрация в системе FtpSC32.dpr - проект дллки ReadMe.txt - хмм Скачать (56K)
Митронов Станислав
|