|
Раздел "Кодинг". Содержание:
Статьи:
Объектно-ориентированный подход к программированию
Перетаскивание объектов с помощью Delphi
Поиск файлов на Delphi
Работа с буфером обмена в Delphi
Как писать Win32API приложения на Delphi
Раздел "Кодинг". Статьи:
 | Объектно-ориентированный подход к программированию |
|
Объект можно сравнивать с черным ящиком. Фокусник кладет в него платочек, говорит заветное заклинание, и вытаскивает кролика. Так же и мы. Мы можем инициализировать объект, или он сам инициализируется значениями по умолчанию, вызвать нужный метод объекта, и получить результат. Нас мало интересует то, что в нем конкретно происходит, если объект уже достаточно хорошо отлажен. Основная идея объектно-ориентированного подхода заключается в наличие интерфейса, который служит для полиморфного обращения с объектом и его потомками. За счет наличия интерфейса легко достигается повторное использование кода. Многие программисты, переходящие от процедурного программирования к объектно-ориентированному программированию справедливо замечают, что они могут сделать все то же самое и без использования объектов. Объектно-ориентированное программирование - это всего лишь соглашение о правилах построения программ. Вся мощь объектной ориентации раскрывается в крупных проектах, или при написании большого количества однотипных программ, например программ, работающих с базами данных. За счет повторного использования кода достигается простота в работе программиста (накопление опыта), сокращается размер программы (методы объектов одного типа или методы, наследуемые от предков потомками существуют в единственном экземпляре), самодокументируемость, а значит и больше простоты при отладке (объекты описываются в определенном месте программы отдельно от реализации), простота сопровождения программы (не меняя интерфейс объекта, Вы можете изменить реализацию методов) и т.д. Но, это только в идеале. На самом деле достаточно просто извратить постулаты объектной ориентированности. Все зависит от правильности и лаконичности дерева наследования Вашей библиотеки объектов. Нам повезло, Мы можем использовать в своей работе последнее достижение в области объектно-ориентированного программирования - продукт компании Borland-Inprise Delphi.
При дальнейшем чтении текста, если Вам будет сразу что-то непонятно, то продолжайте читать дальше. В такой сложной теме трудно последовательно изложить все по порядку, т.к. многие вопросы переплетаются с более сложными и наоборот. По ходу чтения текста, Вы составите полное представление о теме.
Объектно-ориентированное программирование.
Объект в Delphi представляет из себя специальную структуру, которая описывает поля, свойства и методы объекта - class. Предком для всех объектов служит class Tobject. Давайте рассмотрим простой объект.
Type
TmyObject = class(TObject)
Private // закрытая часть
AmyField: Integer; // Свойство
Protected // Защищенная часть
Procedure SetMyField(Val: Integer); // Процедура записи свойства класса
Public // Открытая часть
Constructor Create; // Конструктор
Destructor Destroy; override; // Деструктор
Property MyField: Integer read AmyField write SetMyField; // Свойство класса
End;
Имена классов принято начинать с буквы T, но это просто соглашение, а не правило. Вы можете назвать Ваш объект как хотите. Однако, буква Т в начале имени класса - это правило хорошего тона. Далее, указывается, что этот класс является потомком от Tobject. Если Вы запишите TmyClass = class, то все равно ваш класс будет потомком от Tobject. Далее, идет закрытая часть интерфейса класса. Здесь объявляются свойства и методы класса, которые будут доступными только из методов этого же класса, и будут недоступными для других классовых методов и из других модулей программы. При наследовании класса, потомок тоже не будет иметь доступа к закрытой части интерфейса. Иногда, такое поведение класса неудобно. Например, при большом количестве обращений к списку данных одного класса из другого через открытую часть интерфейса, при каждом обращении, возможно, будут проверяться допустимые границы индекса списка. Это правильно, но может значительно замедлить работу программы, поэтому было бы неплохо иметь возможность для ограниченного числа классов или функций разрешить доступ к закрытой части, чтобы они могли обращаться к свойствам класса, объявленным в закрытой части. Возможно, Вы писали на С++ и знаете, что там такие классы и функции называются друзьями. В Delphi эта возможность реализуется через объявление дружественных классов и функций в одном модуле программы, т.е. все друзья должны быть объявлены в одном модуле. Далее, идет защищенная часть. Она отличается от закрытой тем, что из потомка класса, Вы можете иметь доступ к этой части. Далее, идет открытая часть интерфейса. Здесь Вы можете объявить свойства и методы класса, которые будут доступны для других классов, процедур и функций. Есть еще одна часть интерфейса - published (опубликованная). Эта часть имеет место у потомков от Tcomponent. Delphi использует эту часть интерфейса в инспекторе объектов. При доступе к классу во время выполнения программы, эта часть ничем не отличается от public. Здесь имеет смысл объявлять свойства и события класса. Все свойства и события будут доступны из инспектора объектов, и Вы сможете редактировать их во время разработки. Чтобы работать с классом, Вы должны объявить переменную объектного типа этого класса, затем инициализировать ее вызовом конструктора.
Type
TmyClass = class(TObject) // Объявление класса
…
end;
…
Var
AmyClass: TmyClass; // Объявление переменной класса
begin
AmyClass:=TmyClass.Create; // Вызов конструктора, обратите внимание на то, что вызывается конструктор
// TmyClass.Create, а не AmyClass.Create
Try
…
finally
AmyClass.Free; // Уничтожение класса
End;
end;
Классы в Delphi могут создаваться только в динамической памяти, поэтому все переменные объектного типа - это указатели на экземпляр класса в динамической памяти. Типичное имя конструктора - Create, типичное имя деструктора - Destroy. Придерживайтесь этого правила, если нет особой необходимости в обратном.
Инициализация и разрушение объектов
Для объявления конструктора используется зарезервированное слово constructor, после которого идет имя конструктора и параметры, если необходимо. Конструктор возвращает указатель на экземпляр класса. У конструктора Tobject имя Create, поэтому у всех потомков этого класса есть конструктор Create, хотя, у некоторых классов есть и другие конструкторы с другими именами, например у обработчиков исключений. В теле конструктора Вы можете вызвать конструктор предка для инициализации закрытой части предка значениями по умолчанию, например:
unit MyUnit;
interface
Type
TmyClass = class(TComponent)
…
public
constructor Create(AOwner: TComponent); override; // перегружаем конструктор предка
…
end;
implementation
Constructor TmyClass.Create(AOwner: TComponent);
Begin
Inherited Create(Aowner); // Вызов конструктора предка
… // Дальнейшая инициализация объекта
End;
Если имя конструктора предка совпадает с именем потомка, то можно сократить запись при вызове конструктора предка в конструкторе потомка:
Constructor TmyClass.Create(AOwner: TComponent);
Begin
Inherited (Aowner); // Вызов конструктора предка
… // Дальнейшая инициализация объекта
End;
Для уничтожение объекта служит деструктор. Деструктор объявляется с помощью зарезервированного слова destructor, после которого идет имя деструктора. Деструктор ничего не возвращает и не имеет параметров. Я советую Вам вместо прямого вызова деструктора использовать метод Free. Этот метод есть у всех классов в Delphi, т.к. наследуется от Tobject. Этот метод сначала проверяет неравенство указателя на класс nil, а затем только вызывает Destroy. Это более безопасный способ уничтожить объект.
unit MyUnit;
interface
Type
TmyClass = class(TComponent)
…
public
constructor Create(AOwner: TComponent); override; // перегружаем конструктор предка
destructor Destroy; override // Перегружаем деструктор предка
…
end;
implementation
…
destructor TmyClass.Destroy;
Begin
… // Уничтожение объекта
Inherited Destroy; // Вызов деструктора предка, для уничтожение закрытых полей предка
End;
Инкапсуляция
Суть объектно-ориентированного подхода к программированию заключается в трех принципах: инкапсуляции, наследовании и полиморфизме. Для более надежной работы программы, Вы не должны напрямую обращаться к полям объекта. Вместо этого, Вы должны обращаться через специальные методы, которые могут проверить, например, на допустимость значения в заданном интервале или одновременно выполнить дополнительную работу по изменению состояния объекта, в зависимости от изменения значения поля - это и есть инкапсуляция. Такая возможность реализуется в классах через объявление свойств объекта. Свойство позволяет ограничить права на изменение значения поля и организует доступ к полю через методы. Вот пример описания класса со свойством:
Type
TmyClass = class(TObject)
Private
AmyField: Integer; // Объявление поля целого типа
Protected
Procedure SetMyField(Val: Integer); virtual; // Объявление процедуры для записи значения свойства
Public
Property MyField: Integer read AmyField write SetMyField; // объявление свойства
End;
Здесь мы видим, что свойство MyField является целым типом. Оно доступно для чтения и записи, т.к. объявлены методы read и write. Процедура, организующая запись значения объявлена в секции protected для того, чтобы в случае необходимости, Вы могли перегрузить ее в потомке. На самом деле значение свойства хранится в поле AmyField класса. Если Вы не объявите метода write, то свойство станет доступным только по чтению, аналогично и с методом read. К слову говоря, поля для хранения значения свойства может и не быть, главное, чтобы были объявлены методы доступа к нему. Для примера, класс, реализующий интерфейс доступа к свойствам дисплея, мог бы иметь свойства ширина и высота в пикселях. Вам не обязательно хранить эти значения, т.к. можно вызвать стандартную функцию Windows и узнать ширину и высоту экрана, тем более, что в процессе работы эти значения могут меняться при переключении в другой режим. Методы записи и чтения свойства подчиняются жестким правилам. Так для чтения свойства, Вам необходимо объявить функцию без формальных параметров, возвращающую значение того же типа, что и свойство. Для записи значения, Вам необходимо объявить процедуру с одним параметром того же типа, что и свойство. Если в качестве метода для записи или чтения свойства Вы указываете имя поля для хранения значения свойства, то это аналогично прямому доступу к этому полю. При компиляции такого способа обращения к свойству, код будет оптимизирован, поэтому это не повлечет никаких дополнительных расходов ресурсов компьютера. Обычно, такой метод обращения к полю применяют для чтения. Методы доступа к полям класса могут выполнять дополнительную работу при переустановке значения поля класса. Так, при установке свойства Ttable.Active в true производится чтение данных из таблицы базы данных на жестком диске, несколько раз меняется состояние объекта, посылаются события связанным объектам, вызываются обработчики делегированных событий, которые пишет программист, и только потом переустанавливается значение свойства в true. Для присвоения свойству значения по умолчанию, используется ключевое слово default, например:
property Visible: boolean read Avisible write SetVisible default true;
Это значит, что при запуске программы, компилятор установить это свойство в true. Однако я настоятельно рекомендую Вам устанавливать значение свойств по умолчанию в конструкторе класса.
Вы можете иметь индексированные свойства. Вот пример реализации такого класса.
Type
TmyClass = class(TObject);
private
AmyList: Tlist; // Контейнер указателей
Protected
Function GetMyList(Index: Integer): Pointer; // Функция доступа по чтению
Procedure SetMyList(Index: Integer; Val: Pointer); // Процедура доступа по записи
Public
…
Property MyList[Index: Integer]: Pointer read GetMyList write SetMyList; // Объявление индексированного свойства
End;
Здесь мы видим, что свойство MyList индексированно - это элемент списка указателей. В квадратных скобках Вам нужно указать список индексов и их типов. В общем случае, индексом может быть даже строка символов. Далее идет тип свойства и методы записи и чтения. Функция чтения должна иметь список формальных параметров со всеми индексами свойства и возвращать значение того же типа, что и свойство. Процедура записи должна иметь список формальных параметров со всеми индексами свойства и параметр для передачи устанавливаемого значения того же типа что и свойство. Большое значение имеет последовательность указания индексов и обязательность передачи значения свойства в процедуре записи последним в списке формальных параметров. Если индексированное свойство является основным и обращение именно к нему производится чаще остальных, то можно объявить его как default, тогда не нужно указывать имя свойство для доступа к нему, например:
Type
TmyClass = class(TObject);
private
AmyList: Tlist; // Контейнер указателей
Protected
Function GetMyList(Index: Integer): Pointer; // Функция доступа по чтению
Procedure SetMyList(Index: Integer; Val: Pointer); // Процедура доступа по записи
Public
…
Property MyList[Index: Integer]: Pointer read GetMyList write SetMyList; default; // Объявление индексированного свойства по умолчанию
End;
…
Var
MyClass: TmyClass;
Begin
MyClass:= TmyClass.Create;
…
MyClass [3]:=AnyObject; // Аналогично MyClass.MyList[3] ]:=AnyObject;
…
End;
Значение инкапсуляции в объектно-ориентированном программировании трудно переоценить. Чего стоит хотя бы то, что в Delphi к 100% полей классов доступ организован через свойства.
Наследование
Если Вы хотите изменить или дополнить поведение уже существующего класса, то нет никакой необходимости переписывать класс заново. Вам стоит воспользоваться наследованием. Вы должны объявить, что новый класс является потомком уже существующего и добавить в новый класс свойства и методы, которые Вам необходимы или перекрыть существующие методы и свойства:
Type
TmyFirstClass = class(TObject)
Private
Protected
Public
Constructor Create(Val: Integer); virtual;
end;
TmySecondClass = class(TMyFirstClass)
Private
AmyField: string; // Добавили новое поле
Protected
Procedure SetMyField(Val: string); // Добавили процедуру
Public
Constructor Create(Val: Integer); override; // Перегрузили конструктор
Property MyField: string read AmyField write SetMyField; // Добавили свойство
End;
Есть несколько правил области видимости объекта, которые помогут Вам разобраться со способами доступа к объектам и наследования объектов:
- Поля, свойства и методы секции public не имеют ограничений на видимость.
- Поля, свойства и методы секции private, доступны только в методах класса и в функциях, объявленных в том же модуле, где и класс.
- Поля, свойства и методы секции protected тоже доступны только из методов класса и функций, объявленных в модуле, но они доступны в классах, являющихся потомками, в том числе и объявленных в других модулях.
При описании потомков, Вы можете изменять область видимости методов и свойств. Можно расширять область видимости, но не сужать. Т.е. если есть свойство в секции private, вы можете сделать его public, но не наоборот. Вот пример расширения области видимости:
Type
TmyClass=class(TObject)
Private
AmyField: Integer;
protected
property MyField: Integer read AmyField;
…
End;
TmySunClass = class(TMyClass)
…
Public
Property MyField; // Только упомянули его в другой секции и он поменял область видимости.
End;
Унаследованные поля и методы потомка имеются и в предке. При совпадении имен предка и потомка говорят о перекрытии потомком полей или методов предка. По способу вызова методы класса можно разделить на статические, виртуальные, динамические, перегружаемые и абстрактные. Абстрактные методы не имеют реализации и должны быть обязательно перекрыты в потомках. Абстрактными могут быть только виртуальные и динамические методы, например:
Type
TmyClass=class(TObject)
…
protected
procedure MyMethod(Val: Integer); virtual; abstract;
…
End;
Статические методы и поля в объектах-потомках перекрываются одинаково: Вы можете без ограничений перекрывать старые имена и при этом изменять тип методов, т.к. при перекрытии будет создано новое поле или метод с тем же именем. Перекрытый метод предка доступен в потомке через зарезервированное слово inherited.
Type
TmyClass=class(TObject)
…
protected
procedure MyMethod;
…
End;
TmySunClass=class(TmyClass)
…
protected
procedure MyMethod(Val: Integer);
…
End;
…
procedure TmySunClass.MyMethod(Val: Integer);
begin
inherited MyMethod; // Метод предка без параметров, а метод потомка уже с параметром, т.е. мы поменяли тип процедуры.
…
end;
По умолчанию все методы - статические, поэтому их адреса известны уже на стадии компиляции, они будут вызываться при выполнении программы самым быстрым способом. Виртуальные и динамические методы описываются с помощью специальных директив virtual или dynamic. Эти методы могут быть перекрыты в потомке одноименными методами, имеющими тот же тип.
И наконец, хочу сказать, что Delphi не поддерживает множественного наследования. Для достижения того же самого, Вам придется использовать простое наследование от одного из нужных классов, а второй класс добавить как поле, и организовать доступ к свойствам второго класса через свойства наследника. Так можно соединить несколько классов. Другой способ - это создать класс-контейнер, в котором в качестве полей будут нужные включенные классы, а интерфейс доступа к ним организовать через свойства контейнера.
Полиморфизм
Указателю на экземпляр объектного типа может быть присвоен адрес любого экземпляра любого из дочерних типов. При обращении к свойствам и методам через этот указатель будет доступен именно экземпляр, адрес которого был присвоен, а не предок. Это и есть полиморфизм. Т.е. Вы можете иметь доступ к потомку через указатель объектного типа предка. Рассмотрим пример:
Type
TMyClass = class(TObject)
…
public
procedure GetData: string; virtual; abstract;
…
end;
TmySun1Class = class(TMyClass)
Protected
AmyField: string;
…
public
procedure GetData: string; override;
…
end;
TmySun2Class = class(TMyClass)
Protected
AmyField: Integer;
…
public
procedure GetData: string; override;
…
end;
…
implementation
procedure TmySun1Class.GetData: string;
begin
Result:= AmyField;
end;
procedure TmySun2Class.GetData: string;
begin
Result:=IntToStr(AmyField);
end;
…
Var
MyClass: TmyClass;
Class1: TmySun1Class;
Class2: TmySun2Class;
Begin
Class1:=TmySun1Class.Create;
Class2:=TmySun2Class.Create;
…
MyClass:= Class1;
Label1.Caption:= MyClass.GetData;
MyClass:= Class2;
Label2.Caption:= MyClass.GetData;
end;
Если посмотреть на этот код внимательно, то можно понять, что у компилятора нет возможности определить метод какого именно класса нужно вызывать. Поэтому, для определения адреса метода используются специальные таблицы, где хранятся адреса на виртуальные и динамические методы: VMT - таблица виртуальных методов и DMT - таблица динамических методов. Когда компилятор встречает указатель на виртуальный метод, то он ищет его адрес в VMT, где хранятся все адреса виртуальных методов класса унаследованных и перекрытых, поэтому такая таблица занимает много памяти, хотя и способ вызова метода работает сравнительно быстро. Динамические методы вызываются медленнее, но занимают меньше памяти, т.к. в них хранятся адреса динамических методов только данного класса и их индексы. При вызове динамических методов проводится поиск по этой таблице, если метод не найден, то поиск продолжается в DMT предков вплоть до Tobject, где вызывается стандартный обработчик вызова динамических методов. Зачем же нам все это надо? При проектировании иерархии классов предметной области, нужно статическими делать методы, которые не меняют своего поведения в потомках, т.е. при более детальном рассмотрении явления. Динамические и виртуальные методы могут меняться при переходе от общего к частному. Вспомните класс Tfield, который является общим предком для всех классов-полей таблицы. Потомки этого класса реализуют доступ к столбикам таблицы разных типов от целого числа до BLOB массива, однако, Вы можете иметь удобный доступ к этим потомкам через указатель типа Tfield и работать с ними одинаково.
Перегрузка методов, процедур и функций
Перегрузка объявляется с помощью зарезервированного слова overload. Рассмотрим пример:
Type
TmyDateClass=class(TObject)
private
Adate: TdateTime;
…
Public
Procedure SetDate(Val: TDateTime); overload; // Объявляем возможность перегрузки
…
end;
TmySecondDateClass=class(TmyDateClass)
private
Adate: TdateTime;
…
Public
Procedure SetDate(Val: string); overload; // Объявляем возможность перегрузки
…
End;
…
implementation
Procedure TmyDateClass .SetDate(Val: TDateTime);
Begin
Adate:=Val;
End;
Procedure TmySecondDateClass.SetDate(Val: string);
Begin
Adate:=StrToDate(Val);
End;
Во время работы программы, вы можете использовать во втором классе оба метода SetDate. Если Вы передадите в качестве параметра строку, то будет вызван метод второго класса, а если TdateTime, то метод предка. Можно перегружать и виртуальные методы, только вместо override нужно использовать reintroduce, например:
Type
TmyDateClass=class(TObject)
private
Adate: TdateTime;
…
Public
Procedure SetDate(Val: TDateTime); overload; virtual; // Объявляем возможность перегрузки
…
end;
TmySecondDateClass=class(TmyDateClass)
private
Adate: TdateTime;
…
Public
Procedure SetDate(Val: string); reintroduce; overload; // Объявляем возможность перегрузки
…
End;
Вы можете использовать перегрузку и для процедур и функций необязательно при наследовании, и даже процедур и функций не классового типа, например:
Function Myfunction(Val: string): string; overload;
Begin
Result:=Val+' Ok!'
End;
Function Myfunction(Val: Extended): extended; overload;
Begin
Result:=Val/2;
End;
Или
TmyDateClass=class(Tobject)
private
Adate: TdateTime;
…
Public
Procedure SetDate(Val: TDateTime); overload; // Объявляем возможность перегрузки
Procedure SetDate(Val: string); overload; // Объявляем возможность перегрузки
…
End;
Параметры по умолчанию
Если Вам нужно создать функцию, которая в качестве параметров почти всегда получает одно и то же значение, но все-таки иногда оно может меняться, то Вам нужно объявить параметры по умолчанию в качестве формальных параметров, например:
Procedure MyProcedure(Val1: Extended; Val2: Integer = 2);
Begin
…
End;
Тогда Вы сможете вызывать ее такими способами:
MyProcedure(42.33); // аналогично MyProcedure(42.33, 2);
MyProcedure(15.6, 8);
Правило гласит, что все параметры по умолчанию должны быть сосредоточены в конце списка формальных параметров процедуры или функции. Опускать параметры по умолчанию, можно только начиная с конца списка, поэтому нужно упорядочивать параметры по умолчанию по степени важности.
Делегирование
Событие - это свойства процедурного типа, предназначенные для создания пользовательской реакции на внешние воздействия. События в Delphi реализуется за счет создания поля процедурного типа и объявления соответствующего свойства класса, например:
Type
TmyEvent = procedure(Sender: Tcomponent); of object; // определение процедурного типа
TmyClass=class(Tcomponent)
Private
FmyEvent: TmyEvent;
Protected
Procedure DoMyEvent;
published
property OnMyEvent: TmyEvent read FmyEvent write FmyEvent;
end;
Допустим, Вы определили функцию function MyProcedure(Sender: Tcomponent) для обработки события с помощью инспектора объектов или написали вручную и налету присвоили объекту: MyClass.OnMyEvent:=MyProcedure. При наступлении определенных условий Ваш класс может вызвать процедуру DoMyEvent, где будет вызвана Ваша процедура MyProcedure так:
Procedure TmyClass .DoMyEvent;
Begin
If Assigned(FmyEvent) then FmyEvent(Self);
End;
Мы видим, что был проверен указатель на пользовательскую процедуру обработки события, и если он действителен, то вызывается пользовательская процедура - это и есть делегирование. Обратите внимание, что я разместил свойство OnMyEvent в секции published для того, чтобы программист мог воспользоваться инспектором объектов для написания процедуры обработки события.
 | Перетаскивание объектов с помощью Delphi |
|
Свойства DragMode, DragCursor, методы BeginDrag, OnDragOver, OnDragDrop, OnEndDrag, OnStartDrag, параметр Accept
Процесс перетаскивания с помощью мыши информации из одного объекта в другой широко используется в Widows. Можно перемещать файлы между папками, перемещать сами папки и др.
Все свойства, методы и события, связанные с процессом перетаскивания, определены в классе TControl, являющегося прародителем всех визуальных компонентов Delphi. Поэтому они являются общими для всех компонентов.
Начало перетаскивания определяется свойством DragMode, которое может устанавливаться в процессе проектирования или программно равным dmManual или dmAutomatic. Значение dmAutomatic (автоматическое) определяет автоматическое начало процесса перетаскивания при нажатии пользователем кнопки мыши над компонентом. Однако в этом случае событие OnMouseDown, связанное с нажатием пользователем кнопки мыши, для этого компонента вообще не наступает.
Значение dmManual (ручное) говорит о том, что начало процесса перетаскивания должен определить программист. Для этого он должен в соответствующий момент вызвать метод BeginDrag. Например, он может поместить вызов этой функции в обработчик события OnMouseDown, наступающего в момент нажатия кнопки мыши. В этом обработчике он может проверить предварительно какие-то условия (режим работы приложения, нажатие тех или иных кнопок мыши и вспомогательнх клавиш) и при выполнении этих условий вызвать BeginDrag.
Пусть, например, процесс перетаскавания должен начаться, если пользователь нажал левую кнопку мыши и клавишу Alt над списком ListBox1. Тогда свойство DragMode этого компонента надо установить в dmManual, а его обработчик события OnMouseDown может иметь вид:
procedure TForm1.ListBox1MouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
if (Button = mbLeft) and (ssAlt in Shift)
then (Sender as TControl).BeginDrag(false);
end;
Параметр Button обработчика события OnMouseDown показывает, какая кнопка мыши была нажата, а параметр Shift является множеством, содержащим обозначения нажатых в этот момент кнопок мыши и вспомогательных клавиш клавиатуры. Если нажата левая кнопка мыши и клавиша Alt, то вызывается метод BeginDrag данного компонента.
В функцию BeginDrag передано значение False. Это означает, чтопроцесс перетаскивания начнется не сразу, а только после того, как пользователь сдвинет мышь с нажатой при этом кнопкой. Это позволяет отличить простой щелчок мыши от начала перетаскивания. Если же передать в BeginDrag значение True, то перетаскивание начнется немедленно.
Когда начался процесс перетаскивания, обычный вид курсора изменяется. Пока он перемещается над формой или компонентами, которые не могут принять информацию, он обычно имеет вид crNoDrop. Если же он перемещается над компонентом, готовым принять информацию из перетаскиваемого объекта, то приобретает вид, определяемый свойством перетаскиваемого объекта DragCursor. По умолчанию это свойство равно crDrag, что соответствует изображению. Вид курсора определяется свойством DragCursor перетаскиваемого объекта, а не того объекта, над которым перемещается курсор.
В процессе перетаскивания компоненты, над которыми перемещаетяс курсор, могут информировать о готовности принять информацию от перетаскиваемого объекта. Для этого в компоненте должен быть предусмотрен обработчик события OnDragOver, наступающего при перемещении над данным компонентом курсора, перетаскивающего некоторый объект. В этом обработчике надо проверить, может ли данный компонент принять информацию перетаскиваемого объекта, и, если не может, задать значение False передаваемому в обработчик параметру Accept. По умолчанию этот параметр равен True, что означает возможность принять перетаскиваемый объект. Обработчик для списка может иметь, например, следующий вид:
procedure TForm1.ListBox1DragOver(Sender, Source: TObject; X, Y: Integer;State:
TDragState; var Accept: Boolean);
begin
if(Sender as TControl<> Source) then Accept := Source is TListBox else
Accept := False;
end;
В нем сначала проверяется, не является ли данный компонент (Sender) и перетаскиваемый объект (Source) одним и тем же объектом. Это сделано, чтобы избежать перетаскивания информации внутри одного и того же списка.
Значение параметра Accept, задаваемое в обрапботчике события onDragOver, определяет вид курсора, перемещающегося при перетаскивании над данным компонентом. Этот вид показывает пользователю, может ли данный компонент принять перетаскиваемую информацию. Если в компоненте не описан обработчик события onDragOver, то считается, что данный компонент, не может принять информацию перетаскиваемого объекта.
Процедура приема информации от перетаскиваемого объекта записывается в обработчике события OnDragDrop принимающего компонента. Это событие наступает, если после перетаскивания пользователь отпустил клавишу мыши над данным компонентом. В обработчик этого события передаются параметры Source (объект-источник) и X и Y координаты курсора. Если продолжить пример перетаскивания информации из одного списка в другой, то обработчик события OnDragDrop может иметь вид:
procedure TForm1.ListBox1DragDrop(Sender, Source: TObject; X, Y: Integer);
begin
(Sender as TListBox).Items.Add((Source as TListBox).Items[(Source as TListBox).ItemIndex]);
end;
В этом обработчике сторка, выделенная в списке-источнике (Source as TListBox).Items[(Source as TListBox).ItemIndex], добавляется в список-приемник методом (Sender as TListBox).Items.Add.. Используется операция AS, позволяющая расссматривать параметры Sender и Source как указатели на объект класса TListBox. Это делается потому, что эти параметры объявлен в заголовке процедуры как указатели на объекты класса TObject. Но в классе TObject нет свойств Items и ItemIndex, которые нам требуются. Эти свойства определены в классе TListBox, являющемся наследником TObject. Поэтому с параметрами Sender и Source в данном случае надо оперировать как с указателями на объекты TListBox, что и выполняет операция as.
В данном случае можно было бы не использовать параметр Sender, заменив (Sender as TListBox) просто на ListBox1. Но запись оператора в общем виде с помощью параметра Sender позволяет воспользоваться таким обработчиком и для других компонентов ListBox, если они имеются в приложении.
После завершения или прерывания перетаскивания наступает событие OnEndDrag, в обработчике которого можно предусмотреть какое-то дополнительное действие. Имеется также связанное с перетаскиванием событие OnStartDrag, которое позволяет произвести какие-то операции в начале перетаскивания, когда иным способом этот момент нельзя зафиксировать.
Таким образом, если в приложении имеется несколько списков и нужно задать возможность копирования строк каждого из этих списков в любой другой, то это потребует двух операций:
Написать для одного списка обработчик события onDragOver. Для всех остальных списков сослаться в событиях onDragOver на этот же обработчик (выделив в форме все оставшиеся списки).
Написать аналогичным образом для всех списков обработчик события OnDragDrop.
Если начинать перетаскивание нужно только при выполнении какого-то дополнительног условия, например, при нажатии клавиши Alt, то потребуется задать для всех списков значение свойства DragMode, равное dmManual и написать обработчик события OnMouseDown.
В приведенном ниже примере:
многострочный редактор Memo1 готов принять информацию от TLabel и TListBox;
списки ListBox готовы принять информацию от всех других списков и при этом, если информация передается не от списка ListBox2, то их списка-источника она удаляется (переносится). Из списка ListBox2 информация только копируется.
для метки Label1 c заголовком dmAutomatic определено событие OnEndDrag так, что выводится сообщение об успешном или неуспешном завершении процесса переноса, которое выводится в случае включения флажка с заголовком «Сообщения»:
procedure TForm1.Label1EndDrag(Sender, Target: TObject; X, Y: Integer);
begin
If CheckBox1.Checked then
If Target = Nil Then
ShowMessage('Перенесение объекта '+ (Sender as TControl).Name + ' прервано else
ShowMessage((Sender as TControl).Name + ' перенесена в ' + (Target as TControl).Name);
end;
для списков ListBox3 и ListBox4
перетаскивание возможно только при нажатой клавише Alt, но для ListBox4 перетаскивание
начинается сразу (значение функции BeginDrag(true)), а для ListBox3 (и метки
Label1) момент перетаскивания определяется началом движения мышки (значение
функции BeginDrag(false)).
unit Udrag1;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls;
type
TForm1 = class(TForm)
Memo1: TMemo;
Label1: TLabel; Label2: TLabel; Label3: TLabel;
ListBox1: TListBox; ListBox2: TListBox;
ListBox3: TListBox; ListBox4: TListBox;
Label4: TLabel; CheckBox1: TCheckBox;
procedure Memo1DragOver(Sender, Source: TObject; X, Y: Integer; State: TDragState;
var Accept: Boolean);
procedure Memo1DragDrop(Sender, Source: TObject; X, Y: Integer);
procedure ListBox1DragOver(Sender, Source: TObject; X, Y: Integer; State: TDragState;
var Accept: Boolean);
procedure ListBox1DragDrop(Sender, Source: TObject; X, Y: Integer);
procedure ListBox3MouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState;
X, Y: Integer);
procedure Label1EndDrag(Sender, Target: TObject; X, Y: Integer);
procedure ListBox4MouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState;
X, Y: Integer); private { Private declarations }
public { Public declarations }
end;
var
Form1: TForm1;
implementation {$R *.DFM}
procedure TForm1.Memo1DragOver(Sender, Source: TObject; X, Y: Integer;
State: TDragState; var Accept: Boolean);
begin
Accept := (Source is TLabel)or(Source is TListBox);
end;
procedure TForm1.Memo1DragDrop(Sender, Source: TObject; X, Y: Integer);
begin
if(Source.ClassName = 'TLabel')
then Memo1.Lines.Add((Source as TLabel).Caption)
else Memo1.Lines.Add((Source as TListBox).Items[(Source as TListBox).ItemIndex]);
;
end;
procedure TForm1.ListBox1DragOver(Sender, Source: TObject; X, Y: Integer;
State: TDragState; var Accept: Boolean);
begin
if(Sender <> Source) then Accept := Source is TListBox else Accept :=
False;
end;
procedure TForm1.ListBox1DragDrop(Sender, Source: TObject; X, Y: Integer);
begin
(Sender as TListBox).Items.Add((Source as TListBox).Items[(Source as TListBox).ItemIndex]);
if (Source as TListBox).Name <> 'ListBox2'
then (Source as TListBox).Items.Delete( (Source as TListBox).ItemIndex);
end;
procedure TForm1.ListBox3MouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
if (Button = mbLeft) and (ssAlt in Shift) then (Sender as TControl).BeginDrag(false);
end;
procedure TForm1.Label1EndDrag(Sender, Target: TObject; X, Y: Integer);
begin
If CheckBox1.Checked then
If Target = Nil Then
ShowMessage('Перенесение объекта'+ (Sender as TControl).Name + ' прервано')
else
ShowMessage((Sender as TControl).Name + ' перенесена в ' + (Target as TControl).Name);
end;
procedure TForm1.ListBox4MouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
if (Button = mbLeft) and (ssAlt in Shift) then (Sender as TListBox).BeginDrag(true);
end;
end.
 | Поиск файлов на Delphi |
|
В этой статье мы познакомимся с функциями
для поиска файлов на дисках Вашего (и не
только :) компьютера. Для тех кто собирается
писать вирусы или что-то подобное используя
приведенный алгоритм, то предупреждаю:
создание вредоносных программ карается по
УК РФ!!! Так что думайте сами, а тем кому
интересно как же найти файл на диске с
помощью Делфьи, то читайте дальше...
Для поиска файлов на диске в Delphi существует две функции, первая из них - это FindFirst, ниже приведено ее описание:
function FindFirst(const Path: string; Attr: Integer; var F: TSearchRec): Integer;
Path - путь, по которому искать файл, включая его имя и
расширение (возможно использовать символ *).
attr - атрибуты файла. Может принимать следующие значения:
faReadOnly $00000001 Только чтение
faHidden $00000002 Скрытый
faSysFile $00000004 Системный
faVolumeID $00000008 Метка диска
faDirectory $00000010 Директория
faArchive $00000020 Обычный
faAnyFile $0000003F Любой файл
F- переменная типа TsearchRec, в нее Delphi запихивает все характеристики найденного файла. Описание типа TsearchRec (предлагаю только для того, чтобы было проще освоиться, сам тип уже описан в
SysUtils).
type
TSearchRec = record
Time: Integer; // время создания
Size: Integer; //размер файла в байтах
Attr: Integer; // Атрибуты
Name: TFileName; //имя файла
ExcludeAttr: Integer;
FindHandle: THandle; //хандл на файл
FindData: TWin32FindData; //доп. информация о файле
end;
Теперь приведу простой пример
использования этой функции. Поставьте на
форму одну кнопку TButton, а обработчик события
OnClick у нее должен иметь примерно такой вид:
procedure TForm1.Button1Click(Sender: TObject);
var sr:TSearchRec;
begin
findFirst('*.exe',faAnyFile,sr);
edit1.Text:=sr.Name;
end;
Чтобы искать следующий такой же файл,
надо написать FindNext (Sr);. Если файл найден, то процедуры FindFirst и FindNext возвращают 0 (зеро).
Ну а теперь собственно о том, как можно
применить эти функции на практике, то есть
опять пример!!! Чтобы разобраться с
использованием этих функций попробуем
написать программку, которая выдавала
список всех программ с расширением *.exe в указанной
директории, а затем при нажатии на кнопку
включалась бы выбранная программа. На
примере я покажу, как найти все .exe файлы в
директории Windows, а затем объясню как можно
модифицировать программку!
Итак, ставим на форму компонент TListBox в
него мы будем выводить список найденных
файлов. Обработчик события OnClick для
нашей первой кнопки заменяем на такой:
procedure TForm1.Button1Click(Sender: TObject);
var sr:TSearchRec;
Result:word;
begin
ChDir('C:\windows');//меняем
папку на C:\Windows
Result := FindFirst ('*.exe',faAnyFile,sr);
ListBox1.Clear;
While result=0 do
Begin
Result:=FindNext (sr);
ListBox1.Items.add(sr.name);
End;
end;
Как видите мы просто организовали
цикличный проход по директории C:\Windows,
который прекращается, как только функции
возвращает не ноль! Функция ChDir была использована
для смены папки с текущей на папку C:\windows\
Но приведенный выше способ находит файлы лишь в том каталоге, который задан переменной Path:String. Мне кажется, этого недостаточно для решения большей части поисковых проблем, поэтому стоит написать алгоритм поиска файлов в каждой найденной директории.
У меня расширенная процедура поиска выглядит вот так:
procedure ffind(cat:string); //каталог, откуда начать поиск
var sea:TSearchRec;
res:integer; //результат поиска (0 или нет)
begin
res:=FindFirst(cat+'*.*',faAnyFile,sea); //ищем первый файл
res:=findNext(sea);//ищем следующий файл
While res=0 do
begin
if (Sea.Attr=faDirectory) and ((Sea.Name='.')or(Sea.Name='..')) then//чтобы не было файлов . и..
begin
Res:=FindNext(sea);
Continue;//продолжаем цикл
end;
if (Sea.Attr=faDirectory) then//если нашли директорию, то ищем файлы в ней
begin
Ffind(cat+Sea.Name+'\');//рекурсивно вызываем нашу процедуру
Res:=FindNext(Sea);//ищем след. файл
Continue;//продолжаем цикл
end;
form1.ListBox1.Items.Add(Sea.Name);//добавляем в Listbox:Tlistbox имя файла
Res:=FindNext(Sea);//ищем след. файл
end;
FindClose(Sea);//освобождаем пересенную поиска
end;
Здесь была использована процедура FindClose(var sea: TsearchRec);
она необходима для освобождения поисковой
переменной. В следующих примерах ее я
использовать не буду, но Вы имейте ее в виду!!!
Возможно, этот алгоритм не самый быстрый и удобный, но он работает.
Для того, чтобы ваше приложение не выглядело подвисшим, можно добавить Application.ProcessMessages в начало нашей процедуры.
Теперь поставьте на форму еще кнопку для
того, чтобы по ее нажатии запускать
выбранную в ListBox'e программу. Обработчик
события Onclick для нашей второй кнопки у
меня получился таким:
procedure TForm1.Button2Click(Sender: TObject);
begin
WinExec(pchar(listbox1.Items[listbox1.itemindex]),sw_show);
end;
Поскольку файлы находятся в директории
Windows, то при вызове метода WinExec путь к
файлам можно не указывать, а если вы
используете какую-либо другую директорию,
то вызов метода WinExec должен быть
примерно таким:
WinExec(pchar('C:\Путь к вашей папке\'+listbox1.Items[listbox1.itemindex]),sw_show);
Ну если вы хотите искать файлы в указанном
пользователем каталоге можно использовать
компонент DirectoryListBox, который дает доступ
к каталогам на вашем компьютере и позволяет
менять текущий каталог двойным нажатием
мыши. Узнать выбранный каталог можно так:
DirectoryListBox1.Directory
Поэтому в обработчике первой кнопки нужно
убрать вызов функции ChDir. А в обработчике
второй кнопки вставить приведенную выше
конструкцию.
Таким образом приведу полный код
приложения, которое получилось у меня с использованием
компонента DirectoryListBox
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, FileCtrl;
type
TForm1 = class(TForm)
Edit1: TEdit;
Button1: TButton;
ListBox1: TListBox;
Button2: TButton;
DirectoryListBox1: TDirectoryListBox;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.Button1Click(Sender: TObject);
var sr:TSearchRec;
Result:word;
begin
Result := FindFirst ('*.exe',faAnyFile,sr);
ListBox1.Clear;
While result=0 do
Begin
Result:=FindNext (sr);
ListBox1.Items.add(sr.name);
End;
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
WinExec(pchar(DirectoryListBox1.Directory+'\'+listbox1.Items[listbox1.itemindex]),sw_show);
end;
end.
Ну вот и все :)) Надеюсь, что помог Вам своими рассуждениями и примерами!
 | Работа с буфером обмена в Delphi |
|
Каждый юзер наверняка сталкивался с помещением информации в буфер и извлечением ее оттуда. Тот же пресловутый Ворд при нажатии кнопки "копировать" помещает выделенный текст в буфер, а при нажатии "вставить" - извлекает его. Таким же
образом помещаться в буфер могут изображения и данные разных форматов. Итак, буфер - это специальная область оперативной памяти в которую Windows помещает пользовательские данные для обмена между разными приложениями или внутри одного приложения.
Для того, чтобы работать с буфером обмена из Delphi, необходимо подключить к своему приложению модуль
clipbrd.pas, то есть пишем в uses clipbrd.pas,...
РАБОТА С ТЕКСТОМ
Самый простой способ поместить в буфер обмена какой-либо текст - это добавить следующий код:
Clipboard.asText:='ВАШ_ТЕКСТ';
или
Clipboard.SetTextBuf(PChar('ВАШ_ТЕКСТ'));
Чтобы извлечь текст из буфера, можно сделать так (поместим текст в Memo1:TMemo):
Memo1.Text:=Clipboard.asText;
РАБОТА С ИЗОБРАЖЕНИЯМИ
Если вы работаете с компонентом Image1:Timage, то самым простым способом поместить в него изображение из буфера будет присвоение хэндла данных Clipboard'a хэндлу Tbitmap:
Image1.Picture.Bitmap.Handle:=Clipboard.GetAsHandle(CF_bitmap);
КАК УЗНАТЬ ФОРМАТ ДАННЫХ БУФЕРА В ДАННЫЙ МОМЕНТ
Для этого существует специальная функция Clipboard.HasFormat(Format:
WORD):boolean;
Функция возвращает true, если в буфере именно этот формат, в противном случает возвращает false.
Описание форматов:
CF_TEXT - Обычный текст, заканчивающийся <CR><LF>
CF_BITMAP - Битмап изображение *.bmp
CF_METAFILEPICT - Метафайл изображение *.wmf
CF_PICTURE - Объект типа TPicture
CF_COMPONENT - Компонента Delphi (Tbutton, например)
CF_OBJECT - Любой объект в Delphi типа TPersistent
ОЧИЩАЕМ БУФЕР
Чтобы очистить буфер обмена от содержащихся в нем данных, используйте процедуру
Clipboard.clear;
ОТКРЫВАЕМ // ЗАКРЫВАЕМ
Если вы хотите, чтобы все приложения, кроме вашего не могли изменять содержимое буфера, используйте
Clipboard.Open;
После пользования буфером, его необходимо закрывать процедурой Clipboard.Close;
З.Ы. Многие объекты в Delphi изначально имеют методы, позволяющие помещать данные в буфер и извлекать их оттуда, примером может служить Tpicture.
 | Как писать Win32API приложения на Delphi |
|
Главная проблема, возникающая при написании WinAPI приложений - это неудобство ручного создания всех окон приложения. Требуется вызывать функцию CreateWindow для каждого (в том числе и дочернего) окна программы, а затем еще и менять шрифт в некоторых из них. Лучшим на мой взгляд выходом из этой ситуации является использование ресурсов диалоговых окон (dialog box resources) для соэдания всех окон приложения. В этой статье я расскажу как это делается в Delphi на примере простого приложения с одним главным и двумя (модальными) окнами.
Шаг 1. Создание ресурсов диалоговых окон
Для создания ресурсов я использовал редактор ресурсов из состава Borland C++
5.02. В Borland Resource Workshop 4.5 все почти аналогично. Создаем главное окно, вот его код:
500 DIALOGEX 0, 0, 240, 117
EXSTYLE WS_EX_DLGMODALFRAME | WS_EX_APPWINDOW | WS_EX_CLIENTEDGE
STYLE DS_MODALFRAME | DS_3DLOOK | DS_CENTER | WS_OVERLAPPED | WS_VISIBLE | WS_CAPTION |
WS_SYSMENU | WS_MINIMIZEBOX
CLASS "WndClass1"
CAPTION "Главное окно приложения"
MENU 300
FONT 8, "MS Sans Serif", 400, 0
LANGUAGE LANG_RUSSIAN , 0
{
CONTROL "OK", IDOK, "BUTTON", BS_DEFPUSHBUTTON | BS_CENTER | WS_CHILD | WS_VISIBLE | WS_TABSTOP,
19, 94, 50, 14, WS_EX_CLIENTEDGE
CONTROL "Cancel", IDCANCEL, "BUTTON", BS_PUSHBUTTON | BS_CENTER | WS_CHILD | WS_VISIBLE | WS_TABSTOP,
96, 94, 50, 14, WS_EX_CLIENTEDGE
CONTROL "Help", IDHELP, "BUTTON", BS_PUSHBUTTON | BS_CENTER | WS_CHILD | WS_VISIBLE | WS_TABSTOP,
172, 94, 50, 14, WS_EX_CLIENTEDGE
CONTROL "Группа", -1, "button", BS_GROUPBOX | BS_RIGHT | WS_CHILD | WS_VISIBLE | WS_GROUP,
20, 9, 100, 76
CONTROL "Кнопка 1", 105, "button", BS_AUTORADIOBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP,
28, 21, 60, 12
CONTROL "Кнопка 2", 106, "button", BS_AUTORADIOBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP,
28, 37, 60, 12
CONTROL "Кнопка 3", 107, "button", BS_AUTORADIOBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP,
28, 53, 60, 12
CONTROL "ListBox1", 108, "listbox", LBS_NOTIFY | LBS_SORT | LBS_NOINTEGRALHEIGHT | WS_CHILD |
WS_VISIBLE | WS_BORDER | WS_TABSTOP,
132, 13, 92, 72
}
Обратите внимание на поле CLASS. В нем должно стоять то же значение, что и в поле lpszClassName записи TWndClassEx основной программы. В редакторе ресурсов значение этого поля можно изменить в окне свойств ресурса.
Два других окна создаются как обычные ресурсы, поэтому их код я здесь не привожу.
Шаг 2. Основная программа.
Текст программы в нашем случае несколько отличается от текста, например, winmin. Регистрация оконного класса:
wc.cbSize:=sizeof(wc);
wc.style:=cs_hredraw or cs_vredraw;
wc.lpfnWndProc:=@WindowProc;
wc.cbClsExtra:=0;
wc.cbWndExtra:=DLGWINDOWEXTRA;
wc.hInstance:=HInstance;
wc.hIcon:=LoadIcon(hInstance, 'MAINICON');
wc.hCursor:=LoadCursor(0,idc_arrow);
wc.hbrBackground:=COLOR_BTNFACE+1;
wc.lpszMenuName:=nil;
wc.lpszClassName:='WndClass1';
RegisterClassEx(wc);
Обратите внимание, что в поле cbWindowExtra стоит константа DLGWINDOWEXTRA, если бы её там не было, нам не удалось бы создать главное окно, основанное на ресурсе Dialog Box. Кроме того, в поле lpszClassName стоит то же значение, что и в соответствующем поле описания ресурса окна.
Итак, класс создан и зарегистрирован, теперь создаем главное окно из ресурса: MainWnd:=CreateDialog(hInstance, '#500', 0, nil);
Напоминаю, что '#500' значит имя ресурса окна. Не забудьте подключить откомпилированный файл сценария ресурса к программе при помощи директивы {$r ...}
Шаг 3. Оконная функция.
Оконная функция ничем не отличается от обычной:
function WindowProc(wnd:HWND; Msg : Integer; Wparam:Wparam; Lparam:Lparam):Lresult; stdcall;
var nCode, ctrlID, size : word;
pt : TPoint;
s : string;
Begin
case msg of
wm_command :
Begin
nCode:=hiWord(wParam);
ctrlID:=loWord(wParam);
case ctrlID of
IDHELP : begin
DialogBox(hInstance,'#501',wnd,@DialogFunc);
end;
IDOK : begin
DialogBoxParam(hInstance,'#503',wnd,@DialogFunc2, Integer(pd));
s := 'Login: '+pd^.login;
s := s + ' ' + 'Pass: '+pd^.pass;
ListBox_AddString(lb, s);
end;
IDCANCEL : begin
DestroyWindow(wnd);
end;
end;
End;
wm_destroy :
Begin
Dispose(pd);
postquitmessage(0); exit;
Result:=0;
End;
else Result:=DefWindowProc(wnd,msg,wparam,lparam);
end;
end;
Шаг 4. Цикл сбора сообщений.
Цикл сбора сообщений следует изменить следующим образом, как при использовании немодальных диалоговых окон. Можно, правда, оставить все как есть, но тогда вы не сможете использовать клавиатуру для перемещения между дочерними окнами, использования кнопок "по умолчанию" и т.д.
While GetMessage(Mesg,0,0,0) do
begin
if mainWnd<>0 then
if IsDialogMessage(mainWnd,Mesg) then continue;
TranslateMessage(Mesg);
DispatchMessage(Mesg);
end;
После нажатия кнопки "ОК" появляется еще одно окно. Если в нем ввести текст и нажать "ОК", этот текст будет добавлен в
Listbox.
|