Работа с DLL библиотеками

DLL - Dynamic Link Library иначе динамически подключаемая библиотека, которая позволяет многократно применять одни и те же функции в разных программах. На самом деле довольно удобное средство, тем более что однажды написанная библиотека может использоваться во многих программах. В сегодняшнем уроке мы научимся работать с dll и конечно же создавать их!
Ну что ж начнём!

Для начала создадим нашу первую Dynamic Link Library! Отправляемся в Delphi и сразу же лезем в меню File -> New ->Other.

Выбираем в списке Dynamic-Link Library (в версиях младше 2009 Delphi пункт называется DLL Wizard).

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


library Project2;  
//Вы, наверное уже заметили, что вместо program   
//при создании dll используется слово library.  
//Означающее библиотека.  
uses  
SysUtils, dialogs,  
Classes; // Внимание ! Не забудьте указать эти модули,  
// иначе код работать не будет  
{$R *.res}  
{В ЭТУ ЧАСТЬ ПОМЕЩАЕТСЯ КОД DLL}  
  
Procedure FirstCall; stdcall; export;  
//Stdcall – При этом операторе параметры помещаются в стек  
//справа налево, и выравниваются на стандартное значение  
//Экспорт в принципе можно опустить, используется для уточнения  
//экспорта процедуры или функции.  
Begin  
ShowMessage('Моя первая процедура в dll');  
//Вызываем сообщение на экран  
End;  
  
Procedure DoubleCall; stdcall; export;  
Begin  
ShowMessage('Моя вторая процедура');  
//Вызываем сообщение на экран  
End;  
  
Exports FirstCall, DoubleCall;  
//В Exports содержится список экспортируемых элементов.  
//Которые в дальнейшем будут импортироваться какой-нибудь программой.  
begin  
End.  

На этом мы пока остановимся т.к. для простого примера этого будет вполне достаточно. Сейчас сохраняем наш проект, лично я сохранил его под именем Project2.dll и нажимаем комбинацию клавиш CTRL+F9 для компиляции библиотеки. В папке, куда вы сохранили dpr файл обязан появится файл с расширением dll, эта и есть наша только что созданная библиотека. У меня она называется Project2.dll

Займёмся теперь вызовом процедур из данной библиотеки. Создаём по стандартной схеме новое приложение. Перед нами ничего необычного просто форма. Сохраняем новое приложение в какую-нибудь папку. И в эту же папку копируем только что созданную dll библиотеку. Т.е. в данном примере Project2.dll

Теперь вам предстоит выбирать, каким способом вызывать функции из библиотеки. Всего существует два метода вызова.

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


Procedure FirstCall; stdcall; external 'Project2.dll';  
// Вместо Project2.dll может быть любое имя библиотеки  
Procedure DoubleCall; stdcall; external 'Project2.dll';  

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

Теперь для того, что бы вызвать данные процедуры нам необходимо лишь вставить их названия в любое место кода, что мы сейчас и сделаем. Кидаем на форму 2 компонента Button с закладки Standart и создаем на каждой обработчик событий OnClick

OnClick первой кнопки:


Procedure TForm1.Button1Click(Sender: TObject);  
Begin  
FirstCall; // Имя процедуры, которая находится в dll  
End;  

OnClick второй кнопки:


Procedure TForm1.Button2Click(Sender: TObject);  
Begin  
DoubleCall; // Имя процедуры, которая находится в dll  
End;  

Вот и все !

Способ № 2:
Сложнее чем первый, но у него есть свои плюсы, а самое главное, что он идеально подходит для плагинов.
Для применения данного метода, первым делом объявляем несколько глобальных переменных:


Var  
LibHandle: HModule; //Ссылка на модуль библиотеки  
FirstCall: procedure; stdcall;  
//Имена наших процедур лежащих в библиотеке.  
DoubleCall: procedure; stdcall;  

Затем после ключевого слова implementation напишем процедуру которая будет загружать нашу библиотеку:


Procedure LoadMyLibrary(FileName: String);  
Begin  
LibHandle:= LoadLibrary(PWideChar(FileName));  
//Загружаем библиотеку!  
// Внимание ! PChar для версий ниже 2009 Delphi  
If LibHandle = 0 then begin  
MessageBox(0,'Невозможно загрузить библиотеку',0,0);  
Exit;  
End;  
FirstCall:= GetProcAddress(LibHandle,'FirstCall');  
//Получаем указатель на объект  
//1-ий параметр ссылка на модуль библиотеки  
//2-ой параметр имя объекта в dll  
DoubleCall:= GetProcAddress(LibHandle,'DoubleCall');  
If @FirstCall = nil then begin  
//Проверяем на наличие этой функции в библиотеке.  
MessageBox(0,'Невозможно загрузить библиотеку',0,0);  
Exit;  
End;  
If @DoubleCall = nil then begin  
//Проверяем на наличие этой функции в библиотеке.  
MessageBox(0,'Невозможно загрузить библиотеку',0,0);  
Exit;  
End; End;   

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


Procedure TForm1.FormCreate(Sender: TObject);  
Begin  
LoadMyLibrary('Project2.dll');  
End;  

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

OnClick первой кнопки:


Procedure TForm1.Button1Click(Sender: TObject);  
Begin  
FirstCall; // Имя процедуры, которая находится в dll  
End;  

OnClick второй кнопки:


Procedure TForm1.Button2Click(Sender: TObject);  
Begin  
DoubleCall; // Имя процедуры, которая находится в dll  
End;  

Ну и напоследок создаем обработчик событий OnDestroy на форме, в котором выгружаем dll библиотеку из памяти


Procedure TForm1.FormDestroy(Sender: TObject);  
Begin  
FreeLibrary(LibHandle);  
//Выгружаем библиотеку из памяти.  
End;  

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

Добавлено: 07 Августа 2018 21:12:59 Добавил: Андрей Ковальчук

Рисуем график функции в Delphi

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

Рисование по пикселям

Рисовать на канве можно разными способами. Первый вариант - рисовать по пикселям. Для этого используется свойство канвы Pixels. Это свойство представляет собой двумерный массив, который отвечает за цвета канвы. Например Canvas.Pixels[10,20] - соответствует цвету пикселя с координатами (10,20). С массивом пикселей можно обращаться, как с любым свойством: изменять цвет, задавая пикселю новое значение, или определять его цвет, по хранящемуся в нем значению. На примере ниже мы зададим черный цвет пикселю с координатами (10,20):

Canvas.Pixels[10,20]:=clBlack;


Теперь мы попробуем нарисовать график функции F(x), если известен диапазон ее изменений Ymax и Ymin, и диапазон изменения аргумента Xmax и Xmin. Для этого мы напишем пользовательскую функцию, которая будет вычислять значение функции F в точке x, а также будет возвращать максимум и минимум функции и ее аргумента.


function Tform1.F(x:real; var Xmax,Xmin,Ymax,Ymin:real):real;  
begin  
F:=Sin(x);  
Xmax:=4*pi;  
Xmin:=0;  
Ymax:=1;  
Ymin:=-1;  
end;  

Не забудьте также указать заголовок этой функциии в разделе Public:


public  
{ public declarations }  
function F(x:real; var Xmax,Xmin,Ymax,Ymin:real):real;  

Здесь для ясности мы просто указали диапазон изменения функции Sin(x) и ее аргумента, ниже эта функция будет описана целиком. Параметры Xmax, Xmin, Ymax, Ymin - описаны со словом Var потому что они являются входными-выходными, т.е. через них функция будет возвращать значения вычислений этих данных в основную программу. Поэтому надо объявить Xmax, Xmin, Ymax, Ymin как глобальные переменные в разделе Implementation:


implementation  
var Xmax,Xmin,Ymax,Ymin:real;  

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


procedure TForm1.Button1Click(Sender: TObject);  
var x,y:real;  
PX,PY:longInt;  
begin  
for PX:=0 to Image1.Width do  
begin  
x:=Xmin+PX*(Xmax-Xmin)/Image1.Width;  
y:=F(x,Xmax,Xmin,Ymax,Ymin);  
PY:=trunc(Image1.Height-(y-Ymin)*Image1.height/(Ymax-Ymin));  
image1.Canvas.Pixels[PX,PY]:=clBlack;  
end;  
end;  

В этом коде вводятся переменные x и y, являющиеся значениями аргумента и функции, а также переменные PX и PY, являющиеся координатами пикселей, соответствующих x и y. Сама процедура состоит из цикла по всем значениям горизонтальной координаты пикселей PX компонента Image1. Сначала выбранное значение PX пересчитывается в соответствующее значение x. Затем производится вызов функции F(x) и определяется ее значение Y. Это значение пересчитывается в вертикальную координату пикселя PY.

Рисование с помощью пера Pen

У канвы имеется свойство Pen - перо. Это объект, в свою очередь имеющий ряд свойств. Одно из них - свойство Color - цвет, которым наносится рисунок. Второе свойство - Width - ширина линии, задается в пикселах (по умолчанию 1).

Свойство Style определяет вид линии и может принимать следующие значения:

psSolid Сплошная линия
psDash Штриховая линия
psDot Пунктирная линия
psDashDot Штрих-пунктирная линия
psDashDotDot Линия, чередующая штрих и два пунктира
psClear Отсутствие линии
psInsideFrame Сплошная линия, но при Width > 1 допускающая цвета, отличные от палитры Windows
Все стили со штрихами и пунктирами доступны только при толщине линий равной 1. Иначе эти линии рисуются как сплошные.

У канвы имеется свойство PenPos, типа TPoint. Это свойство определяет в координатах канвы текущую позицию пера. Перемещение пера без прорисовки осуществляется методом MoveTo(x,y). После вызова этого метода канвы точка с координатами (x,y) становится исходной, от которой методом LineTo(x,y) можно провести линию в любую точку с координатами (x,y).

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

Image1.Canvas.MoveTo(0,Image1.height div 2);


А перед заключительным end цикла добавим следующий оператор:

Image1.Canvas.LineTo(PX,PY);


Таким образом у Вас должен получиться такой код:


procedure TForm1.Button1Click(Sender: TObject);  
var x,y:real;  
PX,PY:longInt;  
begin  
Image1.Canvas.MoveTo(0,Image1.height div 2);  
for PX:=0 to Image1.Width do  
begin  
x:=Xmin+PX*(Xmax-Xmin)/Image1.Width;  
y:=F(x,Xmax,Xmin,Ymax,Ymin);  
PY:=trunc(Image1.Height-(y-Ymin)*Image1.height/(Ymax-Ymin));  
image1.Canvas.Pixels[PX,PY]:=clBlack;  
Image1.Canvas.LineTo(PX,PY);  
end;  
end;  

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

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


...  
type  
TForm1 = class(TForm)  
Button1: TButton;  
Image1: TImage;  
procedure Button1Click(Sender: TObject);  
private  
{ private declarations }  
public  
function F(x:real):real;  
procedure Extrem1(Xmax,Xmin:real; var Ymin:real);  
procedure Extrem2(Xmax,Xmin:real; var Ymax:real);  
{ public declarations }  
end;  
  
var  
Form1: TForm1;  
  
implementation  
Const e=1e-4;//точность одна тысячная  
var Xmax,Xmin,Ymax,Ymin:real;  
{$R *.DFM}  
function Tform1.F(x:real):real;  
begin  
F:=Sin(x);  
end;  
  
//поиск минимума функции  
procedure TForm1.Extrem1(Xmax,Xmin:real; var Ymin:real);  
var x,h:real; j,n:integer;  
begin  
n:=10;  
repeat  
x:=Xmin;  
n:=n*2;  
h:=(Xmax-Xmin)/n;  
Ymin:=F(Xmin);  
for j:=1 to n do begin  
if f(x)<Ymin then Ymin:=f(x);  
x:=x+h;  
end;  
until abs(f(Ymin)-f(Ymin+h))<e;  
end;  
  
//поиск максимума функции  
procedure TForm1.Extrem2(Xmax,Xmin:real; var Ymax:real);  
var x,h:real; j,n:integer;  
begin  
n:=10;  
repeat  
x:=Xmin;  
n:=n*2;  
h:=(Xmax-Xmin)/n;  
Ymax:=F(Xmin);  
for j:=1 to n do begin  
if f(x)>=Ymax then Ymax:=f(x);  
x:=x+h;  
end;  
until abs(f(Ymax)-f(Ymax+h))<e;  
end;  
  
  
procedure TForm1.Button1Click(Sender: TObject);  
var x,y:real;  
PX,PY:longInt;  
begin  
//здесь необходимо указать диапазон изменения x  
Xmax:=8*pi;  
Xmin:=0;  
  
//вычисляем экстремумы функции  
Extrem1(Xmax,Xmin,Ymin);  
Extrem2(Xmax,Xmin,Ymax);  
  
//рисуем график функции  
Image1.Canvas.MoveTo(0,Image1.height div 2);  
for PX:=0 to Image1.Width do  
begin  
x:=Xmin+PX*(Xmax-Xmin)/Image1.Width;  
y:=F(x);  
PY:=trunc(Image1.Height-(y-Ymin)*Image1.height/(Ymax-Ymin));  
image1.Canvas.Pixels[PX,PY]:=clBlack;  
Image1.Canvas.LineTo(PX,PY);  
end;  
end;  
end.  

Ну вот и всё, программа построения графика функции готова.

Добавлено: 07 Августа 2018 21:11:10 Добавил: Андрей Ковальчук

Работа со строками [Функции]

Предисловие:
Довайте с вами рассмотрим основные функции для работы со строками, вообще в свободное время мне
нравиться помудиться со строками- пропарсить какойлибо сайт и тд: наша цель, пропарсить сайт
_http://myip.ru и добыть наш ип в удобной форме x) Хотя легче конечно залезть на сам сайт и посмотреть
НО представим что ип нам нужно вывести в своей проге или отправить на мыло- конечно узнать ип можно
поразному, но мы выберем именно этот способ

Наконец-то расшифрована звукозапись первого полета в космос Ю.Гагарина:
Эй... Куда вы меня тащите...Эээ... Вы что?... Что это за хреновина? У вас что у всех крыши ПОЕХАЛИ...
Вот и мы...

Поехали
Наши строковые функции:


Length(Str: String)   
SetLength(Str: String; NewLength: Integer)   
Pos(SubStr, Str: String)   
PosEx(SubStr, Str: String; Offset: Integer)   
Delete(Str: String; Start, Length: Integer)    
Copy(Str: String; Start, Length: Integer)    
LowerCase(Str: String)    
UpperCase(Str: String)    
AnsiReplaceStr(Str, FromText, ToText: String)    
AnsiReplaceText(Str, FromText, ToText: String)   
ReverseString(Str: String)    
AnsiReverseString(Str: AnsiString)    
DupeString(Str: String; Count: Integer)  

Для выполнения поставленной задачи "Добыть IP" нам потребуються не все из них- но всё же рассмотрим
их все. Так- начнём попорядку:


//////LENGTH//////   
Length(Str: String)  

Дання функция возврощает колличество символов в строке.
Пример:


len:=length('hallо world')   

дання строка возвратит нам число 11, как видим значение которое вернула функция поместиться в
переменную len, для примера- чтобы посмотреть результат напишем showmessage(len);


len:=length('hellо world');   
showmessage(len);   
--- 
Результат "11" 
---



//////SetLength//////   
SetLength(Str: String; NewLength: Integer)   

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


len:='строка для примера';   
len:=setlength(len,6);   
showmessage(str);   
--- 
Результат "строка" 
---



//////Pos//////   
Pos(SubStr, Str: String)   

Данная функция ищет вхождение подстроки
Пример:


len:='login@mail.ru';   
num:=pos('@', len);  
showmessage(inttostr(num));   
--- 
Результат "6" 
---



//////PosEx//////   
PosEx
(SubStr, Str: String; Offset: Integer) [/DELPHI]
Для того чтобы работать с этой функцией подключети в uses "StrUtils".
Данная фукнция аналогична функции Pos с той лишь разницей что можно указать отступ, тобиш с какого
символа будет начат поиск подстроки
Пример:


len1:='Что за тупая функция';   
len2:='тупая';  
num1:=PosEx(len2, len1, 2);   
num1:=PosEx(len2, len1, 3);  
showmessage(inttostr(P1));   
showmessage(inttostr(P2));   
--- 
Результат "3" и "4" 
---



//////Delete//////   
Delete(Str: String; Start, Length: Integer)   

Функция удаляет часть строки...
Пример:


len:='Дарова Ping0, опять нажрался?';   
Delete(len, 13, 17);   
showmessage(len);  
--- 
Результат "Дарова Ping0" 
---



//////Copy////// Copy(Str: String; Start, Length: Integer)   

Данная функция копирует часть строки
Пример:


len:='Дарова Ping0, опять нажрался?';   
len:=copy(len, 15, 15);   
showmessage(len);   
--- 
Результат "опять нажрался?" 
---



//////LowerCase//////   
LowerCase(Str: String)   

Данная функция преобразует строку в нижний регистор
Пример:


len:=lowercase'I HeKeR pOetOmU nEpishU ZaborChiKoM';   
showmessage(len);   
--- 
Результат "i heker poetomu nepishu zaborchikom" 
---


жаль что данная функци я никак нереагирует на русские буквы =(


//////UpperCase//////   
UpperCase(Str: String)   

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


//////AnsiReplaceStr//////   
AnsiReplaceStr(Str, FromText, ToText: String)   

Опять же чтобы работала данная функция нцжно подключить в uses "StrUtils".
Пример:


len:='Всем Привет';   
len2:=AnsiReplaceStr(Str1, 'Привет', 'пока');   
showmessage(str2)   
--- 
Результат "Всем пока" 
---



//////AnsiReplaceText//////   
AnsiReplaceText(Str, FromText, ToText: String)   

Опять же незабываем подключить в uses "StrUtils".
Функция аналогична предыдущий функции "AnsiReplaceStr" с той лишь разницей что она независима от регистра.
Пример:


len:='Всем Привет';   
len2:=AnsiReplaceStr(Str1, 'ПриВеТ', 'пока');   
showmessage(str2)   
--- 
Результат "Всем пока" 
---



//////ReverseString//////   
ReverseString(Str: String)   

Незабываем подключить в uses "StrUtils".
Функция моя самая любимая ) Она позволяет перевернуть строку
Пример:


len:='Всем привет';   
len:=reversestring(len);   
showmessage(len)   
--- 
Результат "акоп месВ" 
---



//////AnsiReverseString//////   
AnsiReverseString(Str: AnsiString)   

Аналогична функции ReverseString


//////DupeString//////   

DupeString(Str: String; Count: Integer)
Интересная функция однако, копирует строку столько раз сколько указанно в аргументе Count
Пример:


len:='Всем привет';   
len:=dupestring(len,5);   
showmessage(len)   
--- 
Результат "Всем приветВсем приветВсем приветВсем приветВсем привет" 
---


С функциями всё- теперь довайте для приличия всёже получим этот ИП "млять, только теперь я понимаю-
что этот долбанный ип нестоил столько моего времени, которое я потратил на написание статьи )"
Кидаем на форму IdHTTP, Memo и Button
и в обработчие собития OnClick у кнопки, пишем:
Код:


var len:string; num:integer;   
begin memo1.text:=idhttp1.get('http://www.myi p.ru/get_ip.php');  
len:=memo1.text;   
num:=pos('<TD bgcolor=white align=center v align=middle>',len) +45;   
delete(len,1,num);   
len:=copy(memo1.text, num, pos('<',len)) ;   
memo1.Text:=len;   

Жмёте кнопку и получаете ип..
Ну вот впринципе и всё!
Всем спасибо за внимание- надеюсь кому пригодиться

Добавлено: 07 Августа 2018 21:09:45 Добавил: Андрей Ковальчук

Скачиваем файлы из интернета

Задача: скачать файл по http в указанную папку с использованием потока.

Делаем форму

Бросаем на форму два TEdit, TProgressBar, одну кнопку и TSaveDialog.

Для кнопки пишем маленький обработчик:


//Этой строкой мы скопируем имя файла SaveDialog1.FileName:=copy(Edit1.Text,LastDelimiter('\⁄',Edit1.Text)+1,maxint);  
if SaveDialog1.Execute then  
  Edit2.Text:=SaveDialog1.FileName;  

Теперь на форму добавим IdHTTP и кнопку (Button2) с надписью "начать закачку".

Делаем поток

С обработчиком пока повременим, а напишем самое сложное – класс для потока.


{$R *.dfm}  
//---------------------------------------  
type  
  TDownLoader = class(TThread)  
  protected  
    procedure Execute; override;  
  public  
    property URL:string read FURL write FURL;  
    property ToFolder:string read FToFolder write FToFolder;  
end;  

Первые две строки сделаны для того, чтобы было видно, где вписать код. И нажимаем Ctrl+Shift+C. Delphi допишет немного кода. Он теперь будет выглядеть так:


type  
  TDownLoader = class(TThread)  
  private  
    FToFolder: string;  
    FURL: string;  
  protected  
    procedure Execute; override;  
  published  
  public  
    property URL:string read FURL write FURL;  
    property ToFolder:string read FToFolder write FToFolder;  
  end;  
  
procedure TForm1.Button1Click(Sender: TObject);  
begin  
  SaveDialog1.FileName:=copy(Edit1.Text,LastDelimiter('\⁄',Edit1.Text)+1,maxint);  
  if SaveDialog1.Execute then  
    Edit2.Text:=SaveDialog1.FileName;  
end;  
  
{ TDownLoader }  
  
procedure TDownLoader.Execute;  
begin  
  //здесь мы начнём писать код работы  
end;  

Компонент idHTTP был брошен на форму только с одной целью – чтобы Delphi добавила все заголовочные файлы в uses. Потом его можно будет удалить. Но можно и самостоятельно вписать в uses файл idHTTP.

Главный код потока

Итак, код обработчика:


procedure TDownLoader.Execute;  
var  
  http:TIdHTTP;  
  str:TFileStream;  
begin  
  //Создим класс для закачки  
  http:=TIdHTTP.Create(nil);  
  //каталог, куда файл положить  
  ForceDirectories(ExtractFileDir(ToFolder));  
  //Поток для сохранения  
  str:=TFileStream.Create(ToFolder, fmCreate);  
  try  
    //Качаем  
    http.Get(url,str);  
  finally  
    //Нас учили чистить за собой  
    http.Free;  
    str.Free;  
end;  
  
end;  

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

Запускаем поток

И наконец, обработчик для кнопки:


procedure TForm1.Button2Click(Sender: TObject);  
var d:TDownLoader;  
begin  
  //Создадим класс потока.  
  //Поток для начала будет остановлен  
  d:=TDownLoader.Create(true);  
  //Передадим параметры потоку  
  d.URL:=Edit1.Text;  
  d.ToFolder:=Edit2.Text;  
  //Поток должен удалить себя по завершению своей работы   
  d.FreeOnTerminate:=true;  
  //И запустим его на закачку.  
  d.Resume;  
  //Теперь с процедуры мы выйдем, но поток работает  
  //и живёт своей жизней  
end;  

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

Дополнительные возможности

Добавим для начала уведомление о закачке. Добавим в public часть формы добавим строку:


public  
  { public declarations }  
  procedure thrTerminate(Sender:TObject);  
end;  

и нажмём Ctrl+Shift+C.

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


procedure TForm1.thrTerminate(Sender: TObject);  
begin  
  ShowMessage('Готово');  
end;  

И добавим её вызов в обработчике кнопки запуска:


//Поток должен удалить себя по завершению своей работы  
d.FreeOnTerminate:=true;  
d.OnTerminate:=thrTerminate; 

Делаем прогресс

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

Способ два, которым мы и воспользуемся – это посредник. Мы шлём сообщение посреднику, чтобы он сообщил другому потоку (а может и группе), чтобы он что-то сделал. Этот способ хорош тем, что поток может обрабатывать сообщения не по принуждению, а по возможности. То есть сообщения стают в очередь. В качестве посредника мы выберем саму среду Windows.

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

Поехали дальше. После uses перед Type вставим строку:


const  
  MY_MESS = WM_USER + 100;  

А в объявлении формы добавим новый метод:


procedure MyProgress(var msg:TMessage);message MY_MESS;  

И заветное Ctrl+Shift+C

В свежесозданном обработчике пишем такое:


procedure TForm1.MyProgress(var msg: TMessage);  
begin  
  case msg.WParam of  
    0: begin ProgressBar1.Max:=msg.LParam;ProgressBar1.Position:=0; end;  
    1: ProgressBar1.Position:=msg.LParam;  
  end;  
end;  

То-есть, если передали тип операции 0 – значит нужно инициализировать прогресс. Передали 1 – нужно выставить позицию. Как именно – указано в другом параметре (msg.LParam).

А теперь возвращаемся к нашим обработчикам IdHTTP, которые мы оставили без реализации. Туда нужно вписать всего по строчке. Ниже приведена реализация.


procedure TDownLoader.IdHTTP1Work(ASender: TObject; AWorkMode: TWorkMode;  
AWorkCount: Integer);  
begin  
  PostMessage(Application.MainForm.Handle,MY_MESS,1,AWorkCount);  
end;  
  
procedure TDownLoader.IdHTTP1WorkBegin(ASender: TObject; AWorkMode: TWorkMode;  
AWorkCountMax: Integer);  
begin  
  PostMessage(Application.MainForm.Handle,MY_MESS,0,AWorkCountMax);  
end;  

Заключение

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

В сопутствующем архиве вы найдёте полный рабочий пример. Он будет компилироваться на Delphi 2006 и Indy 10. На 7 Delphi сходу может не скомпилироваться, но если вы будете делать вручную и немного думать, то всё заработает. Причина – с Delphi 7 поставляется более старая версияIndy.

Добавлено: 07 Августа 2018 08:41:05 Добавил: Андрей Ковальчук

Уменьшаем размер EXE в 40 раз, или Вся правда о консольных приложениях

«Пустая» форма весит около 355 КБ, и этот начальный размер увеличивается с каждой новой версией Delphi. «Пустая» программа, написанная с использованием библиотеки KOL, уменьшающей размер исполняемого файла, — 32 КБ.

«Чистое» консольное приложение имеет размер 8 КБ, поскольку отображается как процесс и, соответственно, не имеет сложных взаимодействий с Windows-окнами. То есть можно сделать так, чтобы по Ctrl+Alt+Del консоль не было видно :).

Итак, в меню Delphi выбери File>New>Other и в появившемся окне среди прочего найди пункт Console Application. Возникнет следующая заготовка:


program Project1; //название проекта  
  
{$APPTYPE CONSOLE} //директива, указывающая на наличие консоли  
  
uses SysUtils; //подключенные модули  
  
begin //начало процесса  
  { TODO -oUser -cConsole Main : Insert code here } //комментарий от Borland  
end. //конец процесса  

Ага. Это «пустое» консольное приложение. Нажми F9, чтобы запустить его. Что ты увидел? Черное окошко вроде Сеанса MS-DOS возникло и сразу исчезло. Куда оно делось? Всё дело в том, что консольное приложение — это процесс, который, как и всё на свете, когда-нибудь закончится :). Начало процесса — ключевое слово begin, а конец — end. Поскольку между ними отсутствуют какие-либо другие команды, то end (прекращение процесса) исполняется сразу после начала, и консоль исчезает. Чтобы такого не было, надо «занять» приложение каким-нибудь циклом, желательно вечным ;). Вот так:


begin  
  repeat  
    //это наш вечный цикл  
  until 1=0;  
end.  

Обрати внимание на команду until. Наш цикл будет исполнятся до тех пор, пока 1 не станет равен 0. Угадайте сами, когда это случится :). Другой вариант:


begin  
  while true do begin  
    //вставляй код здесь  
  end;  
end.  

И еще вариант:


Label MyLabel; //«метка»  
begin  
  MyLabel:  
    //твой код здесь  
  goto MyLabel;  
end.  

В общем, вариантов сколько угодно. Главное, что консоль не будет закрываться. А теперь надо реализовать чтение и запись на полотно консоли, как это сделано в (не)старом (не)добром MS-DOS. Помогут нам в этом процедуры из модуля System.pas. Синтаксис:


WriteLn(ЧТО_ЗАПИСЫВАЕМ) //вывод данных в консоль  
  
ReadLn(ЧТО_ЧИТАЕМ) //чтение данных из консоли  

Почему же модуль System.pas не продекларирован в разделе uses? Это базовый модуль Delphi, который всегда подключен «по умолчанию». А теперь добавь к исходному коду:


WriteLn(‘Hello World!’);  

Соответствующая строка («Hello World!») будет выведена на консоль. Если эта команда будет помещена в вечный цикл (как его создать — см. выше), то строка “Hello World!” тоже будет добавляться бесконечное число раз. Чтобы это исправить, нужно написать:


Begin  
  While true do begin  
  Writeln(‘Hello World!’);  
  Readln;  
  End;  
End.  

Как ты уже понял, команда WriteLn записывает, а ReadLn — читает. При этом команда, стоящая после ReadLn, выполнится, только когда юзер нажмет клавишу Enter.

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


var S: String; //наша переменная  
begin  
while true do begin  
Writeln(‘Enter your name’+#10);  
Readln(S);  
Writeln(#10+‘Your name is ’+S);  
end;  
end.  

Здесь «№10» обозначает конец абзаца, переход курсора на следующую строку (клавиша Enter). А вот пример, где программа закрывается по команде юзера:


var s: String;  
  
begin  
while true do begin  
Readln(S); //что ввел юзер?  
//Юзер мог ввести команду и прописными буквами,  
//и строчными. Преобразуем буквы в прописные  
//командой UpperCase.  
If UpperCase(s)=‘EXIT’ then begin  
//переспросим еще раз  
Writeln(‘Do you really want to exit? [y/n]’);  
//читаем ответ юзера  
Readln(s);  
if UpperCase(s)=’Y’ then exit; //выходим  
end;  
end;  
end.  

Вот так. Сюда можно вставить какой угодно код, только подключив, если требуется, необходимые модули. Теперь еще раз откомпилируй проект и нажми Project>Information for ‘ProjectName’. Размер EXE будет около 40 килобайт, но только потому, что модуль SysUtils.pas в разделе Uses весит так много. А если ты заменишь этот модуль на Windows.pas, то программа будет занимать, как я и обещал, ВОСЕМЬ :) кило на харде :). Конечно, при условии, что ты будешь пользоваться только модулем Windows, который содержит большинство команд, необходимых в повседневности. Если ты не собираешься вступать в консольные переговоры с юзером и пользоваться процедурами WriteLn и ReadLn, то и консоль не нужна. Удали директиву {$APPTYPE CONSOLE}, чтобы черное MS-DOS’овское окошко не появлялось.

И помни: не пытайся указывать русские буквы в команде WriteLn: консоль отобразит их в другой кодировке. Чтобы это исправить, напечатай исходный (русский) текст в Блокноте и поставь шрифт Terminal. Результат будет в кодировке DOS, как его и надо указывать в процедуре WriteLn.

Очистить полотно консоли от текста можно так:


program Project1;  
  
{$APPTYPE CONSOLE}  
  
uses Windows;  
  
var  
buffer: TConsoleScreenBufferInfo; //буфер  
i: integer;  
begin  
WriteLn('Press <Enter> to clear screen');  
ReadLn;  
GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE),buffer);  
for i:=0 to buffer.dwSize.y do writeln;  
Writeln('Screen is cleared :)');  
Readln;  
end.  

Как поместить консольное приложение в StartUp? Ответ: использовать модуль ShlObj.pas.


program StartUp;  
  
{$APPTYPE CONSOLE}  
  
uses  
  ShlObj,  //!!  
  SysUtils,  
  Windows;  
var  
  Folder: Pchar; //путь к StartUp  
  List: PitemidList;  //список "специальных" папок  
begin  
  //ищем папку  
  SHGetSpecialFolderLocation(0,CSIDL_STARTUP,List);  
  new(folder);  
  SHGetPathFromIDList(List,folder);  
  //Нашли? Переходим в директорию StartUp  
  ChDir(folder);  
  //копируем файл  
  CopyFile(PChar(ExtractFilePath(paramStr(0)) + 'StartUp.exe'), 'StartUp.exe', true); //укажите имя своего EXE файла  
end.  

Теперь загляни в папку «Автозагрузка». Если ты указал в функции имя СВОЕГО файла, он должен быть уже там :).

И маленькое Западло на закуску:


//эта программа меняет системные цвета :)  
program Joke;  
uses  
  Windows;  
const  
  SysColorArray: array [0..13] of Integer = (COLOR_ACTIVEBORDER, COLOR_ACTIVECAPTION, COLOR_APPWORKSPACE, COLOR_BACKGROUND, COLOR_BTNFACE, COLOR_BTNTEXT, COLOR_CAPTIONTEXT, COLOR_INACTIVEBORDER, COLOR_INFOTEXT, COLOR_MENU, COLOR_MENUTEXT, COLOR_WINDOW, COLOR_WINDOWFRAME, COLOR_WINDOWTEXT);  
  ColorArray: array [0..12] of Integer = (16776960, 0, 16711680, 65535, 16711935, 32768, 8388608, 255, 12632256, 16777215, 15780518, 128, 32896);  
     //Цвета хранятся в модуле Graphics.pas,  
     //но мы не будем использовать его,  
     //а запишем цвета в цифровом виде.  
begin  
Sleep(30000) //спим 30 секунд  
SetSysColors(1, SysColorArray[random(13)], ColorArray[random(12)]);  
end.  

Автор работы: Трофим Роцкий

Добавлено: 07 Августа 2018 08:39:35 Добавил: Андрей Ковальчук

Управление мышью

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


type  
    TMouseEvent = procedure (Sender: TObject;  
    Button: TMouseButton;  
    Shift: TShiftState; X, Y: Integer) of object;  
    property OnMouseDown: TMouseEvent;  

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


type TMouseButton = (mbLeft, mbRight, mbMiddle);  

Параметр Shift определяет нажатие дополнительной клавиши на клавиатуре:


type TShiftState = set of (ssShift, ssAlt, ssCtrl, ssLeft, ssRight, ssMiddle, ssDouble);  

Параметры X и Y возвращают координаты курсора.

На отпускание кнопки мыши реагирует метод:


type  
    TMouseEvent = procedure (Sender: TObject;  
    Button: TMouseButton;  
    Shift: TShiftState; X, Y: Integer) of object;  
    property OnMouseUp: TMouseEvent;  

Его параметры описаны выше.

При перемещении мыши можно вызывать метод-обработчик:


type  
    TMouseMoveEvent = procedure (Sender: TObject;  
    Shift: TShiftState; X, Y: Integer) of object;  
    property OnMouseMove: TMouseMoveEvent;  

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


property OnClick: TNotifyEvent;  
property OnDblClick: TNotifyEvent;  

Первый реагирует на щелчок кнопкой, второй - на двойной щелчок.

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


property Cursor: TCursor;  

Для управления дополнительными возможностями мыши для работы в Internet (ScrollMouse) предназначены три метода обработчика, реагирующие на прокрутку:


property OnMouseWheel: TMouseWheelEvent;  
property OnMouseWheelUp: TMouseWheelUpDownEvent;  
property OnMouseWheelDown: TMouseWheelUpDownEvent;  

OnMouseWheel вызывается при прокрутке вообще, OnMouseWheelUp - при прокрутке вперёд, OnMouseWheelDown - при прокрутке назад.

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

Объявление
Описание
property Capture: HWND; Дескриптор элемента управления, над которым находится мышь
property CursorPos: TPoint; Содержит координаты указателя мыши
property Draglmmediate: Boolean; При значении True реакция на нажатие выполняется немедленно
property DragThreshold: Integer; Задержка реакции на нажатие
property MousePresent: Boolean; Определяет наличие мыши
type UINT = LongWord; property RegWheelMessage: UINT; Задает сообщение, посылаемое при прокрутке в ScrollMouse
property WheelPresent: Boolean; Определяет наличие ScrollMouse
property WheelScrollLines: Integer; Задает число прокручиваемых линий
В качестве примера обработки управляющих воздействий от мыши рассмотрим пример DemoMouse. Он очень прост. Перемещение мыши с нажатой левой кнопкой обеспечивает выделение прямоугольного фрагмента. Такую функцию вы можете наблюдать в любом графическом редакторе, а исходный код проекта использовать в собственных разработках.


unit uDemo;  
  
interface  
  
uses  
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,  
ExtCtrls, ComCtrls;  
  
type  
TMainForm = class(TForm) ColorDlg: TColorDialog;  
StatusBar: TStatusBar; Timer: TTimer;  
procedure FormMouseDown(Sender: TObject;  
Button: TMouseButton;  
Shift: TShiftState; X, Y: Integer);  
procedure FormMouseUp(Sender: TObject;  
Button: TMouseButton;  
Shift: TShiftState; X, Y: Integer);  
procedure FormMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);  
procedure TimerTimer(Sender: TObject);  
private  
MouseRect: TRect;  
IsDown: Boolean;  
RectColor: TColor;  
public  
{ public declarations }  
end;  
  
var  
MainForm: TMainForm;  
  
implementation  
  
{$R *.DFM}  
  
procedure TMainForm.FormMouseDown(Sender: TObject; Button: TMouseButton;  
Shift: TShiftState; X, Y: Integer);  
begin  
if Button = mbLeft then with MouseRect do  
begin  
IsDown := True; Left := X; Top := Y; Right := X; Bottom := Y;  
Canvas.Pen.Color := RectColor;  
end;  
if (Button = mbRight) and ColorDlg.Execute then RectColor := ColorDlg.Color;  
end;  
  
procedure TMainForm.FormMouseUp(Sender: TObject;  
Button: TMouseButton;  
Shift: TShiftState; X, Y: Integer);  
begin  
IsDown := False;  
Canvas.Pen.Color := Color;  
with MouseRect do  
Canvas.Polyline([Point(Left, Top), Point(Right, Top), Point(Right,  
Bottom), Point(Left, Bottom), Point(Left, Top)]);  
with StatusBar do  
begin  
Panels[4].Text := ''; Panels [5] .Text := '';  
end;  
end;  
  
procedure TMainForm.FonnMouseMove(Sender: TObject; Shift: TShiftState; X,  
Y: Integer);  
begin  
with StatusBar do  
begin  
Panels[2].Text := 'X: ' + IntToStr(X);  
Panels[3].Text := 'Y: ' + IntToStr(Y);  
end;  
if not IsDown then Exit; Canvas.Pen.Color := Color; with mouserect do  
begin  
Canvas.Polyline([Point(Left, Top), Point(Right, Top),  
Point(Right, Bottom), Point(Left, Bottom), Point(Left, Top)]);  
Right := X;  
Bottom := Y;  
Canvas.Pen.Color := RectColor;  
Canvas.Polyline([Point(Left, Top), Point(Right, Top),  
Point(Right, Bottom), Point(Left, Bottom), Point(Left, Top)]);  
end;  
with StatusBar do begin  
Panels [4] .Text := Ширина: ' + IntToStr(Abs(MouseRect.Right - MouseRect.Left));  
Panels[5].Text := Высота: ' + IntToStr(Abs(MouseRect.Bottom - MouseRect.Top));  
end; end;  
  
procedure TMainForm.TimerTimer(Sender: TObject);  
begin  
with StatusBar do  
begin  
Panels[0].Text := Дата: ' + DateToStr(Now); Panels[1].Text := Время: ' + TimeToStr(Now);  
end;  
end;  
  
end.  

При нажатии левой кнопки мыши в методе-обработчике FormMouseDown включается режим рисования прямоугольника (isDown := True) и задаются его начальные координаты.

При перемещении мыши по форме проекта вызывается метод-обработчик FormMouseMove, в котором координаты курсора и размеры прямоугольника передаются на панель состояния. Если левая кнопка мыши нажата (isDown = True), то осуществляется перерисовка прямоугольника.

При отпускании кнопки мыши в методе FormMouseUp рисование прямоугольника прекращается (isDown := False).

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

Метод-обработчик TimerTimer обеспечивает отображение на панели состояния текущей даты и времени.

Добавлено: 07 Августа 2018 08:38:25 Добавил: Андрей Ковальчук

Русификация консольных приложений в Delphi

С периодичностью раз в месяц-полтора конференция RU.DELPHI оглашается стонами на тему “Консоль не поет по-русски”, за которыми стоит вывод текста в консольных приложениях в кодировке OEM (Delphi IDE, как и все GUI, работает в ANSI).

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

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

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

К недостаткам можно отнести работу вне привычного IDE с его облегчающими жизнь наворотами (кодирование, компиляция и отладка в одном флаконе), а также определенные сложности при разрастании проекта, когда начинают использоваться сторонние строковые ресурсы, созданные с применением кодировки ANSI.

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

Использование фильтрующих процедур. Windows API содержит функции для преобразования между кодировками OEM и ANSI OemToChar, CharToOem, которые и предлагается использовать при выводе текста, заменяя фрагменты


Writeln(‘тра-ля-ля’);  

на


procedure MyWriteln(const S: string);  
var  
    NewStr: string;  
begin  
    SetLengtn(NewStr, Length(S));  
    CharToOem(PChar(S), PChar(NewStr));  
    Writeln(NewStr);  
end;  
...  
MyWriteln(‘тра-ля-ля’);  

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

Изменение кодовой страницы консоли. В принципе, для решения задачи есть документированный способ – изменение кодовой страницы консоли средствами Windows API. Проблема лишь в том, что в Win95/98 функция не работает. Впрочем, если приложение будет работать только в Windows NT, можно воспользоваться функцией SetConsoleOutputCP(866).

Перекрытие процедур вывода в RTL. Вывод в Pascal (еще в версиях от Borland для DOS) через Write-процедуры осуществляется посредством передачи выводимой информации в файл Output, который вполне можно подвергнуть легкой модификации с целью упростить себе жизнь.

Известно, что Write/Writeln без указания файла осуществляет вывод в файл Output. Output имеет тип TextFile, он же TTextRec, содержимое которого описано в SysUtils.pas. Есть там и поля, содержащие адреса процедур, в которые приходит на обработку поток выводимых приложением данных (в случае вывода). Не вдаваясь в подробности (желающие могут посмотреть устройство механизмов вывода в исходниках RTL), покажем, что происходит в процедуре, отвечающей за вывод (TTextRec.InOutFunc): { Реконструкция TextOut из Assign.asm }


function TextOut(var Text: TTextRec): Integer;  
var  
    Dummy: Cardinal;  
    SavePos: Integer;  
begin  
    SavePos := Text.BufPos;  
    if SavePos > 0 then  
        begin  
            Text.BufPos := 0;  
            if WriteFile(Text.Handle, Text.BufPtr^, SavePos, Dummy, nil) then  
                Result := 0 else  
                Result := GetLastError;  
        end else Result := 0;  
end;  

Теперь видно, что нужно сделать для вывода символов в нужной кодовой таблице – перед выводом в файл средствами ОС модифицировать данные в выходном буфере структуры Text, вписав следующую строку:


CharToOemBuff(Text.BufPtr, Text.BufPtr, SavePos);  

Модифицировать буфер можно, т.к. после операции записи в файл содержимое буфера фактически сбрасывается (когда в Text.BufPos записывается 0 – именно столько актуальных данных остается в буфере). Если не завязываться на эту особенность реализации, можно распределить буфер и модифицировать данные уже в нем. Впрочем, решение в любом случае достаточно сильно опирается на особенности реализации, поэтому проверить его пригодность при смене версии Delphi рекомендуется в любом случае. С другой стороны, вероятность отхода Borland от наработанного решения крайне мала.

Заметим, что кроме InOutFunc вывод в файл ОС происходит и в FlushFunc, которая в файле Output указывает на ту же функцию, что и InOutFunc. С учетом всего вышесказанного модуль, осуществляющий «русификацию» консольных приложений «на лету» будет совсем небольшим:


{ 
Модуль “русификации“ консольных приложений 
(c) Eugene Kasnerik, 1999 
e-mail: eugene1975@mail.ru 
}  
unit EsConsole;  
  
interface  
  
implementation  
  
uses  
    Windows;  
  
{ Описание структуры приведено здесь с единственной целью – 
не подключать SysUtils и, соответственно, код инициализации 
этого модуля. Консольные приложения обычно малы и 25К кода 
обработки исключений – несколько высокая плата за описание 
единственной структуры.}  
  
type  
    TTextRec = record  
        Handle: Integer;  
        Mode: Integer;  
        BufSize: Cardinal;  
        BufPos: Cardinal;  
        BufEnd: Cardinal;  
        BufPtr: PChar;  
        OpenFunc: Pointer;  
        InOutFunc: Pointer;  
        FlushFunc: Pointer;  
        CloseFunc: Pointer;  
        UserData: array[1..32] of Byte;  
        Name: array[0..259] of Char;  
        Buffer: array[0..127] of Char;  
    end;  
  
function ConOutFunc(var Text: TTextRec): Integer;  
var  
    Dummy: Cardinal;  
    SavePos: Integer;  
begin  
    SavePos := Text.BufPos;  
    if SavePos > 0 then  
        begin  
            Text.BufPos := 0;  
            CharToOemBuff(Text.BufPtr, Text.BufPtr, SavePos);  
            if WriteFile(Text.Handle, Text.BufPtr^, SavePos, Dummy, nil) then  
                Result := 0 else  
                Result := GetLastError;  
        end else Result := 0;  
end;  
  
initialization  
    Rewrite(Output); // Проводим инициализацию файла  
    { И подменяем обработчики. Есть в этом что-то от 
    хака, но цель оправдывает средства }  
    TTextRec(Output).InOutFunc := @ConOutFunc;  
    TTextRec(Output).FlushFunc := @ConOutFunc;  
end.  

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

Приведенное решение успешно использовалось в Delphi 2 и Delphi 4.

Автор: Евгений Каснерик

Добавлено: 07 Августа 2018 08:37:11 Добавил: Андрей Ковальчук

Создание своего диалога выбора цвета

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


В общем приступим! Сначала создаём форму и помещаем туда всё необходимое:
77 компонентов типа TImage и назовём их так:

MainViewer – экран вывода градиента;
ScalKontr – определяет присутствие изменяемого компонента RGB цвета в градиенте (если выбран R_GB - то красного, если G_RB – то зелёного, если B_RG – то синего) от 0 до 255, путем нажатия курсора на нужную область.
Yarcost – шкала яркости выбранного оттенка.
Proba 1 – для выода цвета, находящегося под курсором при выборе насыщенности компонентом ScalKontr.
Proba 2 – для вывода оттенка, находящегося под курсором при выборе результата и яркости с помощью MainViewer или Yarcost.
ImageZahvat – для вывода выбранной в ScalKontr насыщенности.
Itog – для вывода выбранного оттенка.
12 Edit-ов, 3 RadioButton и 2 кнопки . Их оставим в покое (в смысле не переименовываем).

Теперь нам необходимо определиться с необходимыми процедурами.

Во-первых – это процедура генерирования шкалы контраста.

ВВо-вторых – это процедура генерирования градиента в MainViewer (в соответствии с выбранным типом градиента и выбранной насыщенностью).

В-третьих – процедура генерирования шкалы яркости, в соответствии с выбранным оттенком.

ННачнём с первого. Сам нижеприведённый код я поместил в обработчик OnCreate формы.

Пояснения смотрите в комментариях к коду:


var   
  LineColor, ViewColor: TColor;   
  ColR, ColG, ColB, i, j:integer;   
begin   
  {Если выбран баланс Красного с Синим и Зелёным (R_GB)}   
  if RadioButton1.Checked then   
  begin   
    ColR:=255;   
    ColG:=0;   
    ColB:=0;   
    for j:=0 to 255 do   
    begin   
      LineColor:=RGB(255-j, 0, 0); //Изменяем значение красного цвета   
      for i:=0 to 17 do   
        ScalKontr.Canvas.Pixels[i, j]:=LineColor; //рисуем точку   
    end;   
  end;   
   
    {Если выбран баланс Зелёного с Красным и Синим (G_RB)}   
  if RadioButton2.Checked then   
  begin   
    ColR:=0;   
    ColG:=255;   
    ColB:=0;   
    for j:=0 to 255 do   
    begin   
      LineColor:=RGB(0, 255-j, 0); //Изменяем значение зелёного  цвета   
      for i:=0 to 17 do   
        ScalKontr.Canvas.Pixels[i,j]:=LineColor; //рисуем точку   
    end;   
  end;   
   
    {Если выбран баланс Синего с Красным и Зелёным (B_RG)}   
  if RadioButton3.Checked then   
  begin   
    ColR:=0;   
    ColG:=0;   
    ColB:=255;   
    for j:=0 to 255 do   
    begin   
      LineColor:=RGB(0, 0, 255-j); //Изменяем значение синего цвета   
      for i:=0 to 17 do   
        ScalKontr.Canvas.Pixels[i,j]:=LineColor; //рисуем точку   
    end;   
  end;   
end;  

Думаю здесь всё понятно. Далее приступим к основной процедуре рисования градиента в MainViewer. Вот код:


procedure GenerateRGBInOutGrad(ClrOutR, ClrOutG, ClrOutB: Integer);   
var   
  i, j: Integer; //счётчики   
  PixelColor: TColor; //цвет пиксела   
  Holst: TBitMap; //Объект для записи пикселей   
begin   
  Holst:=TBitMap.Create; //создаём объект типа TBitMap   
  Holst.Width:=256; //указываем ширины   
  Holst.Height:=256; //указываем высоту   
   
  if ClrDialog.RadioButton1.Checked then //если выбрана RadioButton1   
  begin   
     {Палитра красного с зелёным и синим}   
    for j:=0 to 255 do //цикл по оси Y   
    begin   
      for i:=0 to 255 do //цикл по оси X   
      begin   
        PixelColor:=RGB(ClrOutR, j, i); {привязываем значения зелёного и синего к изменениям координат и переводим из 
RGB в TColor}   
        Holst.Canvas.Pixels[i, j]:=PixelColor; //Рисуем точку   
      end;  
    end;  
  end;  
   
  if ClrDialog.RadioButton2.Checked=true then //если выбрана RadioButton2   
  begin   
    {Палитра зелёного с красным и синим}   
    for j:=0 to 255 do //цикл по оси Y   
    begin   
      for i:=0 to 255 do //цикл по оси X   
      begin   
        PixelColor:=RGB(j, ClrOutG, i); {привязываем значения красного и синего к изменениям координат и переводим из 
RGB в TColor}   
        Holst.Canvas.Pixels[i, j]:=PixelColor; //Рисуем точку   
      end;  
    end;  
  end;  
   
  if ClrDialog.RadioButton3.Checked=true then //если выбрана RadioButton3   
  begin   
    {Палитра синего с красным и зелёным}   
    for j:=0 to 255 do //цикл по оси Y   
    begin   
      for i:=0 to 255 do //цикл по оси X  
      begin  
        PixelColor:=RGB(j, i, ClrOutB); {привязываем значения красного и зелёного к изменениям координат и переводим из 
RGB в TColor}   
        Holst.Canvas.Pixels[i, j]:=PixelColor; //Рисуем точку  
      end;  
    end;  
  end;  
   
  ClrDialog.MainViewer.Canvas.Draw(0,0,Holst); //рисуем всю картину на компонент TImage   
  Holst.Free; //освобождаем память, занимаемую объёктом Holst   
end;  

Вот!

Ну и, наконец, код вывода шкалы яркости:


{========================================================== Процедура генерации полосы яркости выбранного оттенка}  
procedure GenerYarkost(ColR, ColG, ColB: Real);   
var   
  i, j: integer; //счётчики   
  LineColor: TColor; //цвет TColor   
  StepR, StepG, StepB: Real; {шаг изменения для каждого цвета, необходимы для равномерного смешивания цветов по всей 
длине линии яркости. }  
begin  
  ClrDialog.Edit4.Text:=IntToStr(round(ColR));   {Эти строки}   
  ClrDialog.Edit5.Text:=IntToStr(round(ColG));   {нужны лишь в моём}   
  ClrDialog.Edit6.Text:=IntToStr(round(ColB));   {примере. В Вашем может и не понадобятся.}   
   
  {Генерация шкалы яркости для выбранного оттенка}   
  StepR:=(256-ColR)/256; //определение шага для красного   
  StepG:=(256-ColG)/256; //определение шага для зелёного   
  StepB:=(256-ColB)/256; //определение шага для синего   
   
  j:=256; //здесь счётчику по Y-ку присваивается начальное значение   
  repeat  
      j:=j-1; {Цикл по Y-ку организован с помощью repeat until чтобы  
    организовать обратный отсчёт от 256 до 1. Это сделано  
    для того, чтобы заполнять шкалу не с верху вниз, а снизу  
    вверх (т.к. начальная точка экранных координат  
    расположена в верхнем правом углу.)}   
   
    ColR:=ColR+StepR; {В каждом переходе цикла по Y}   
    ColG:=ColG+StepG; {увеличиваем значение соответствующего цвета}   
    ColB:=ColB+StepB; {на соответствующий ему шаг.}   
    for i:=0 to 17 do   
    begin   
      {На всякий случай проверим значения цветов на вхождение в пределы 255}   
      if ColR>255 then ColR:=255;   
      if ColG>255 then ColG:=255;   
      if ColB>255 then ColB:=255;   
   
      LineColor:=RGB(round(ColR), round(ColG), round(ColB)); //Записываем оттенок   
      ClrDialog.Yarcost.Canvas.Pixels[i, j]:=LineColor; //Рисуем точку в компонент TImage   
    end;  
  until j=1;  
end;  

ВВот в принципе все необходимые процедуры для написания такого диалога. Всё остальное – дело техники и это вы найдёте в примере к статье. Правда есть ещё один момент, который необходимо описать – это перевод из TColor в RGB формат. Чисто для примера сделаем 3 переменные типа integer (пусть это будут ColR , ColG и ColB ) и одну переменную типа TColor (Например, ColTColor). Далее присвоим значение переменной ColTColor. Пусть это будет зелёный (clGreen). Ну и, наконец, выделим из этой переменной значения RGB компонентов в соответствующие переменные типа integer. Весь код:


procedure TestTColorToRGB;   
var   
  ColR, ColG, ColB: integer;   
  ColTColor: TColor;   
begin   
  ColTColor:=clGreen; //присваиваем зелёный цвет   
   
  ColR:= ColTColor mod $100; //выделяем красный   
  ColG:=( ColTColor div $100) mod $100; //выделяем зелёный   
  ColB:= ColTColor div $10000; //выделяем синий   
end;  

У переменных должны получиться следующие значения:


ColR = 0;  
ColG = 255;  
ColB = 0.  

Вот и всё. Конечно, для крутого графического редактора этого мало, но ведь можно и дополнить чем-нибудь ещё!

Добавлено: 07 Августа 2018 08:35:25 Добавил: Андрей Ковальчук

Размещение значка приложения в System Tray

Часто программисту приходится сталкиваться с задачей написания приложения, работающего в фоновом режиме и не нуждающегося в месте на Панели задач. Если вы посмотрите на правый нижний угол рабочего стола Windows, то наверняка найдете там приложения, для которых эта проблема решена: часы, переключатель раскладок клавиатуры, регулятор громкости и т. п. Ясно, что, как бы вы не увеличивали и не уменьшали формы своего приложения, попасть туда обычным путем не удастся. Способ для этого предоставляет Shell API.

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

Весь API System Tray состоит из 1 (одной) функции:


function Shell_NotifyIcon(dwMessage: DWORD;  IpData: PNotifylconData): BOOL;  
   
PNotifylconData = TNotifylconData;  
TNotifylconData = record   
  cbSize: DWORD;   
  Wnd: HWND;   
  uID: UINT;   
  uFlags: UINT;   
  uCallbackMessage: UINT;   
  hlcon: HICON;   
  szTip: array [0..63] of AnsiChar;   
end;  

Параметр dwMessage определяет одну из операций: NIM_ADD означает добавление значка в область, NIM_DELETE — удаление, NIM_MODIFY — изменение.

Ход операции зависит от того, какие поля структуры TNotifyiconData будут заполнены. Обязательным для заполнения является поле cbsize — там содержится размер структуры. Поле wnd должно содержать дескриптор окна, которое будет оповещаться о событиях, связанных со значком. Идентификатор сообщения Windows, которое вы хотите получать от системы о перемещениях мыши над значком, запишите в поле uCallbackMessage. Если вы хотите, чтобы при этих перемещениях над вашим значком показывалась подсказка, то задайте ее текст в поле szTip. В поле UID задается номер значка — каждое приложение может поместить на System Tray сколько угодно значков. Дальнейшие операции вы будете производить, задавая этот номер. Дескриптор помещаемого значка должен быть задан в поле hIcon. Здесь вы можете задать значок, связанный с вашим приложением, или загрузить свой — из ресурсов.

Примечание. Изменить главный значок приложения можно в диалоговом окне Project / Options на странице Application. Он будет доступен через свойство Application.Icon. Тут же можно отредактировать и строку для подсказки — свойство Application.Title.

Наконец, в поле uFlags вы должны сообщить системе, что именно вы от нее хотите, или, другими словами, какие из полей hicon, uCaiibackMessage и szTip вы на самом деле заполнили. В этом поле предусмотрена комбинация трех флагов: NIF_ICON, NIF_MESSAGE и NIF_TIP. Вы можете заполнить, скажем, поле szTip, но если вы при этом не установили флаг NIF_TIP, созданный вами значок не будет иметь строки с подсказкой.

Два приведенных ниже метода иллюстрируют сказанное. Первый из них создает значок на System Tray, а второй — уничтожает его.


const  
  WM_MYTRAYNOTIFY = WMJJSER + 123;   
   
procedure TForml.CreateTraylcon(n:Integer);   
var nidata : TNotifyiconData;   
begin   
  with nidata do   
  begin   
    cbSize := SizeOf(TNotifyiconData);   
    Wnd := Self.Handle;   
    uID := n;   
    uFiags := NIF_ICON or NIF_MESSAGE or NIFJTIP;   
    uCallBackMessage := WM_MYTRAYNOTIFY;   
    hicon := Application.Icon.Handle;   
    szTip := 'THis is Traylcon Example';   
  end;   
  Shell_NotifyIcon(NIM_ADD, @nidata);   
end;  
   
procedure TForml.DeleteTraylcon(n:Integer);  
var nidata : TNotifylconData;  
begin  
  with nidata do  
  begin  
    cbSize := SizeOf(TNotifylconData);  
    Wnd := Self.Handle;  
      uID := n;  
  end;  
  Shell_NotifyIcon(NIM_DELETE, @nidata);  
end;  

Примечание: нe забывайте уничтожать созданные вами значки на System Tray. Это не делается автоматически даже при закрытии приложения. Значок будет удален только после перезагрузки системы. Внешний вид значка, помещенного нами на System Tray, ничем не отличается от значков других приложений.

Сообщение, задаваемое в поле uCallbackMessage, по сути дела является единственной ниточкой, связывающей вас со значком после его создания. Оно объединяет в себе несколько сообщений. Когда к вам пришло такое сообщение (в примере, рассмотренном выше, оно имеет идентификатор WM_MYTRAYNOTIFY), поля в переданной в обработчик структуре типа TMessage распределены так. Параметр wParam содержит номер значка (тот самый, что задавался в поле uID при его создании), а параметр LParam — идентификатор сообщения от мыши, вроде WM_MOUSEMOVE, WM_LBUTTONDOWN и т. п. К сожалению, остальная информация из этих сообщений теряется. Координаты мыши в момент события придется узнать, вызвав функцию API GetCursorPos:


procedure TForml.WMICON(var msg: TMessage);   
var P : TPoint;  
begin  
  case msg.LParam of   
    WM_LBUTTONDOWN:   
    begin   
      GetCursorPos(p);   
      SetForegroundWindow(Application.MainForm.Handle); PopupMenul.Popup(P.X, P.Y);   
    end;  
    WM_LBUTTONUP:  
  end;  
end;  

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

Теперь решим еще две задачи. Во-первых, как сделать, чтобы приложение минимизировалось не на Панель задач (TaskBar), а на System Tray? И более того — как сразу запустить его в минимизированном виде, а показывать главную форму только по наступлении определенного события (приходу почты, наступлению определенного времени и т. п.).

Ответ на первый вопрос очевиден. Если минимизировать не только окно главной формы приложения (Application.MainForm.Handle), но и окно приложения (Application.Handle), то приложение полностью исчезнет "с экранов радаров". В этот самый момент нужно создать значок на панели System Tray. В его всплывающем меню должен быть пункт, при выборе которого оба окна восстанавливаются, а значок удаляется.

Чтобы приложение запустилось сразу в минимизированном виде и без главной формы, следует к вышесказанному добавить установку свойства Application.showMainForm в значение False. Здесь возникает одна сложность — если главная форма создавалась в невидимом состоянии, ее компоненты будут также созданы невидимыми. Поэтому при первом ее показе установим их свойство visible в значение True. Чтобы не повторять это дважды, установим флаг — глобальную переменную shownonce:


procedure TForml.HideMainForm;   
begin   
  Appiication.showMainForm := False;   
  ShowWindow(Application.Handle, SW_HIDE);   
  ShowWindow(Application.MainForm.Handle, SW_HIDE);   
end;  
   
procedure TForml.RestoreMainForm;   
var i,j : Integer;   
begin   
  Appiication.showMainForm := True;   
  ShowWindow(Application.Handle, SW_RESTORE);  
    ShowWindow(Application.MainForm.Handle, SW_RESTORE);   
  if not ShownOnce then  
    begin   
    for I := 0 to Application.MainForm.ComponentCount -1 do  
          if Application.MainForm.Components[I] is TWinControl then  
              with Application.MainForm.Components[I] as TWinControl do  
                  if Visible then   
          begin  
            ShowWindow(Handle, SW_SHOWDEFAULT);   
            for J := 0 to ComponentCount -1 do  
                          if Components[J] is TWinControl then  
                ShowWindow((Components[J] as TWinControl).Handle, SW_SHOWDEFAULT);  
          end;  
    ShownOnce := True;  
  end;  
end;  
   
procedure TForml.WMSYSCOMMAND(var msg: TMessage);  
begin  
  inherited;  
  if (Msg.wParam=SC_MINIMIZE) then  
  begin  
    HideMainForm; CreateTraylcon(l);  
  end;  
end;  
   
procedure TForml.FileOpenltemlClick(Sender: TObject);  
begin  
  RestoreMainForm;  
  DeleteTraylcon(l);  
end;  

Теперь у вас в руках полноценный набор средств для работы с панелью System Tray. В заключение необходимо добавить, что все описанное реализуется не в операционной системе, а в оболочке ОС — Проводнике (Explorer). В принципе, и Windows NT 4/2000, и Windows 95/98 допускают замену оболочки ОС на другие, например DashBoard или LightStep. Там функции панели System Tray могут быть не реализованы или реализованы через другие API. Впрочем, случаи замены оболочки достаточно редки.

Добавлено: 07 Августа 2018 08:34:20 Добавил: Андрей Ковальчук

Работа с реестром и INI-файлами в Delphi

Реестр
Добавление элементов в контекстное меню "Создать"

Создать новый документ, поместить его в папку Windows/ShellNew
В редакторе реестра найти расширение этого файла, добавить новый подключ, добавить туда строку: FileName в качестве значения которой указать имя созданного файла.
Путь к файлу который открывает не зарегистрированные файлы

Найти ключ HKEY_CLASSES_ROOT\Unknown\Shell
Добавить новый ключ Open
Под этим ключом еще ключ с именем command в котором изменить значение (По умолчанию) на имя запускаемого файла, к имени нужно добавить %1. (Windows заменит этот символ на имя запускаемого файла)
В проводнике контекстное меню "Открыть в новом окне"

Найти ключ HKEY_CLASSES_ROOT\Directory\Shell
Создать подключ: opennew в котором изменить значение (По умолчанию) на: "Открыть в новом окне"
Под этим ключом создать еще подключ command (По умолчанию) = explorer %1
Использование средней кнопки мыши Logitech в качестве двойного щелчка
Подключ HKEY_LOCAL_MACHINE\SoftWare\Logitech и там найти параметр DoubleClick заменить 000 на 001

Новые звуковые события
Например создает звуки на запуск и закрытие WinWord
HKEY_CURRENT_USER\AppEvents\Shemes\Apps добавить подключ WinWord и к нему подключи Open и Close.
Теперь в настройках звуков видны новые события

Путь в реестре для деинсталяции программ:
HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Uninstall

Работа с реестром в Delphi

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

RegCreateKey (Key: HKey; SubKey: PChar; var Result: HKey): Longint;

Создать подраздел в реестре. Key указывает на "корневой" раздел реестра, в SubKey - имя раздела - строится по принципу пути к файлу в DOS (пример subkey1\subkey2\ ...). Если такой раздел уже существует, то он открывается (в любом случае при успешном вызове Result содержит Handle на раздел). Об успешности вызова судят по возвращаемому значению, если ERROR_SUCCESS, то успешно, если иное - ошибка.

RegOpenKey(Key: HKey; SubKey: PChar; var Result: HKey): Longint;

Открыть подраздел Key\SubKey и возвращает Handle на него в переменной Result. Если раздела с таким именем нет, то он не создается. Возврат - код ошибки или ERROR_SUCCESS, если успешно.

RegCloseKey(Key: HKey): Longint;

Закрывает раздел, на который ссылается Key. Возврат - код ошибки или ERROR_SUCCESS, если успешно.
RegDeleteKey(Key: HKey; SubKey: PChar): Longint;
Удалить подраздел Key\SubKey. Возврат - код ошибки или ERROR_SUCCESS, если нет ошибок.

RegEnumKey(Key: HKey; index: Longint; Buffer: PChar;cb: Longint): Longint;

Получить имена всех подразделов раздела Key, где Key - Handle на открытый или созданный раздел (см. RegCreateKey и RegOpenKey), Buffer - указатель на буфер, cb - размер буфера, index - индекс, должен быть равен 0 при первом вызове RegEnumKey. Типичное использование - в цикле While, где index увеличивается до тех пор, пока очередной вызов RegEnumKey не завершится ошибкой (см. пример).

RegQueryValue(Key: HKey; SubKey: PChar; Value: PChar; var cb: Longint): Longint;

Возвращает текстовую строку, связанную с ключом Key\SubKey.Value - буфер для строки; cb- размер, на входе - размер буфера, на выходе - длина возвращаемой строки. Возврат - код ошибки.

RegSetValue(Key: HKey; SubKey: PChar; ValType: Longint; Value: PChar; cb: Longint): Longint;

Задать новое значение ключу Key\SubKey, ValType - тип задаваемой переменной, Value - буфер для переменной, cb - размер буфера. В Windows 3.1 допустимо только Value=REG_SZ. Возврат - код ошибки или ERROR_SUCCESS, если нет ошибок.

Примеры:


{ Создаем список всех подразделов указанного раздела }  
procedure TForm1.Button1Click(Sender: TObject);  
var  
  MyKey: HKey;{ Handle для работы с разделом }  
  Buffer: array[0..1000] of char; { Буфер }  
  Err, { Код ошибки }  
  index: longint; { Индекс подраздела }  
begin  
  Err:=RegOpenKey(HKEY_CLASSES_ROOT,'DelphiUnit',MyKey); { Открыли раздел }  
  if Err<> ERROR_SUCCESS then   
  begin  
    MessageDlg('Нет такого раздела !!',mtError,[mbOk],0);  
    exit;  
    end;  
  index:=0;  
  {Определили имя первого подраздела }  
  Err:=RegEnumKey(MyKey,index,Buffer,Sizeof(Buffer));   
  while err=ERROR_SUCCESS do { Цикл, пока есть подразделы }  
  begin  
    memo1.lines.add(StrPas(Buffer)); { Добавим имя подраздела в список }  
    inc(index); { Увеличим номер подраздела }  
    Err:=RegEnumKey(MyKey,index,Buffer,Sizeof(Buffer)); { Запрос }  
  end;  
  RegCloseKey(MyKey); { Закрыли подраздел }  
end;  

Объект INIFILES - работа с INI файлами
Почему иногда лучше использовать INI-файлы, а не реестр?

INI-файлы можно просмотреть и отредактировать в обычном блокноте.
Если INI-файл хранить в папке с программой, то при переносе папки на другой компьютер настройки сохраняются. (Я еще не написал ни одной программы, которая бы не поместилась на одну дискету :)
Новичку в реестре можно запросто запутаться или (боже упаси), чего-нибудь не то изменить.
Поэтому для хранения параметров настройки программы удобно использовать стандартные INI файлы Windows. Работа с INI файлами ведется при помощи объекта TIniFiles модуля IniFiles. Краткое описание методов объекта TIniFiles дано ниже.
Constructor Create('d:\test.INI');
Создать экземпляр объекта и связать его с файлом. Если такого файла нет, то он создается, но только тогда, когда произведете в него запись информации.

WriteBool(const Section, Ident: string; Value: Boolean);

Присвоить элементу с именем Ident раздела Section значение типа boolean

WriteInteger(const Section, Ident: string; Value: Longint);

Присвоить элементу с именем Ident раздела Section значение типа Longint

WriteString(const Section, Ident, Value: string);

Присвоить элементу с именем Ident раздела Section значение типа String

ReadSection (const Section: string; Strings: TStrings);

Прочитать имена всех корректно описанных переменных раздела Section (некорректно описанные опускаются)

ReadSectionValues(const Section: string; Strings: TStrings);

Прочитать имена и значения всех корректно описанных переменных раздела Section. Формат :
имя_переменной = значение

EraseSection(const Section: string);

Удалить раздел Section со всем содержимым

ReadBool(const Section, Ident: string; Default: Boolean): Boolean;

Прочитать значение переменной типа Boolean раздела Section с именем Ident, и если его нет, то вместо него подставить значение Default.

ReadInteger(const Section, Ident: string; Default: Longint): Longint;

Прочитать значение переменной типа Longint раздела Section с именем Ident, и если его нет, то вместо него подставить значение Default.

ReadString(const Section, Ident, Default: string): string;

Прочитать значение переменной типа String раздела Section с именем Ident, и если его нет, то вместо него подставить значение Default.

Free;

Закрыть и освободить ресурс. Необходимо вызвать при завершении работы с INI файлом

Property Values[const Name: string]: string;

Доступ к существующему параметру по имени Name

Пример:


procedure TForm1.FormClose(Sender: TObject);  
var  
  IniFile:TIniFile;  
begin  
  IniFile := TIniFile.Create('d:\test.INI'); { Создали экземпляр объекта }  
  IniFile.WriteBool('Options', 'Sound', True); { Секция Options: Sound:=true }  
  IniFile.WriteInteger('Options', 'Level', 3); { Секция Options: Level:=3 }  
  IniFile.WriteString('Options' , 'Secret password', Pass);   
  { Секция Options: в Secret password записать значение переменной Pass }  
  IniFile.ReadSection('Options ', memo1.lines); { Читаем имена переменных}  
  IniFile.ReadSectionValues('Options ', memo2.lines); { Читаем имена и значения }  
  IniFile.Free; { Закрыли файл, уничтожили объект и освободили память }  
end;  

Добавлено: 07 Августа 2018 08:33:04 Добавил: Андрей Ковальчук

Работа со строковыми типами данных

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

Юникод-символы - многопользовательские сервера, мультиязыковые приложения

Для большинства целей подходит тип AnsiString (иногда называется Long String).

Стандартные функции обработки строк:

1) Функция Length(Str: String) - возвращает длину строки (количество символов). Пример:


var  
   Str: String; L: Integer;  
{ ... }  
Str:='Hello!';  
L:=Length(Str);  { L = 6 }  

2) Функция SetLength(Str: String; NewLength: Integer) позволяет изменить длину строки. Если строка содержала большее количество символов, чем задано в функции, то "лишние" символы обрезаются. Пример:


var Str: String;  
{ ... }  
Str:='Hello, world!';  
SetLength(Str, 5); { Str = "Hello" }  

3) Функция Pos(SubStr, Str: String) - возвращает позицию подстроки в строке. Нумерация символов начинается с единицы (1). В случае отсутствия подстроки в строке возращается 0. Пример:


var Str1, Str2: String; P: Integer;  
{ ... }  
Str1:='Hi! How do you do?';  
Str2:='do';  
P:=Pos(Str2, Str1);  { P = 9 }  

4) Функция Copy(Str: String; Start, Length: Integer) - возвращает часть строки Str, начиная с символа Start длиной Length. Ограничений на Length нет - если оно превышает количество символов от Start до конца строки, то строка будет скопирована до конца. Пример:


var Str1, Str2: String;  
{ ... }  
Str1:='This is a test for Copy() function.';  
Str2:=Copy(Str1, 11, 4); { Str2 = "test" }  

5) Процедура Delete(Str: String; Start, Length: Integer) - удаляет из строки Str символы, начиная с позиции Start длиной Length. Пример:


var Str1: String;  
{ ... }  
Str1:='Hello, world!';  
Delete(Str1, 6, 7); { Str1 = "Hello!" }  

6) Процедура Insert(SubStr: String; Str: String; Pos: Integer) - вставляет в строку Str подстроку SubStr в позицию Pos. Пример:


var Str: String;  
{ ... }  
Str:='Hello, world!';  
Insert('my ',Str, 8); { Str1 = "Hello, my world!" }  

7) Функции UpperCase(Str: String) и LowerCase(Str: String) преобразуют строку соответственно в верхний и нижний регистры:


var Str1, Str2, Str3: String;  
{ ... }  
Str1:='hELLo';  
Str2:=UpperCase(Str1); { Str2 = "HELLO" }  
Str3:=LowerCase(Str1); { Str3 = "hello" }  

Строки можно сравнивать друг с другом стандартным способом:


var Str1, Str2, Str3: String; B1, B2: Boolean;  
{ ... }  
Str1:='123';  
Str2:='456';  
Str3:='123';  
B1:=(Str1 = Str2); { B1 = False }  
B2:=(Str1 = Str3); { B2 = True }  

Если строки полностью идентичны, логическое выражение станет равным True.

Дополнительные функции обработки строк:

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

1) PosEx(SubStr, Str: String; Offset: Integer) - функция аналогична функции Pos(), но позволяет задать отступ от начала строки для поиска. Если значение Offset задано (оно не является обязательным), то поиск начинается с символа Offset в строке. Если Offset больше длины строки Str, то функция возратит 0. Также 0 возвращается, если подстрока не найдена в строке. Пример:


uses StrUtils;  
{ ... }  
var Str1, Str2: String; P1, P2: Integer;  
{ ... }  
Str1:='Hello! How do you do?';  
Str2:='do';  
P1:=PosEx(Str2, Str1, 1); { P1 = 12 }  
P2:=PosEx(Str2, Str1, 15); { P2 = 19 }  

2) Функция AnsiReplaceStr(Str, FromText, ToText: String) - производит замену выражения FromText на выражение ToText в строке Str. Поиск осуществляется с учётом регистра символов. Следует учитывать, что функция НЕ изменяет самой строки Str, а только возвращает строку с произведёнными заменами. Пример:


uses StrUtils;  
{ ... }  
var Str1, Str2, Str3, Str4: String;  
{ ... }  
Str1:='ABCabcAaBbCc';  
Str2:='abc';  
Str3:='123';  
Str4:=AnsiReplaceStr(Str1, Str2, Str3); { Str4 = "ABC123AaBbCc" }  

3) Функция AnsiReplaceText(Str, FromText, ToText: String) - выполняет то же самое действие, что и AnsiReplaceStr(), но с одним исключением - замена производится без учёта регистра. Пример:


uses StrUtils;  
{ ... }  
var Str1, Str2, Str3, Str4: String;  
{ ... }  
Str1:='ABCabcAaBbCc';  
Str2:='abc';  
Str3:='123';  
Str4:=AnsiReplaceText(Str1, Str2, Str3); { Str4 = "123123AaBbCc" }  

4) Функция DupeString(Str: String; Count: Integer) - возвращает строку, образовавшуюся из строки Str её копированием Count раз. Пример:


uses StrUtils;  
{ ... }  
var Str1, Str2: String;  
{ ... }  
Str1:='123';  
Str2:=DupeString(Str1, 5); { Str2 = "123123123123123" }  

5) Функции ReverseString(Str: String) и AnsiReverseString(Str: AnsiString) - инвертируют строку, т.е. располагают её символы в обратном порядке. Пример:


uses StrUtils;  
{ ... }  
var Str1: String;  
{ ... }  
Str1:='0123456789';  
Str1:=ReverseString(Str1); { Str1 = "9876543210" }  

6) Функция IfThen(Value: Boolean; ATrue, AFalse: String) - возвращает строку ATrue, если Value = True и строку AFalse если Value = False. Параметр AFalse является необязательным - в случае его отсутствия возвращается пустая строка.


uses StrUtils;  
{ ... }  
var Str1, Str2: String;  
{ ... }  
Str1:=IfThen(True, 'Yes'); { Str1 = "Yes" }  
Str2:=IfThen(False, 'Yes', 'No'); { Str2 = "No" }  

Мы рассмотрели функции, позволяющие выполнять со строками практически любые манипуляции. Как правило, вместо строки с указанным типом данных, можно использовать и другой тип - всё воспринимается одинаково. Но иногда требуются преобразования. Например, многие методы компонент требуют параметр типа PChar, получить который можно из обычного типа String функцией PChar(Str: String):


uses ShellAPI;  
{ ... }  
var FileName: String;  
{ ... }  
FileName:='C:\WINDOWS\notepad.exe';  
ShellExecute(0, 'open', PChar(FileName), '', '', SW_SHOWNORMAL);  

Тип Char представляет собой один-единственный символ. Работать с ним можно как и со строковым типом. Для работы с символами также существует несколько функций:

Chr(Code: Byte) - возвращает символ с указанным кодом (по стандарту ASCII):


var A: Char;  
{ ... }  
A:=Chr(69); { A = "E" }  

Ord(X: Ordinal) - возвращает код указанного символа, т.е. выполняет противоположное действие функции Chr():


var X: Integer;  
{ ... }  
X:=Ord('F'); { X = 70 }  

Из строки можно получить любой её символ - следует рассматривать строку как массив. Например:


var Str, S: String; P: Char;  
{ ... }  
Str:='Hello!';  
S:=Str[2]; { S = "e" }  
P:=Str[5]; { P = "o" }  

В этой статье описаны основные приёмы работы со строковыми типами данных. Как правило, этих данных достаточно для написания любого алгоритма.

Добавлено: 07 Августа 2018 08:30:48 Добавил: Андрей Ковальчук

Файловые операции средствами ShellAPI

В данной статье мы подробно рассмотрим применение функции SHFileOperation. function SHFileOperation(const lpFileOp: TSHFileOpStruct): Integer; stdcall; Данная функция позволяет производить копирование, перемещение, переименование и удаление (в том числе и в Recycle Bin) объектов файловой системы. Функция возвращает 0, если операция выполнена успешно, и ненулевое значение в противном :-) случае.

Функция имеет единственный аргумент - структуру типа TSHFileOpStruct, в которой и передаются все необходимые данные. Эта структура выглядит следующим образом:


_SHFILEOPSTRUCTA = packed record  
    Wnd: HWND;  
    wFunc: UINT;  
    pFrom: PAnsiChar;  
    pTo: PAnsiChar;  
    fFlags: FILEOP_FLAGS;  
    fAnyOperationsAborted: BOOL;  
    hNameMappings: Pointer;  
    lpszProgressTitle: PAnsiChar; { используется только при установленном флаге FOF_SIMPLEPROGRESS }  
  end;   

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

FO_COPY Копирует файлы, указанные в pFrom в папку, указанную в pTo.
FO_DELETE Удаляет файлы, указанные pFrom (pTo игнорируется).
FO_MOVE Перемещает файлы, указанные в pFrom в папку, указанную в pTo.
FO_RENAME Переименовывает файлы, указанные в pFrom.

pFrom
Указатель на буфер, содержащий пути к одному или нескольким файлам.
Если файлов несколько, между путями ставится нулевой байт.
Список должен заканчиваться двумя нулевыми байтами.

pTo
Аналогично pFrom, но содержит путь к директории - адресату,
в которую производится копирование или перемещение файлов.
Также может содержать несколько путей.
При этом нужно установить флаг FOF_MULTIDESTFILES.

fFlags
Управляющие флаги.
FOF_ALLOWUNDO Если возможно, сохраняет информацию для возможности UnDo.
FOF_CONFIRMMOUSE Не реализовано.
FOF_FILESONLY Если в поле pFrom установлено *.*, то операция
будет производиться только с файлами.
FOF_MULTIDESTFILES Указывает, что для каждого исходного
файла в поле pFrom указана своя директория - адресат.
FOF_NOCONFIRMATION Отвечает "yes to all" на все запросы в ходе опеации.
FOF_NOCONFIRMMKDIR Не подтверждает создание нового каталога,
если операция требует, чтобы он был создан.
FOF_RENAMEONCOLLISION В случае, если уже существует файл
с данным именем, создается файл с именем "Copy #N of..."
FOF_SILENT Не показывать диалог с индикатором прогресса.
FOF_SIMPLEPROGRESS Показывать диалог с индикатором прогресса,
но не показывать имен файлов.
FOF_WANTMAPPINGHANDLE Вносит hNameMappings элемент.
Дескриптор должен быть освобожден функцией SHFreeNameMappings.
fAnyOperationsAborted
Принимает значение TRUE если пользователь прервал любую файловую
операцию до ее завершения и FALSE в ином случае.

hNameMappings
Дескриптор объекта отображения имени файла, который содержит
массив структур SHNAMEMAPPING. Каждая структура содержит
старые и новые имена пути для каждого файла, который перемещался,
скопирован, или переименован. Этот элемент используется только,
если установлен флаг FOF_WANTMAPPINGHANDLE.

lpszProgressTitle
Указатель на строку, используемую как заголовок для диалогового окна прогресса.
Этот элемент используется только, если установлен флаг FOF_SIMPLEPROGRESS.

Примечание.
Если pFrom или pTo не указаны, берутся файлы из текущей директории.
Текущую директорию можно установить с помощью функции SetCurrentDirectory
и получить функцией GetCurrentDirectory.

А теперь - примеры.

Разумеется, вам нужно вставить в секцию uses модуль ShellAPI, в котором определена
функция SHFileOperation.

Рассмотрим самое простое - удаление файлов.


procedure TForm1.Button1Click(Sender: TObject);  
var  
  SHFileOpStruct : TSHFileOpStruct;  
  From : array [0..255] of Char;  
begin  
  SetCurrentDirectory( PChar( 'C:\' ) );  
  From := 'Test1.tst' + #0 + 'Test2.tst' + #0 + #0;  
  with SHFileOpStruct do  
    begin  
      Wnd := Handle;  
      wFunc := FO_DELETE;  
      pFrom := @From;  
      pTo := nil;  
      fFlags := 0;  
      fAnyOperationsAborted := False;  
      hNameMappings := nil;  
      lpszProgressTitle := nil;  
    end;  
  SHFileOperation( SHFileOpStruct );  
end;   

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

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


type TBuffer = array of Char;  
  
  
procedure CreateBuffer( Names : array of string; var P : TBuffer );  
var I, J, L : Integer;  
begin  
  for I := Low( Names ) to High( Names ) do  
    begin  
      L := Length( P );  
      SetLength( P, L + Length( Names[ I ] ) + 1 );  
      for J := 0 to Length( Names[ I ] ) - 1 do  
        P[ L + J ] := Names[ I, J + 1 ];  
      P[ L + J ] := #0;  
    end;  
  SetLength( P, Length( P ) + 1 );  
  P[ Length( P ) ] := #0;  
end;  

Выглядит ужасно, но работает. Можно написать красивее, просто лень.

И, наконец, функция, удаляющая файлы, переданные ей в списке Names.
Параметр ToRecycle определяет, будут ли файлы перемещены в корзину
или удалены. Функция возвращает 0, если операция выполнена успешно,
и ненулевое значение, если руки у кого-то растут не из того места, и этот
кто-то всунул функции имена несуществующих файлов.


function DeleteFiles( Handle : HWnd; Names : array of string; ToRecycle : Boolean ) : Integer;  
var  
  SHFileOpStruct : TSHFileOpStruct;  
  Src : TBuffer;  
begin  
  CreateBuffer( Names, Src );  
  with SHFileOpStruct do  
    begin  
      Wnd := Handle;  
      wFunc := FO_DELETE;  
      pFrom := Pointer( Src );  
      pTo := nil;  
      fFlags := 0;  
      if ToRecycle then fFlags := FOF_ALLOWUNDO;  
      fAnyOperationsAborted := False;  
      hNameMappings := nil;  
      lpszProgressTitle := nil;  
    end;  
  Result := SHFileOperation( SHFileOpStruct );  
  Src := nil;  
end;   

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

Проверяем :


procedure TForm1.Button1Click(Sender: TObject);  
begin  
  DeleteFiles( Handle, [ 'C:\Test1', 'C:\Test2' ], True );  
end;   

Вроде все работает.

Кстати, обнаружился забавный глюк - вызовем процедуру DeleteFiles таким образом:


procedure TForm1.Button1Click(Sender: TObject);  
begin  
  SetCurrentDirectory( PChar( 'C:\' ) );  
  DeleteFiles( Handle, [ 'Test1', 'Test2' ], True );  
end;   

Файлы 'Test1' и 'Test2' удаляются совсем, без помещения в корзину,
несмотря на установленный флаг FOF_ALLOWUNDO.
Мораль: при использовании функции
SHFileOperation используйте полные пути всегда, когда это возможно.
Ну, с удалением файлов разобрались.

Теперь очередь за копированием и перемещением.

Следующая функция перемещает файлы указанные в списке Src в директорию Dest.
Параметр Move определяет, будут ли файлы перемещаться или копироваться.
Параметр AutoRename указывает, переименовывать ли файлы в случае конфликта имен.


function CopyFiles( Handle : Hwnd; Src : array of string; Dest : string;  
Move : Boolean; AutoRename : Boolean ) : Integer;  
var  
  SHFileOpStruct : TSHFileOpStruct;  
  SrcBuf : TBuffer;  
begin  
  CreateBuffer( Src, SrcBuf );  
  with SHFileOpStruct do  
    begin  
      Wnd := Handle;  
      wFunc := FO_COPY;  
      if Move then wFunc := FO_MOVE;  
      pFrom := Pointer( SrcBuf );  
      pTo := PChar( Dest );  
      fFlags := 0;  
      if AutoRename then fFlags := FOF_RENAMEONCOLLISION;  
      fAnyOperationsAborted := False;  
      hNameMappings := nil;  
      lpszProgressTitle := nil;  
    end;  
  Result := SHFileOperation( SHFileOpStruct );  
  SrcBuf := nil;  
end;   

Ну, проверим.


procedure TForm1.Button1Click(Sender: TObject);  
begin  
  CopyFiles( Handle, [ 'C:\Test1', 'C:\Test2' ], 'C:\Temp', True, True );  
end;   

Все в порядке (а кудa ж оно денется).

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

Осталась последняя о


function RenameFiles( Handle : HWnd; Src : string; New : string; AutoRename : Boolean ) : Integer;  
var SHFileOpStruct : TSHFileOpStruct;  
begin  
  with SHFileOpStruct do  
    begin  
      Wnd := Handle;  
      wFunc := FO_RENAME;  
      pFrom := PChar( Src );  
      pTo := PChar( New );  
      fFlags := 0;  
      if AutoRename then fFlags := FOF_RENAMEONCOLLISION;  
      fAnyOperationsAborted := False;  
      hNameMappings := nil;  
      lpszProgressTitle := nil;  
    end;  
  Result := SHFileOperation( SHFileOpStruct );  
end;   

И проверка ...


procedure TForm1.Button1Click(Sender: TObject);  
begin  
  RenameFiles( Handle, 'C:\Test1' , 'C:\Test3' , False );  
end;  

Добавлено: 07 Августа 2018 08:28:56 Добавил: Андрей Ковальчук

Принцип создания плагинов в Delphi

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


function PluginType : PChar;  

функция, определяющая назначение плугина.


function PluginName : PChar;  

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


function PluginExec(AObject: ТТип): boolean;  

главный обработчик, выполняет определённые действия и возвращает TRUE;

и ещё, я делал res файл с небольшим битмапом и компилировал его вместе с плугином, который отображался в меню соответствующего плугина. Откомпилировать res фaйл можно так:

создайте файл с расширением *.rc
напишите в нём : bitmap RCDATA LOADONCALL 1.bmp где bitmap - это идентификатор ресурса RCDATA LOADONCALL - тип и параметр 1.bmp - имя локального файла для кампиляций
откомпилируйте этот файл программой brcc32.exe, лежащей в папке ...\Delphi5\BIN\ .
Загрузка плагина

Перейдём к теоретической части.

Раз плугин это dll значит её можно подгрузить следующими способами:

Прищипыванием её к программе!

function PluginType : PChar; external 'myplg.dll';  
// в таком случае dll должна обязательно лежать возле exe и мы не можем передать  
// туда конкретное имя! не делать же все плугины одного имени! это нам не подходит.  
// Программа просто не загрузится без этого файла! Выдаст сообщение об ошибке.  
// Этот способ может подойти для поддержки обновления вашей программы!  

Динамический
это означает, что мы грузим её так, как нам надо! Вот пример:


var  
  // объявляем процедурный тип функции из плугина  
  PluginType: function: PChar;  
  //объявляем переменную типа хендл в которую мы занесём хендл плугина  
  PlugHandle: THandle;  
  
procedure Button1Click(Sender: TObject);  
begin  
  //грузим плугин  
  PlugHandle := LoadLibrary('MYplg.DLL');  
  //Получилось или нет?  
  if PlugHandle <> 0 then  
  begin  
    // ищем функцию в dll  
    @PluginType := GetProcAddress(plugHandle,'Plugintype');  
    if @PluginType <> nil then  
      //вызываем функцию  
      ShowMessage(PluginType);  
  end;  
  //освобождаем библиотеку  
  FreeLibrary(LibHandle);  
end;  

Вот этот способ больше подходит для построения плугинов!

Функции:


//как вы поняли загружает dll и возвращает её хендл  
function LoadLibrary(lpLibFileName : Pchar):THandle;  
// пытается найти обработчик в переданной ей хендле dll,  
// при успешном выполнении возвращает указатель обработчика.  
function GetProcAddress(Module: THandle; ProcName: PChar): TFarProc   
//освобождает память, занитую dll  
function FreeLibrary(LibModule: THandle);  

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

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

Исходный текст модуля программы:


unit Unit1;  
  
interface  
  
uses  
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,  
  Dialogs, Menus, Grids, DBGrids;  
  
type  
  TForm1 = class(TForm)  
    MainMenu1: TMainMenu;  
    //меню, которое будет содержать ссылки на плугины  
    N1231: TMenuItem;  
    procedure FormCreate(Sender: TObject);  
  private  
    { Private declarations }  
    //лист, в котором мы будем держать имена файлов плугинов  
    PlugList : TStringList;  
    //Процедура загрузки плугина  
    procedure LoadPlug(fileName : string);  
    //Процедура инициализации и выполнения плугина  
    procedure PlugClick(sender : TObject);  
  public  
    { Public declarations }  
end;  
  
var  
Form1: TForm1;  
  
implementation  
{$R *.DFM}  

Процедура загрузки плугина. Здесь мы загружаем, вносим имя dll в список и создаём для него пункт меню; загружаем из dll картинку для пункта меню


procedure TForm1.LoadPlug(fileName: string);  
var  
  //Объявление функции, которая будет возвращать имя плугина  
  PlugName : function : PChar;  
  //Новый пункт меню  
  item : TMenuItem;  
  //Хендл dll  
  handle : THandle;  
  //Объект, с помощью которого мы загрузим картинку из dll  
  res :TResourceStream;  
begin  
  item := TMenuItem.create(mainMenu1); //Создаём новый пункт меню  
  handle := LoadLibrary(Pchar(FileName)); //загружаем dll  
  if handle <> 0 then //Если удачно, то идём дальше...  
  begin  
    @PlugName := GetProcAddress(handle,'PluginName'); //грузим процедуру  
    if @PlugName <> nil then  
      item.caption := PlugName  
      //Если всё прошло, идём дальше...  
    else  
    begin  
      ShowMessage('dll not identifi '); //Иначе, выдаём сообщение об ошибке  
      Exit; //Обрываем процедуру  
    end;  
    PlugList.Add(FileName); //Добавляем название dll  
    res:= TResourceStream.Create(handle,'bitmap',rt_rcdata); //Загружаем ресурс из dll  
    res.saveToFile('temp.bmp'); res.free; //Сохраняем в файл  
    item.Bitmap.LoadFromFile('Temp.bmp'); //Загружаем в пункт меню  
    FreeLibrary(handle); //Уничтожаем dll  
    item.onClick:=PlugClick; //Даём ссылку на обработчик  
    Mainmenu1.items[0].add(item); //Добавляем пункт меню  
  end;  
end;  

Процедура выполнения плугина. Здесь мы загружаем, узнаём тип и выполняем


procedure TForm1.PlugClick(sender: TObject);  
var  
  //Объявление функции, которая будет выполнять плугин  
  PlugExec : function(AObject : TObject): boolean;  
  //Объявление функции, которая будет возвращать тип плугина  
  PlugType : function: PChar;  
  //Имя dll  
  FileName : string;  
  //Хендл dll  
  handle : Thandle;  
begin  
  with (sender as TmenuItem) do  
    filename:= plugList.Strings[MenuIndex];  
  //Получаем имя dll  
  handle := LoadLibrary(Pchar(FileName)); //Загружаем dll  
  //Если всё в порядке, то идём дальше  
  if handle <> 0 then  
  begin  
    //Загружаем функции  
    @plugExec := GetProcAddress(handle,'PluginExec');  
    @plugType := GetProcAddress(handle,'PluginType');  
    //А теперь, в зависимости от типа, передаём нужный ей параметр...  
    if PlugType = 'FORM' then  
      PlugExec(Form1)  
    else  
    //Если плугин для формы, то передаём форму  
    if PlugType = 'CANVAS' then  
      PlugExec(Canvas)  
    else  
    //Если плугин для канвы, то передаём канву  
    if PlugType = 'MENU' then  
      PlugExec(MainMenu1)  
    else  
    //Если плугин для меню, то передаём меню  
    if PlugType = 'BRUSH' then  
      PlugExec(Canvas.brush)  
    else  
    //Если плугин для заливки, то передаём заливку  
    if PlugType = 'NIL' then  
      PlugExec(nil);  
    //Если плугину ни чего не нужно, то ни чего не передаём  
  end;  
  FreeLibrary(handle); //Уничтожаем dll  
end;  
  
procedure TForm1.FormCreate(Sender: TObject);  
var  
  SearchRec : TSearchRec; //Запись для поиска  
begin  
  plugList:=TStringList.create; //Создаём запись для имён dll'ок  
  //ищем первый файл  
  if FindFirst('*.dll',faAnyFile, SearchRec) = 0 then  
  begin  
    LoadPlug(SearchRec.name); //Загружаем первый найденный файл  
    while FindNext(SearchRec) = 0 do  
      LoadPlug(SearchRec.name);  
    //Загружаем последующий  
    FindClose(SearchRec); //Закрываем поиск  
  end;  
  //Левые параметры  
  canvas.Font.pitch := fpFixed;  
  canvas.Font.Size := 20;  
  canvas.Font.Style:= [fsBold];  
end;  
  
end.  

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


library plug;  
  
uses  
  SysUtils, graphics, Classes, windows;  
  
{$R bmp.RES}  
  
function PluginType : Pchar;  
begin  
  //Мы указали реакцию на этот тип  
  Plugintype := 'CANVAS';  
end;  
  
function PluginName:Pchar;  
begin  
  //Вот оно, название плугина. Эта строчка будет в менюшке  
  PluginName := 'Canvas painter';  
end;  

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


function PluginExec(Canvas:TCanvas):Boolean;  
var  
  X : integer;  
  I : integer;  
  Z : byte;  
  S : string;  
  color : integer;  
  proz : integer;  
begin  
  color := 10;  
  proz :=0;  
  S:= 'hello всем это из плугина ля -- ля';  
  for Z:=0 to 200 do  
  begin  
    proz:=proz+2;  
    X:= 0;  
    for I:=1 to length(S) do  
    begin  
      X:=X + 20;  
      Canvas.TextOut(X,50,S[i]);  
      color := color+X*2+Random(Color);  
      canvas.Font.Color := color+X*2;  
      canvas.font.color := 10;  
      canvas.TextOut(10,100,'execute of '+inttostr(proz div 4) + '%');  
      canvas.Font.Color := color+X*2;  
      sleep(2);  
    end;  
  end;  
  PluginExec:=True;  
end;  
  
exports  
  PluginType, PluginName, PluginExec;  
  
end.  

Пару советов:

Не оставляйте у своих плугинов расширение *.dll, это не катит. А вот сделайте, например *.plu . Просто в исходном тексте плугина напишите {$E plu} Ну и в исходном тексте программы ищите не Dll, а уже plu.
Когда вы сдаёте программу, напишите к ней уже готовых несколько плугинов, что бы юзеру было интересно искать новые.
Сделайте поддержку обновления через интернет. То есть программа заходит на ваш сервер, узнаёт, есть ли новые плугины или нет, если есть - то она их загружает. Этим вы увеличите спрос своей программы и конечно трафик своего сайта!

Добавлено: 07 Августа 2018 08:27:23 Добавил: Андрей Ковальчук

Сохранение и загрузка данных в объекты на примере коллекций

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

Необходимо реализовать хранение информации о некоей службе рассылки и ее подписчиках. Будем хранить данные о почтовом сервере и список подписчиков. Каждая запись о подписчике хранит его личные данные и адрес, а также список тем(или каталогов), на которые он подписан. Как большие поклонники Гради Буча (Grady Booch), а также будучи заинтересованы в удобной организации кода, мы организуем информацию о подписчиках в виде объектов. В Delphi для данной задачи идеально подходит класс TCollection, реализующий всю необходимую функциональность для работы со списками типизированных объектов. Для этого мы наследуемся от TCollection, называя новый класс TMailList - список рассылки, а также создаем наследника от TCollectionItem - TMailClient - адресат рассылки. Последний будет содержать все необходимые данные о подписчике, а также реализовывать необходимые функции для работы с ним.

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


type   
  TMailClient = class(TCollectionItem)   
  private   
    FName: string;   
    FAddress: string;   
    FEnabled: boolean;   
    FFolders: TStringList;   
  public   
    Files: TStringList;  // список файлов к рассылке. заполняется в run-time. Сохранению не подлежит    
    constructor Create(Collection: TCollection); override;   
    destructor Destroy; override;   
    procedure PickFiles;   
  published   
    property Name: string read FName write FName;  // имя адресата   
    property Address: string read FAddress write FAddress; // почтовый адрес   
    property Enabled: boolean read FEnabled write FEnabled default true;  
    property Folders: TStringList read FFolders write FFolders; // список папок (тем) подписки   
  end;  

Класс содержит сведения о имени клиента, его адресе, его статусе(Enabled), а также список каталогов, на которые он подписан. Процедура PickFiles составляет список файлов к отправке и сохраняет его в свойстве Files
Класс TMailList, хранящий объекты класса TMailClient, приведен ниже.


TMailList = class(TCollection)   
public   
  function GetMailClient(Index: Integer): TMailClient;   
  procedure SetMailClient(Index: Integer; Value: TMailClient);   
public   
  function  Add: TMailClient;   
  property Items[Index: Integer]: TMailClient read GetMailClient  write SetMailClient; default;   
end;  

Теперь поместим класс TMailList в класс TMailer. В него можно будет потом включить данные о параметрах доступа к почтовому серверу для отправки почты. Он мог бы и отправлять почту, но в данном примере это не использовано, дабы не перегружать код.

То есть в нашем примере он выполняет только роль носителя данных о подписчиках и их подписке. Класс TComponent, от которого он наследуется можно сохранить в файл, в то время как TCollection самостоятельно не сохранится. Только если она агрегирована в TComponent. Именно это у нас и реализовано.


TMailer = class(TComponent)   
private   
  FMailList: TMailList;   
public   
  constructor Create(AOwner: TComponent); override;   
  destructor Destroy; override;   
published   
  property MailList: TMailList read FMailList write FMailList; // коллекция - список рассылки.   
  // здесь можно поместить, к примеру, данные о соединении с почтовым сервером    
end;  

Повторюсь. В данном случае мы наследуемся от класса TComponent, для того, чтобы была возможности записи данных объекта в файл. Свойство MailList содержит уже объект класса TMailList.
Реализация всех приведенных классов приведена ниже.


constructor TMailClient.Create(Collection: TCollection);   
begin   
  inherited;   
  Folders := TStringList.Create;   
  Files := TStringList.Create;   
  FEnabled := true;   
end;   
   
destructor TMailClient.Destroy;   
begin   
  Folders.Free;   
  Files.Free;   
  inherited;   
end;   
  
// здесь во всех каталогах Folders ищем файлы для рассылки и помещаем их в Files.   
procedure TMailClient.PickFiles;   
var i: integer;  
begin   
    for i:=0 to Folders.Count-1 do CreateFileList(Files, Folders[i]);   
end;   
  
// Стандартный код при наследовании от класса коллекции: переопределяем тип    
function TMailList.GetMailClient(Index: Integer): TMailClient;   
begin   
  Result := TMailClient(inherited Items[Index]);   
end;   
   
// Стандартный код при наследовании от класса коллекции    
procedure TMailList.SetMailClient(Index: Integer; Value: TMailClient);   
begin   
  Items[Index].Assign(Value);   
end;   
  
 // Стандартный код при наследовании от класса коллекции: переопределяем тип    
function TMailList.Add: TMailClient;   
begin   
  Result := TMailClient(inherited Add);   
end;   
  
// создаем коллекцию адресатов рассылки TMailList   
constructor TMailer.Create(AOwner: TComponent);   
begin   
  inherited Create(AOwner);   
  MailList := TMailList.Create(TMailClient);   
end;   
   
destructor TMailer.Destroy;   
begin   
  MailList.Free;   
  inherited;   
end;   
//---------------------  

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


procedure CreateFileList(sl: TStringList; const FilePath: string);   
var   
  sr: TSearchRec;   
  procedure ProcessFile;   
  begin   
    if (sr.Name = '.')or(sr.Name = '..') then exit;   
    if sr.Attr <> faDirectory then   
      sl.Add(FilePath + '\' + sr.Name);   
    if sr.Attr = faDirectory then   
    begin   
      CreateFileList(sl, FilePath + '\' + sr.Name);   
    end;   
  end;   
begin   
  if not DirectoryExists(FilePath) then exit;   
  if FindFirst(FilePath + '\' + '*.*', faAnyFile , sr) = 0 then ProcessFile;   
  while FindNext(sr) = 0 do ProcessFile;   
  FindClose(sr);   
end;  

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


var   
  Mailer: TMailer; // это наш объект для хранения данных о почтовой рассылки  
    
// Процедура загрузки данных в объект. Может быть процедурой OnCreate() главной формы.  
procedure TfMain.FormCreate(Sender: TObject);  
var   
  sDataFile, sTmp: string;   
  i, j: integer;  
begin   
   
  Mailer := TMailer.Create(self);   
   
 // будем считать, что данные были сохранены в файл users.dat в каталоге программы  
  sDataFile := ExtractFilePath(ParamStr(0)) + 'users.dat';   
   
  //...загрузка данных из файла   
  if FileExists(sDataFile) then  
    LoadComponentFromTextFile(Mailer, sDataFile);   
   { здесь данные из файла загружены }  
  
  //...перебор подписчиков   
  for i:=0 to Mailer.MailList.Count-1 do   
  begin   
  
    sTmp := Mailer.MailList[i].Name;  //...обращение к имени   
    sTmp := Mailer.MailList[i].Address; //...обращение к адресу   
    //... sTmp - фиктивная переменная. Поменяйте ее на свои.    
      
    Mailer.MailList[i].PickFiles;  //... поиск файлов для отправки очередному подписчику.   
   
   //...перебор найденных файлов к отправке   
    for j:=0 to Mailer.MailList[i].Files.Count-1 do   
    begin   
      sTmp := Mailer.MailList[i].Files[j];   
    end;  
      
  end;  
end;  

После загрузки данных мы можем работать с данными в нашей коллекции подписчиков. Добавлять и удалять их ( Mailer.MailList.Add; Mailer.MailList.Delete(Index); ). При завершении работы программы необходимо сохранить уже новые данные в тот же файл.


// Процедура сохранения данных из объекта в файл. Может быть процедурой OnDestroy() главной формы.  
procedure TfMain.OnDestroy;  
begin  
  //...сохранение данных в файл users.dat  
  SaveComponentToTextFile(Mailer, ExtractFilePath(ParamStr(0)) + 'users.dat');   
end;  

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

Далее приведен код функций для сохранения/чтения компонента.


//...процедура загружает(инициализирует) компонент из текстового файла с ресурсом   
procedure LoadComponentFromTextFile(Component: TComponent; const FileName: string);   
var   
  ms: TMemoryStream;   
  fs: TFileStream;   
begin   
  fs := TFileStream.Create(FileName, fmOpenRead);   
  ms := TMemoryStream.Create;   
  try   
    ObjectTextToBinary(fs, ms);   
    ms.position := 0;   
    ms.ReadComponent(Component);   
  finally   
    ms.Free;   
    fs.free;   
  end;   
end;   
   
//...процедура сохраняет компонент в текстовый файл   
procedure SaveComponentToTextFile(Component: TComponent; const FileName: string);   
var   
  ms: TMemoryStream;   
  fs: TFileStream;   
begin   
  fs := TFileStream.Create(FileName, fmCreate or fmOpenWrite);   
  ms := TMemoryStream.Create;   
  try   
    ms.WriteComponent(Component);   
    ms.position := 0;   
    ObjectBinaryToText(ms, fs);   
  finally   
    ms.Free;   
    fs.free;   
  end;   
end;  

Добавлено: 07 Августа 2018 08:25:36 Добавил: Андрей Ковальчук

Чудеса TStringList. Почему надо использовать объекты TStringList везде

Object Pascal в сочетании с ассемблером в современной его форме Delphi 5/6/7 предоставляет неограниченные возможности для полета мысли программиста, и этой статьей мы откроем серию, в которой последовательно будем это демонстрировать.

Начнем мы с простого, крайне полезного, но мало известного, по крайней мере, для начинающих, стандартного класса Дельфи TStringList. Сейчас мы посмотрим, как красиво решаются типичные задачи при помощи этого класса.

Для начала в двух словах, какие такие замечательные свойства есть у объектов данного класса. TStringList - это класс, предназначенный для хранения списка строк и списка объектов с текстовым представлением (прямо как в 1С - это СписокЗначений). Кроме того, этот список может быть отсортирован по алфавиту или при помощи сравнительной функции, написанной программистом. Кроме того, этот список может быть интерпретирован как список значений (Name=string). Кроме того, этот список может быть сохранен в файл или поток, преобразован в непрерывную строку или строку, разделенную запятыми. Физически получаемая строка или поток представляет собой обычный текст, в котором строки разделены символами CR/LF (стандартный Windows Text File), или запятыми (CommaText, Excel). Ну и само собой, есть возможность загружать список строк из файла, потока, строки и строки, разделенной запятыми. Следует особо отметить замечательное свойство упаковки в строку, разделенную запятыми: при обратной распаковке строки всегда восстанавливаются в их исходном виде. Это означает, что допустима многократная вложенность строк, разделенных запятыми, дающая огромный выигрыш при упаковке/распаковке многомерных структурных данных в текстовый формат, что мы и продемонстрируем во второй задаче.

Итак, для начала рассмотрим список строк как список объектов с текстовым представлением, т. к. именно в данном ключе следует использовать список строк в реальных приложениях. Что из себя представляет объект с текстовым представлением? Это может быть, например, список товаров, имеющих помимо наименования еще и дополнительные параметры типа единицы измерения, количества в упаковке и цены. Итак, имеем предопределение типов:


type   
  TEdIzm=class  
    public  
      Name:string;  
      Weight:double;  
    end;  
  TTovar=class  
    public  
      Name:string; //наименование  
      EdIzm:TEdIzm; //ед. изм.  
      CountUp:integer; //кол-во в упаковке  
      PriceOut:currency; //цена продажная  
    end;  
var  
  TovarList:TStringList;  

Набор объектов TTovar - это классический справочник однородных товаров, например, хлебобулочных изделий. Поле Weight в классе TEdIzm требуется для перевода одних единиц в другие. Вернемся к нашим булкам. Допустим, поставщик "Карякинский Хлебозавод" предоставил нам текстовый файл, в котором находится информация о его новой продукции и отпускных ценах в формате текстового файла:


*Начало файла*  
Товар1=[наименование товара]  
ЕдИзм1=[наименование ед.изм.]  
Вес1=[вес единицы измерения]  
КолУп1=[количество в упаковке]  
Цена1=[цена поставщика]  
--  
Товар2=[наименование товара]  
ЕдИзм2=[наименование ед.изм.]  
Вес2=[вес единицы измерения]  
КолУп2=[количество в упаковке]  
Цена2=[цена поставщика]  
--  
*Конец файла*  

Всего в файле содержится, например, 2000 наименований. Наша задача - загрузить данные этого файла в список строк ListBox1:TListBox (лежащий на форме) в отсортированном виде так, чтобы была возможность по двойному щелчку просмотреть параметры каждого товара (для этого к каждой строке будет прикреплен объект типа TTovar).

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


function LoadTovary(filename:string):TStrings;   
var str:TStringList;  
    tovar:TTovar;  
    edizm:TEdIzm;  
    I:integer;  
begin  
  //загружаем файл с данными  
  str:=TStringList.Create;  
  str.LoadFromFile(filename);  
  //посчитаем, сколько будет товаров  
  result:=TStringList.Create; // TStrings являедся предком TStringList, поэтому данное присвоение корректно  
  result.Capacity:=str.count div 6;  
  for I:=0 to result.capacity do  
  begin  
    tovar:=TTovar.Create;  
    edizm:=TEdIzm.Create;  
    tovar.Name:=str.Values['Товар'+inttostr(i)];  
    edizm.Name:=str.Values['ЕдИзм'+inttostr(i)];  
    edizm.Weight:=strtofloat(str.Values['Вес'+inttostr(i)]);  
    tovar.CountUp:=strtoint(str.Values['КолУп'+inttostr(i)]);  
    tovar.PriceOut:=strtoint(str.Values['Цена'+inttostr(i)]);  
    tovar.EdIzm:=edizm;  
    result.AddObject(tovar.Name,tovar);  
  end;  
  result.Sort;  
end;  
  
...  
ListBox1.Items:=LoadTovary('fromhlzd.txt');  
...  

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


function StringListComparePrice(List: TStringList; Index1, Index2: Integer): Integer;  
begin  
  Result := TTovar(List.Objects[Index1]).PriceOut-TTovar(List.Objects[Index2]).PriceOut;  
end;  
  
...  
result.CustomSort(StringListComparePrice);  
...  

Ну и в конце концов, продемонстрируем реакцию на двойное нажатие на получившемся списке товаров. По двойному щелчку мы покажем отпускную цену товара:


procedure TForm1.ListBox1DblClick(Sender: TObject);  
begin  
  showmessage('Цена поставщика '+  
floattostr(TTovar(TListBox(sender).Items.Objects[TListBox(sender).ItemIndex]).PriceOut)  
  );  
end;  

Кстати, не забываем освобождать системные ресурсы объектов перед удалением строк методами ListBox1.Items.Delete()/Clear() или str.Delete()/Clear() (str:TStringList), если это требуется, а так же при закрытии приложения:


procedure TForm1.FormDestroy(Sender: TObject);  
var i:integer;  
begin  
  for i:=0 to ListBox1.Items.Count-1 do  
  with TTovar(ListBox1.Items.Objects[i]) do  
  begin  
    EdIzm.Free;  
    Free;  
  end;  
end;  

Теперь немного усложним задачу. Пусть у нас есть не один файл, а десять - от десяти разных поставщиков. Нам надо в общей сложности загрузить 20000 наименований и затем отсортировать их. Если мы будем именно так и делать, то сортировка такого большого списка объектов займет значительное время, это и составляет задачу. Однако такая задача решается очень просто - достаточно после создания объекта TStringList сразу присвоить его свойству Sorted значение true. После этого вставка новых строк будет осуществляться с помощью быстрого алгоритма, однако потеряется возможность сортировать по параметрам прикрепленных объектов, т.к. в случае Sorted=true сортировка производится автоматически только по текстовому представлению (см. исходники TStringList). При Sorted=true, обработка дубликатов определяется свойством Duplicates. Можно пропускать, разрешать и запрещать дубликаты объектов с одинаковым текстовым представлением. При этом, если дубликаты пропускаются или запрещены, необходимо следить за освобождением ресурсов объектов, не добавленных в список. По умолчанию, свойство Duplicates равно dupIgnore, что означает, что дубликаты пропускаются, поэтому по умолчанию надо следить за освобождением ресурсов.

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

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

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

Упаковщик


procedure runobrab;  
var strtov,stred:TStringList;  
begin  
  strtov:=TStringList.Create;  
  with TTovar(ListBox1.Items.Objects[ListBox1.ItemIndex]) do  
  begin  
    stred:=TStringList.Create;  
    stred.Values['ЕдИзм']:=EdIzm.Name;  
    stred.Values['Вес']:=floattostr(EdIzm.Weight);  
    strtov.Values['Товар']:=Name;  
    strtov.Values['ЕдИзм']:=stred.CommaText;  
    strtov.values['КолУп']:=inttostr(CountUp);  
    strtov.values['Цена']:=floattostr(PriceOut);  
  end;  
  winexec('obrab.exe '+strtov.CommaText,SW_SHOWNORMAL);  
  strtov.Free;  
  stred.Free;  
end;  

Распаковщик


function getparam:TTovar;  
var strtov,stred:TStringList;  
begin  
  strtov:=TStringList.Create;  
  stred:=TStringList.Create;  
  strtov.CommaText:=paramstr(1);  
  stred.CommaText:=strtov.Values['ЕдИзм'];  
  result:=TTovar.Create;  
  with result do  
  begin  
    EdIzm:=TEdIzm.Create;  
    EdIzm.Name:=stred.Values['ЕдИзм'];  
    EdIzm.Weight:=strtofloat(stred.Values['Вес']);  
    Name:=strtov.Values['Товар'];  
    CountUp:=strtoint(strtov.values['КолУп']);  
    PriceOut:=strtofloat(strtov.values['Цена']);  
  end;  
  stred.Free;  
  strtov.Free;  
end;  

Продолжение следует…

Автор: Цованян Роман

Добавлено: 07 Августа 2018 08:24:30 Добавил: Андрей Ковальчук