WWW.DISSERS.RU

БЕСПЛАТНАЯ ЭЛЕКТРОННАЯ БИБЛИОТЕКА

   Добро пожаловать!

Pages:     | 1 | 2 || 4 | 5 |   ...   | 11 |

«Петр Дарахвелидзе Евгений Марков Санкт-Петербург «БХВ-Петербург» 2003 УДК 681.3.06 Б Б К 32.973.26-018.2 Д20 Дарахвелидае ...»

-- [ Страница 3 ] --

позволяет получить ссылку на класс компонента настройки цветовой палит ры (см. разд. "Компоненты настройки цветовой палитры" далее в этой главе), используемый панелью инструментов.

Следующие три метода дают информацию о классах различных типов эле ментов управления, используемых при проектировании пользовательского интерфейса С П М Щ Ю Компонента TActionManager.

ОО Ь Для того чтобы получить доступ к элементам управления, связанным со стилем, предназначен метод function GetControlClass(ActionBar: TCustomActionBar;

Anltem:

TActionClientltem) : TCustomActionControlClass;

Глава 6. Элементы управления Windows XP Он возвращает класс элемента управления из панели ActionBar, связанного с элементом управления Anitem. Именно эта функция вызывается при соз дании элементов управления в панелях инструментов компонента TActionManager.

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

Аналогично, для получения класса, используемого в панели меню, приме няется метод function GetPopupClass(ActionBar: TCustomActionBar): TGetPopupClass;

и для классов кнопок панели инструментов применяется функция function GetScrollBtnClass: TCustomToolScrollBtnClass;

А класс самой панели инструментов возвращает функция function GetAddRemoveltemClass(ActionBar: TCustomActionBar):

TCustomAddRemoveltemClass;

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

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

Для того чтобы использовать Theme API, можно стандартным способом подключить к проекту динамическую библиотеку Theme.dll:

var ThemeDLL: HINST;

begin ThemeDLL := LoadLibrary('theme.dll');

if ThemeDLL О 0 then begin end;

end;

150 Часть II. Интерфейс и логика приложения Затем можно использовать возможности этого программного интерфейса напрямую. С деталями его реализации вы можете ознакомиться в докумен тации Microsoft MSDN.

Однако можно поступить проще. В составе Delphi 7 имеется модуль Ux Theme.pas, в котором как раз и реализованы возможности Theme API. Кро ме этого, модуль Themes.pas содержит классы для основных элементов управления, которые могут использоваться при создании визуальных сти лей, а также класс менеджера тем TThemeServices.

Так как детальное обсуждение возможностей Theme API выходит за рамки этой книги, в листинге 6.4 представлен схематичный пример использования функций этого программного интерфейса. Кроме того, как и все остальные API, работающие с GUI (Graphic User Interface) операционной системы, реальный код с использованием Theme API всегда перегружен многочис ленными и ужасными на вид (а на самом деле вполне безобидными) функ циями, рассчитывающими области вывода, неклиентские зоны оконных элементов и т. д.

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

;

Листинг 6.4. Пример использования функций Theme API в Delphi j var DC: HDC;

CurrentThemeData: HTHEME;

begin if UseThemes and InitThemeLibrary then try DC := GetWindowDC(Handle);

try CurrentThemeData := OpenThemeData(0, 'button');

CloseThemeData(CurrentThemeData);

finally ReleaseDC(Handle, DC);

end finally FreeThemeLibrary;

end else ShowMessage('Приложение или операционная система не поддерживают использование Theme API');

end;

Глава 6. Элементы управления Windows XP Функция function UseThemes: Boolean;

проверяет способность операционной системы и приложения использовать Theme API.

Методы function InitThemeLibrary: Boolean;

procedure FreeThemeLibrary;

соответственно инициализируют и выгружают библиотеку theme.dll.

Графический контекст ос наверняка понадобится при отрисовке элементов управления (см. гл. 10).

Функция OpenThemeData: function(hwnd: HWND;

pszClassList: LPCWSTR): HTHEME;

stdcall;

возвращает дескриптор темы для текущего визуального стиля и класса, имя которого представлено параметром pszClassList.

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

При завершении работы не забудьте освободить занятые дескрипторы гра фического контекста и темы. Для темы используйте функцию CloseThemeData: function(hTheme: HTHEME): HRESULT;

stdcall;

Заинтересованный читатель найдет подробное описание нужных функций Theme API в Microsoft MSDN или же может полюбопытствовать содержи мым модулей UxTheme.pas и Themes.pas.

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

• TStandardCoiorMap — по умолчанию настроен на стандартную цветовую палитру Windows;

• TXPCoiorMap — по умолчанию настроен на стандартную цветовую палитру Windows XP;

152 Часть II. Интерфейс и логика приложения • TTwiiightCoiorMap — по умолчанию настроен на стандартную полутоно вую (черно-белую) палитру Windows.

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

Все панели инструментов (класс TActionTooiBar), созданные в этом компо ненте (см. гл. 8), имеют свойство property ColorMap: TCustomActionBarColorMap;

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

Обратите внимание, что в компоненте TTooiBar, перенесенном из Палитры компонентов на форму вручную, это свойство отсутствует.

Все компоненты настройки цветовой палитры имеют один метод-обра ботчик property OnColorChange: TnotifyEvent;

который вызывается при изменении любого цвета палитры.

Резюме В оформлении пользовательского интерфейса операционной системы Win dows ХР появилось новшество — визуальные стили, которые позволяют на страивать не только внешний вид элементов управления, но и их поведе ние, и даже управлять отрисовкой отдельных частей элементов управления.

Это стало возможным благодаря системной библиотеке ComCtl32.dll вер сии 6.

Любое приложение может использовать возможности этой библиотеки по созданию "продвинутых" пользовательских интерфейсов. Для этого оно должно содержать манифест — документ XML, описывающий, какую вер сию ComCtl32.dll используют его элементы управления.

Для отрисовки частей элементов управления приложение может использо вать функции Theme API, разработанные Microsoft. Кроме этого, данный программный интерфейс позволяет управлять менеджером тем, который сменяет текущие темы для визуального стиля. Темы хорошо известны поль зователям, начиная с Windows 95.

Разработчики могут создавать в Delphi 7 собственные визуальные стили, разрабатывая их на основе класса TActionBarstyleEx.

ГЛАВА Списки и коллекции Практически любое приложение должно уметь выполнять ряд стандартных операций по обработке каких-либо данных. К ним относятся загрузка дан ных при открытии приложения, представление данных в удобном виде для использования внутри приложения, сохранение данных при завершении работы. Перечисленные действия необходимы и приложениям баз данных, и играм, и научным программам.

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

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

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

Для работы со строковыми списками предназначены классы Tstrings и TStringList.

Любые типы данных можно заносить в список указателей, который реали зован в классе TList.

Использование наборов объектов (широко применяются в классах VCL), которые называются коллекциями, осуществляется при помощи классов TCollection И TCollectionltem.

154 Часть II. Интерфейс и логика приложения В этой главе рассматриваются следующие вопросы:

• что такое список;

как устроено основное свойство всех списков, объеди няющее его элементы;

• добавление, изменение и удаление элементов списка;

• поиск заданного элемента;

• механизм выделения памяти под элементы списка;

• список строк;

• список указателей;

• чем отличается коллекция от списка;

• коллекции;

• использование потоков.

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

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

Класс TStrings Класс TStrings является базовым классом, который обеспечивает потомков основными свойствами и методами, позволяющими создавать работоспо собные списки строк. Его прямым предком является класс TPersistent.

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

Внимание!

Попытка прямого использования в приложении экземпляра класса TStrings вызовет ошибку применения абстрактного класса на этапе выполнения про граммы, а именно при попытке заполнить список значениями. Простая замена Глава 7. Списки и коллекции типа объектной переменной списка на T S t r i n g L i s t делает приложение пол ностью работоспособным без какого-либо дополнительного изменения исход ного кода.

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

Класс TStringList Класс TStringList обеспечивает реальное использование списков строк в приложении. По существу, класс представляет собой оболочку вокруг ди намического массива значений списка, представленного свойством strings.

Объявление свойства (унаследованное от Tstrings) выглядит так:

property Strings[Index: Integer]: string read Get write Put;

default;

Для работы со свойством используются внутренние методы Get и Put, в ко торых применяется внутренняя переменная FList:

type PStringltem = "TStringltem;

TStringltem = record FString: string;

FObj ect: TObj ect;

end;

PStringltemList =ATStringItemList;

TStringltemList = array[0..MaxListSize] of TStringltem;

FList: PStringltemList;

Из ее объявления видно, что список строк представляет собой динамиче ский массив записей TStringltem. Эта запись позволяет объединить саму строку и связанный с ней объект.

Максимальный размер списка ограничен константой MaxListSize = Maxint div 16;

значение которой после нехитрых вычислений составит 134 217 727. Таким образом, видно, что строковый список Delphi теоретически конечен, хотя на практике гораздо чаще размер списка ограничивается размером доступ ной памяти.

156 Часть II. Интерфейс и логика приложения Обращение к отдельному элементу списка может осуществляться через свойство strings таким образом:

SomeStrings.Strings[i] := Editl.Text;

или так:

SomeStrings[i] := Editl.Text;

Оба способа равноценны.

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

Функция function Add(const S: string): Integer;

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

Метод procedure Append(const S: string);

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

Метод procedure AddStrings(Strings: TStrings);

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

При необходимости можно добавить новый элемент в произвольное место списка. Для этого применяется метод procedure Insert(Index: Integer;

const S: string);

который вставляет элемент s на место элемента с индексом index. При этом все указанные элементы смещаются на одну позицию вниз.

Для удаления элемента списка используется метод procedure Delete(Index: Integer);

Метод procedure Move(CurIndex, Newlndex: Integer);

перемещает элемент, заданный индексом curindex, на новую позицию, за данную ИНДеКСОМ Newlndex.

А метод procedure Exchange(IndexI, Index2: Integer);

меняет местами элементы с индексами indexi и index2.

Глава 7. Списки и коллекции Довольно часто в списках размещается строковая информация следующего вида:

'Name=Value' В качестве примера можно привести строки из файлов INI или системного реестра. Специально для таких случаев в списке предусмотрено представле ние строк в двух свойствах. В свойстве Names содержится текст до знака ра венства. В свойстве values содержится текст после знака равенства по умол чанию. Однако символ-разделитель можно заменить на любой другой, ис пользовав свойство property NameValueSeparator: Char;

Доступ к значениям свойства values осуществляется по значению. Напри мер, если в списке есть строка City=Saint-Petersburg то значение свойства value будет равно Value['City'] = 'Saint-Petersburg' Кроме этого, значение свойства value можно получить, если известен его индекс:

property ValueFormlndex[Index: Integer]: string;

Как видно из объявления внутреннего списка FList (см. выше), с каждым элементом списка можно связать любой объект. Для этого используется свойство property Objects[Index: Integer]: TObject;

Свойство strings элемента и свойство objects связанного с ним объекта имеют одинаковые индексы. Если строка не имеет связанного объекта, то свойство objects равно Nil. Один объект может быть связан с несколькими строками списка одновременно.

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

TCityProps = class (TObject) Square: Longlnt;

Population: Longlnt;

Status: String;

end;

Для того чтобы добавить к строке из списка объект, используется метод AddObjecf.

function AddObject(const S: string;

AObject: TObject): Integer;

virtual;

158 Часть II. Интерфейс и логика приложения Обратите внимание, что в параметре AObject необходимо передавать указа тель на объект. Проще всего это сделать таким образом:

SomeStrings.AddObject('Someltem', TCityProps.Create);

Или же так:

var SPb: TCityProps;

SPb := TCityProps.Create;

{Создание объекта} SPb.Population := 5000000;

SomeStrings.Strings[i] := 'Санкт-Петербург';

SomeStrings.Objects[i] := SPb;

(Связывание объекта и строки) Можно поступить и подобным образом (помните, что строка уже должна существовать):

SomeStrings.Strings[i] := 'Санкт-Петербург';

SomeStrings.Objects[i] := TCityProps.Create;

(SomeStrings.Objects[i] as TCityProps).Population := 5000000;

Аналогично методу insert, элемент и связанный с ним объект можно вста вить в произвольное место списка методом procedure InsertObject(Index: Integer;

const S: string;

AObject: TObject);

При перемещении методом Move вместе с элементом переносится и указа тель на связанный объект.

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

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

for i := 0 to SomeList.Count — 1 do SomeList.Objects[i].Destroy;

Если при удалении связанного объекта необходимо выполнить некоторые действия, предусмотренные в деструкторе, приведение типов TCityProps(SomeList.Objects[i]).Destroy;

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

Глава 7. Списки и коллекции / Метод procedure Clear;

override;

полностью очищает список, удаляя все его элементы.

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

Свойство только для чтения property Count: Integer;

возвращает число элементов списка.

Так как основу списка составляет динамический массив, то для него в про цессе работы должна выделяться память. При добавлении в список новой строки память для нее выделяется автоматически. Свойство property Capacity: Integer;

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

Свойство property Duplicates: TDuplicates;

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

Тип type TDuplicates = (duplgnore, dupAccept, dupError);

определяет реакцию списка на добавление повторного элемента:

• duplgnore — запрещает добавление повторных элементов;

• dupAccept — разрешает добавление повторных элементов;

• dupError — запрещает добавление повторных элементов и генерирует ис ключительную ситуацию.

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

160 Часть II. Интерфейс и логика приложения type TStringListSortCompare = function(List: TStringList;

Indexl, Index2: Integer): Integer;

procedure CustomSort(Compare: TStringListSortCompare);

Чтобы отсортировать список, вы должны описать функцию сравнения двух элементов с индексами indexl и index2, которая должна возвращать сле дующие результаты:

• 1 — если элемент с индексом indexl вы хотите поместить впереди эле мента Index2;

• 0 — если они равны;

• 1 — если элемент с индексом indexl вы хотите поместить после элемента Index2.

Для описанного выше примера с объектом-городом нужны три процедуры:

function SortByStatus(List: TStringList;

Indexl, Index2: Integer):

Integer;

begin Result := AnsiCompareStr((List.Objects[Indexl] as TCityProps).Status, (List.Objects[Index2] as TCityProps).Status;

end;

function SortBySquare(List: TStringList;

Indexl, Index2: Integer):

Integer;

begin if (List.Objects[Indexl] as TCityProps).Square < (List.0bjects[Index2] as TCityProps). Square) then Result := - else if (List.Objects[Indexl] as TCityProps).Square = (List.Objects[Index2] as TCityProps).Square then Result := else Result := 1;

end;

function SortByPopulation(List: TStringList;

Indexl, Index2: Integer):

Integer;

begin if (List.Objects[Indexl] as TCityProps).Population < (List.Objects[Index2] as TCityProps). Population then Result := - else if (List.Objects[Indexl] as TCityProps). Population = (List.Objects[Index2] as TCityProps). Population then Result := else Result := 1;

end;

Передаем одну из процедур в метод CustomSort:

Cities.CustomSort(SortByPopulation);

Глава 7. Списки и коллекции Для поиска нужного элемента используется метод function FincHconst S: string;

var Index: Integer): Boolean;

В параметре s передается значение для поиска. В случае успеха функция возвращает значение True, а в параметре index содержится индекс найден ного элемента.

Метод function IndexOf(const S: string): Integer;

возвращает индекс найденного элемента s. Иначе функция возвращает —1.

Метод function IndexOfName(const Name: string): Integer;

возвращает индекс найденного элемента, для которого свойство Names сов падает со значением параметра Name.

Для поиска связанных объектов используется метод function IndexOfObject(AObject: TObject): Integer;

В качестве параметра AObject должна передаваться ссылка на искомый объект.

А свойство property CaseSensitive: Boolean;

включает или отключает режим поиска и сортировки с учетом регистра символов.

Помимо свойства strings, содержимое списка можно получить при помощи свойств property Text: string;

И property CommaText: string;

Они представляют все строки списка в виде одной строки. При этом в пер вом свойстве элементы списка разделены символами возврата каретки и переноса строки. Во втором свойстве строки заключены в двойные кавычки и разделены запятыми или пробелами. Так, для списка городов (Москва, Петербург, Одесса) свойство Text будет равно Москва#$0#$АПетербург#$0#$АОдесса а СВОЙСТВО CommaText равно "Москва", "Петербург", "Одесса".

Важно иметь в виду, что эти свойства доступны не только по чтению, но и по записи. Так что заполнить список вы сможете не только циклически, 6 Зак. 162 Часть II. Интерфейс и логика приложения вызывая и используя методы Add или insert, но и одним-единственным Присвоением значения свойствам Text И И CommaText.

Л Список может взаимодействовать с другими экземплярами класса TStringList.

Широко распространенный метод procedure Assign(Source: TPersistent);

полностью переносит список source в данный.

Метод function Equals(Strings: TStrings): Boolean;

возвращает значение True, если элементы списка strings полностью совпа дают с элементами данного списка.

Список можно загрузить из файла или потока. Для этого используются методы procedure LoadFromFile(const FileName: s t r i n g ) ;

И procedure LoadFromStream(Stream: TStream);

Сохранение списка выполняется методами procedure SaveToFile(const FileName: string);

И procedure SaveToStream(Stream: TStream);

Перед изменением списка вы можете получить управление, описав обработ чик события property OnChange: TNotifyEvent;

а после изменения property OnChanging: TNotifyEvent;

На дискете, прилагаемой к этой книге, вы можете ознакомиться с приме ром ИСПОЛЬЗОваНИЯ СПИСКОВ СТрОК DemoStrings.

Список указателей Для хранения списка указателей на размещенные в адресном пространстве структуры (объекты, динамические массивы, переменные) предназначен класс TList. Так же, как и список строк TStringList, список указателей обеспечивает эффективную работу с элементами списка.

Глава 7. Списки и коллекции Класс TList Основой класса TList является список указателей. Сам список представляет собой динамический массив указателей, к которому можно обратиться через индексированное свойство property Items[Index: Integer]: Pointer;

Нумерация элементов начинается с нуля.

Прямой доступ к элементам массива возможен через свойство type PPointerList = "TPointerList;

TPointerList = array[0..MaxListSize-1] of Pointer;

property List: PPointerList;

которое имеет атрибут "только для чтения".

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

) ( Примечание В списке могут содержаться указатели на разнородные структуры. Не обяза тельно хранить в списке только указатели на объекты или указатели на записи.

Реализованные в классе TList операции со списком обеспечивают потреб ности разработчика и совпадают с операциями списка строк.

Для добавления в конец списка нового указателя используется метод function Add(Item: Pointer): Integer;

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

Новый указатель можно добавить в нужное место списка. Для этого исполь зуется метод procedure Insert(Index: Integer;

Item: Pointer);

В параметре index указывается необходимый порядковый номер в списке.

Перенос существующего элемента на новое места осуществляется методом procedure Move(Curlndex, Newlndex: Integer);

Параметр curlndex определяет старое положение указателя. Параметр Newindex задает новое его положение.

Также можно поменять местами два элемента, определяемые параметрами Indexl И Index2:

procedure Exchange(Indexl, Index2: I n t e g e r ) ;

164 Часть II. Интерфейс и логика приложения Для удаления указателей из списка используются два метода. Если известен индекс, применяется метод procedure Delete(Index: Integer);

Если известен сам указатель, используется метод function Remove(Item: Pointer): Integer;

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

function Expand: TList;

Для того чтобы метод сработал, необходимо, чтобы Count = Capacity. Алго ритм работы метода представлен в табл. 7.1.

Таблица 7.1. Алгоритм увеличения памяти списка Значение свойства Capacity На сколько увеличится свойство Capacity <4 4..8 >8 Метод procedure Clear;

dynamic;

используется для удаления всех элементов списка сразу.

Для поиска указателя по его значению используется метод function IndexOf(Item: Pointer): Integer;

Метод возвращает индекс найденного элемента в списке. При неудачном поиске возвращается —1.

Для сортировки элементов списка применяется метод type TListSortCompare = function (Iteml, Item2: Pointer): Integer;

procedure Sort(Compare: TListSortCompare);

Так как состав структуры, на которую указывает элемент списка, невозмож но заранее обобщить, разработка процедуры, осуществляющей сортировку, возлагается на программиста. Метод sort лишь обеспечивает попарное сравнение указателей на основе созданного программистом алгоритма (при мер сортировки см. выше в разд. "Класс TStringList").

Глава 7. Списки и коллекции Полностью все свойства и методы класса TList представлены в табл. 7.2.

Таблица 7.2. Свойства и методы класса TList Объявление Описание Определяет число строк, property Capacity: Integer;

для которых выделена память Возвращает число строк property Count: Integer;

в списке Список указателей property Items[Index: Integer]: Pointer;

type Динамический массив указателей TPointerList = array[0..MaxListSize-1] of Pointer;

PPointerList = ATPointerList;

property List: PPointerList;

function Add(Item: Pointer): Integer;

Добавляет к списку новый указатель Полностью очищает список procedure Clear;

dynamic;

Удаляет указатель с индексом procedure Delete(Index: Integer);

Index Генерирует исключительную class procedure Error(const Msg: string;

ситуацию EListError.

Data: Integer);

virtual;

Сообщение об ошибке создается из форматирующей строки Msg и числового параметра Data Меняет местами указатели procedure Exchange(Index1, Index2: Integer), с индексами indexl и Index Увеличивает размер памяти, function Expand: TList;

отведенной под список Возвращает первый указатель function First: Pointer;

из списка Возвращает индекс указателя, function IndexOf(Item: Pointer): Integer;

заданного параметром Item Вставляет новый элемент procedure Insert(Index: Integer;

Item в позицию Index Item: Pointer);

Возвращает последний function Last: Pointer;

указатель в списке Перемещает элемент списка procedure Move(Curlndex, Newlndex:

на новое место Integer);

166 Часть II. Интерфейс и логика приложения Таблица 7.2 (окончание) Объявление Описание procedure Pack;

Удаляет из списка все пустые (Nil) указатели function Remove (Item: Pointer): Integer;

Удаляет из списка указатель Item type TListSortCompare = function (Iteml, Сортирует элементы списка Item2: Pointer): Integer;

procedure Sort(Compare: TListSortCompare);

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

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

Список точек можно отсортировать по координате X в порядке возрастания.

j Листинг 7.1. Модуль главной формы проекта DemoList unit Main;

interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, Buttons;

type TMainForm = class(TForm) ListBtn: TBitBtn;

ClearBtn: TBitBtn;

DelBtn: TBitBtn;

SortBtn: TBitBtn;

procedure FormCreate(Sender: TObject);

procedure FormClose(Sender: TObject;

var Action: TCloseAction);

Глава 7. Списки и коллекции procedure FormMouseDown(Sender: TObject;

Button: TMouseButton;

Shift: TShiftState;

X, Y: Integer);

procedure ListBtnClick(Sender: TObject);

procedure ClearBtnClick(Sender: TObject);

procedure DelBtnClick(Sender: TObject);

procedure SortBtnClick(Sender: TObject);

private PixList: TList;

PixNum: Integer;

public { Public declarations } end;

TMyPixel = class(TObject) FX: Integer;

FY: Integer;

FText: Integer;

constructor Create(X, Y, Num: Integer);

procedure SetPixel;

end;

var MainForm: TMainForm;

implementation {$R *.DFM} const PixColor = clRed;

var CurPixel: TMyPixel;

constructor TMyPixel.Create(X, Y, Num: Integer);

begin inherited Create;

FX : X;

= FY : Y;

= FText := Num;

SetPixel;

end;

procedure TMyPixel.SetPixel;

begin MainForm.Canvas.PolyLine([Point(FX, FY), Point(FX, FY)]);

MainForm.Canvas.TextOut(FX + 1, FY + 1, intToStr(FText));

end;

168 Часть II. Интерфейс и логика приложения function PixCompare(Iteml, Item2: Pointer): Integer;

var Pixl, Pix2: TMyPixel;

begin Pixl := Iteml;

Pix2 := Item2;

Result := Pixl.FX - Pix2.FX;

end;

procedure TMainForm.FormCreate(Sender: TObject);

begin PixList := TList.Create;

PixNum := 1;

{Счетчик точек} Canvas.Pen.Color := PixColor;

{Цвет точки} Canvas.Pen.Width := 3;

{Размер точки} Canvas.Brush.Color := Color;

(Цвет фона текста равен цвету формы} end;

procedure TMainForm.FormClose(Sender: TObject;

var Action: TCloseAction);

begin PixList.Free;

end;

procedure TMainForm.FormMouseDown(Sender: TObject;

Button: TMouseButton;

Shift: TShiftState;

X, Y: Integer);

begin PixList.Add(TMyPixel.Create(X, Y, PixNum));

Inc(PixNum);

end;

procedure TMainForm.ListBtnClick(Sender: TObject);

var i: Integer;

begin with PixList do for i := 0 to Count — 1 do begin CurPixel := Items [i];

CurPixel.SetPixel;

end;

end;

procedure TMainForm.ClearBtnClick(Sender: TObject);

begin Canvas.FillRect(Rect(0, 0, Width, Height));

end;

Глава 7. Списки и коллекции procedure TMainForm.DelBtnClick(Sender: TObject);

begin PixList.Clear;

PixNum : 1;

= end;

procedure TMainForm.SortBtnClick(Sender: TObject);

var i: Integer;

begin PixList.Sort(PixCompare);

with PixList do for i := 0 to Count — 1 do TMyPixel(Items[i]).FText := i + 1;

end;

end.

Класс TMyPixel обеспечивает хранение координат точки и ее порядковый номер в серии. Эти параметры передаются в конструктор класса. Метод setPixel обеспечивает отрисовку точки на канве формы (см. гл. 10).

Экземпляр класса создается для каждой новой точки при щелчке кнопкой мыши в методе-обработчике FormMouseDown. Здесь же указатель на новый объект сохраняется в создаваемом при помощи метода Add элементе списка PixList. Таким образом, программа "запоминает" расположение и порядок следования точек.

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

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

Перед вторичным отображением точек необходимо очистить поверхность формы. Эту Операцию ВЫПОЛНЯеТ МеТОД-обрабоТЧИК ClearBtnClick.

Список точек можно отсортировать по координате X в порядке возрастания.

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

Если функция возвращает положительное число, то элемент itemi больше элемента item2. Если результат отрицательный, то itemi меньше, чем item2.

Если элементы равны, функция должна возвращать ноль.

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

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

Метод-обработчик DeiBtnciick обеспечивает полную очистку списка PixList.

Коллекции Коллекция представляет собой разновидность списка указателей, оптимизи рованную для работы с объектами определенного вида. Сама коллекция ин капсулирована в классе тсо!lection. Элемент коллекции должен быть эк земпляром класса, унаследованного от класса TCoiiectionitem. Это облегча ет программирование и позволяет обращаться к свойствам и методам объектов напрямую.

Коллекции объектов широко используются в компонентах VCL. Например, панели компонента TCooiBar (см. гл. 5) объединены в коллекцию. Класс TCooiBands, объединяющий панели, является наследником класса TCoiiection.

А отдельная панель — экземпляром класса TCooiBar, происходящего от класса TCoiiectionitem.

Поэтому знание свойств и методов классов коллекции позволит успешно использовать их при работе со многими компонентами (TDBGrid, TListview, TStatusBar, TCooiBar И Т. Д.).

Для работы с коллекцией, независимо от инкапсулирующего ее компонен та, применяется специализированный Редактор коллекции (рис. 7.1), набор 7* Editing CoolBar I.Bands ЕЭ О•Bandi 1 • Band Рис. 7.1. Редактор коллекции Глава 7. Списки и коллекции элементов управления которого может немного изменяться для разных ком понентов.

Список Редактора объединяет элементы коллекции. При выборе одной строки из списка свойства объекта коллекции становятся доступны в Ин спекторе объектов. В список можно добавлять новые элементы и удалять существующие, а также менять их взаимное положение.

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

Класс TCollection Класс TCollection является оболочкой коллекции, обеспечивая разработчи ка набором свойств и методов для управления ею (табл. 7.3).

Сама коллекция содержится в свойстве property Items[Index: Integer]: TCollectionltem;

Полное объявление свойства в классе выглядит следующим образом:

property Items[Index: Integer]: TCollectionltem read Getltem write Setltem;

Методы Getitem и setltem обращаются к внутреннему полю Fitems:

FItems: TList;

Именно оно хранит коллекцию объектов во время выполнения. Отсюда следует, что коллекция представляет собой список указателей на экземпля ры класса TCollectionltem или его наследника. Класс TCollection обеспечи вает удобство использования элементов списка.

Таблица 7.3. Свойства и методы класса TCollection Описание Объявление Возвращает число элементов property Count: Integer;

коллекции type TcollectionltemClass = class of Возвращает класс-наследник TCollectionltem;

TCollectionltem, экземпляры которого собраны в коллекции property ItemClass: TcollectionltemClass;

property Items[Index: Integer]: Коллекция экземпляров класса TCollectionltem;

Добавляет к коллекции новый function Add: TCollectionltem;

экземпляр класса Копирует коллекцию из объекта procedure Assign(Source: TPersistent);

Source в данный объект override;

Часть II. Интерфейс и логика приложения Таблица 7.3 (окончание) Объявление Описание Отменяет перерисовку коллекции.

procedure BeginUpdate;

virtual;

Используется при внесении изменений в коллекцию Удаляет из коллекции procedure Clear;

все элементы Отменяет действие метода procedure EndUpdate;

virtual;

BeginUpdate Возвращает объект коллекции function FindItemID(ID: Integer):

TCoiiectionitem;

с номером ID function GetNamePath: string;

override;

Возвращает имя класса коллекции во время выполнения, если кол лекция не имеет владельца. Иначе возвращает название свойства класса, владеющего коллекцией Вставляет в коллекцию новый function Insert(Index: Integer):

объект на место с номером index Tcollectionltem;

Класс TCoiiectionitem Класс TCoiiectionitem инкапсулирует основные свойства и методы элемента коллекции (табл. 7.4). Свойства класса обеспечивают хранение информации о расположении элемента в коллекции.

Таблица 7.4. Свойства и методы класса TCoiiectionitem Объявление Описание property Collection: Tcollection;

Содержит экземпляр класса коллекции, которой принадлежит данный элемент Содержит имя элемента, которое пред property DisplayName: string;

ставляет его в Редакторе коллекции Содержит уникальный номер элемента property ID: Integer;

в коллекции, который не может изме няться Содержит порядковый номер элемента property Index: Integer;

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

• Классы TStrings и TstringList обеспечивают применение списков строк.

• Класс TList инкапсулирует список указателей.

П Классы TCoiiection и TCoiiectionitem позволяют применять в компонен тах и программном коде коллекции группы однородных объектов.

В среде Delphi вы можете найти еще много полезных классов общего при менения. В модуле CLASSES.PAS есть класс TBits, обеспечивающий побит ное чтение и запись информации. В модуле CONTNRS.PAS есть классы TStack И TQueue (стек И очередь), а также ПОТОМКИ TList — TClassList, TComponentList и т. д. Они помогут вам решать типовые задачи быстро и без "изобретения велосипеда".

ГЛАВА Действия (Actions) и связанные с ними компоненты С давних пор повелось, что стандарты на пользовательский интерфейс Windows-приложений Microsoft задает "явочным порядком". Первая громкая история на эту тему связана с появлением в 1994 г. Excel 2.0 for Windows, когда разработчики из Редмонда впервые применили интерфейс со многими документами (впоследствии широко известный как MDI) и даже не подума ли задокументировать и опубликовать его. После справедливого возмуще ния широких кругов программистской общественности Microsoft исправил ся и теперь новые возможности интерфейса публикуются если не до выхода продукта, то, по крайней мере, ненамного позже. Вспомним, с Internet Explorer появилась панель CoolBar и кнопки, картинки, которые подсвечи вались при прохождении над ними курсора мыши. Большинство же нови нок связано с флагманским продуктом Microsoft — Office. Одна из них — весьма, кстати, полезная — это система настраиваемых пользователем меню и панелей инструментов.

В Delphi 7 разработчику предоставляется доступ к аналогичным возмож ностям. Правда, для работы с ними придется забыть "старый" интерфейс — компоненты TMainMenu, TToolBar — и полностью переучиться. Теперь "про двинутый" интерфейс С С О Т ИЗ Н В Х компонентов TActionManager, ОТИ ОЫ TActionMainMenuBar, TActionToolBar И примкнувшего К НИМ TCustomizeDlg (страница Палитры компонентов Additional). Для читателя уже знакомого с действиями (Actions) названия этих компонентов покажутся знакомыми.

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

Данная глава посвящена рассмотрению принципов их использования. Сна чала поговорим о понятии действия (Action) и рассмотрим компонент TActionList, который является кроссплатформенным (работает как в Delphi 7, так и в Kylix). Далее рассмотрим обширный набор стандартных действий.

Глава 8. Действия (Actions) и связанные с ними компоненты 175_ И в заключение читатель узнает о Windows-потомке TActionList под названием TActionManager и о связанных с ним компонентах.

Действия. Компонент TActionList Пользовательский интерфейс современных приложений весьма многообра зен, и зачастую один и тот же результат можно получить разными способа ми — щелчком на кнопке на панели инструментов, выбором пункта меню, нажатием комбинации клавиш и т. п. Можно решить проблему "в лоб" и повторить один и тот же код два, три раза и т. д. Недостатки такого подхо да, очевидно, не обязательно комментировать. Можно воспользоваться Ин спектором объектов и назначить пункту меню тот же обработчик события, что и кнопке, благо событие onclick имеет везде одинаковый синтаксис.

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

Наконец, современный способ — это воспользоваться компонентом TActionList и разом решить эти проблемы. Как следует из названия, это — централизованное хранилище, где воздействия со стороны пользователя связываются с реакциями на них.

Действием (Action) будем именовать операцию, которую пользователь хочет произвести, воздействуя на элементы интерфейса. Тот компонент, на кото рый он хочет воздействовать, называется целью действия (Action target).

Компонент, посредством которого действие инициировано (кнопка, пуню меню), — клиент действия (Action client). Таким образом, в иерархии клас сов Delphi действие TAction — это невизуальный компонент, который игра ет роль "черного ящика", получающего сигнал от одного или нескольких клиентов, выполняющих действия над одной (или разными) целями.

( ) Примечание Действия могут работать только будучи объединенными в список компонентов T A c t i o n L i s t или TActionManager. Вне этих компонентов применение действий невозможно.

Спроектировав на бумаге пользовательский интерфейс, начните работу с помещения на форму компонента TActionList. Он находится в Палитре компонентов на первой странице Standard (вот видите какое ему уделяется внимание!). После этого следует запустить редактор списка действий двой ным щелчком мышью на компоненте или с помощью контекстного меню (рис. 8.1).

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

Часть II. Интерфейс и логика приложения [Obiect Inspector В ] Action!

\Ъ. Editing Form LActionlistl • :

^ЩЩй^:!/*, | Caption Categories:-';

j Category (No CategoijO (No Category) FileOpeni ;

Checked False Edit ( Enabled True " [ ;

Grouplndex (All Actions) i HelpContext 0 I : HelpKevwoid HelpType htKeyword „ Hint ' Imagelndex Name Actioni Second'atyShoi (TShottCutllist) Shortcut' (None) 'Tag Visible' [ True All shown Рис. 8.1. Внешний вид редактора действий Рис. 8.2. Опубликованные свойства компонента T A c t i o n L i s t объекта T A c t i o n Помимо них можно вставить и обычное действие, которое получит имя Actioni. Итак, что же из себя представляет действие? Его опубликованные свойства показаны на рис. 8.2. Рассмотрим их по группам.

События, связанные с действиями Компонент TAction реагирует на три события: OnExecute, OnOpdate И OnHint.

Первое — и самое главное — должно быть как раз реакцией на данное дей ствие. Это событие возникает в момент нажатия кнопки, пункта меню — короче, при поступлении сигнала от клиента действия. Здесь — как прави л о — и пишется обработчик. Почему "как правило"? Потому что схема об работки сигнала 4-этапная:

1. Сначала вызывается обработчик события OnExecute списка действий TActionList:

property OnExecute: TActionEvent;

TActionEvent = procedure (Action: TBasicAction;

var Handled: Boolean) of object;

Если обработчик этого события вами не предусмотрен, или в параметре Handled он вернул значение False, происходит генерация следующего со бытия — шаг 2.

2. Вызывается обработчик события onActionExecute глобального объекта Application (тип события тот же — TActionEvent). Если и оно не обрабо тало сигнал действия, переходим к следующему шагу.

Глава 8. Действия (Actions) и связанные с ними компоненты 3. Вызывается обработчик события onExecute самого действия (объекта ти па TAction или его потомка).

4. Если первые три шага не обработали ситуацию (вернули False), то, вероятно, это было связано с неправильной целью (Target) действия.

В качестве "последнего шанса" приложению посылается сообщение CM_ACTIONEXECOTE. В этом случае происходит поиск другой цели для дан ного действия (об алгоритме поиска цели см. ниже).

Первые две возможности в этом списке используются относительно редко.

Тем не менее они полезны, если вам нужно глобально добавлять/уда лять/разрешать/запрещать действия.

Введение события Onupdate является очень хорошей находкой, о нем напи шем подробно. И автор этих строк, и, возможно, вы потратили немало вре мени, чтобы в разрабатываемых программах элементы управления находи лись в актуальном состоянии. Если, скажем, вашей программой открыт первый файл, то нужно активировать ряд кнопок и пунктов меню (Save, Save as, Print и т. п.);

как только закрыт последний — отключить их. Если в буфере обмена есть что-то подходящее, необходимо активизировать пункт меню и кнопку Paste, если нет — отключить. В результате код, отслежи вающий это, у неопытных программистов "размазывается" по всему прило жению. А ведь можно поступить проще. Событие TAction.onupdate возника ет в моменты простоя приложения, т. е. тогда, когда оно не занято обработ кой сообщений (цикл содержится в методе idle объекта Application). Это гарантирует, что оно возникнет ДО ТОГО, как пользователь щелкнет мы шью и увидит выпадающие пункты меню;

поэтому можно успеть обновить их состояние. Пример использования события onupdate:

procedure TForml.PasteActionUpdate(Sender: TObject);

begin TAction(Sender).Checked := Clipboard.HasFormat(CF_TEXT);

end;

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

Третье событие имеет такой тип:

THintEvent = procedure (var HintStr: string;

var CanShow: Boolean) of object;

Оно вызывается тогда, когда от элемента управления требуется показать подсказку, связанную с данным действием. В обработчике события можно указать, будет ли что-нибудь показываться (параметр CanShow) и, если да, то что именно (параметр Hintstr).

Часть II. Интерфейс и логика приложения Это были события, относящиеся к компоненту TAction. Сам компонент TActionList также имеет три события: OnExecute, OnUpdate И OnChange.

О первых двух мы уже сказали;

третье происходит в момент изменения списка (добавления или удаления действий).

Свойства, распространяемые на клиентов действия Если у нескольких кнопок или пунктов меню общий обработчик, разумно потребовать, чтобы у них были и другие общие свойства. Так оно и реали зовано в Delphi. В табл. 8.1 перечислены свойства, чье значение автомати чески распространяется на всех клиентов данного действия.

Таблица 8.1. Свойства компонента TAction, автоматически распространяемые на всех его клиентов Назначение Свойство property Caption: string;

Заголовок, связанный с действием property Hint: string;

Подсказка к действию property Enabled: Boolean;

Устанавливает, разрешено/запрещено ли действие property Checked: Boolean;

Устанавливает, отмечено ли действие property Grouplndex: Integer;

Индекс группы, в которую входит дейст вие. Объекты TAction с одним значением этого свойства (причем большим нуля) имеют зависимое переключение. Если свойство Checked любого объекта из группы устанавливается в True, у осталь ных оно сбрасывается в False property AutoCheck: boolean;

Установка в True автоматически меняет значение свойства Checked на противо положное после каждого выполнения действия property Imagelndex: Integer;

Индекс картинки в общем наборе карти нок (набор указывается в свойствах роди тельского TActionList) property HelpType: THelpType;

Указывает на тип значения, связывающе го действие с разделом системы помощи (htKeyword/htContext) property HelpContext: THelpContext;

Если свойство HelpType установлено в htContext, это свойство содержит ID раздела системы помощи Глава 8. Действия (Actions) и связанные с ними компоненты Таблица 8.1 (окончание) Свойство Назначение property HelpKeyword: string;

Если свойство HelpType установлено в htKeyword, то свойство содержит ключе вое слово (термин), по которому происхо дит открытие соответствующего раздела системы помощи Вы привыкли к программам с картинками в меню и на панелях инструмен тов? Действие также можно снабдить картинкой. Компонент TActionList связывается со списком картинок TimageList, а действие TAction — с кон кретной картинкой через свойство imageindex. Таким образом, все элементы управления, связанные с действием, — кнопки и пункты меню — будут иметь одну и ту же картинку, как показано на рис. 8.3. Впрочем, это отно сится ко всем остальным свойствам из табл. 8.1.

l?Formi НЯЦЕЗЦ н у edit,,. ' у «Л Undo Ctrl+Z xMal Jt Cut Ctrl+X Й Paste Ctrl+V ' ''• Рис. 8.З. Меню и панель инструментов используют один список действий Прочие свойства Чтобы связать с действием комбинацию "горячих" клавиш, выберите одну из возможных комбинаций в редакторе свойства shortcut. Более того, в Delphi 7 существует возможность добавлять не одну, а множество комби наций "горячих" клавиш. Вторая и последующие должны быть заданы в свой стве secondaryshortcuts. Когда вызывается редактор этого свойства, пользо ватель видит обычный редактор набора строк. И вводить комбинации нуж но по принципу "как слышится, так и пишется": например +, ++<0> и т. п., по одной комбинации на каждой строке.

Для упорядочивания все действия разбиты на категории:

property Category: string;

180 Часть II. Интерфейс и логика приложения Это свойство содержит условное название категории, к которой относится действие, например, File, Edit, Help и т. п. Роль категории сводится к тому, чтобы объединить сходные действия при показе в ActionList или ActionManager. Названия категорий вы видите на панели меню на самом верхнем уровне.

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

property ActionComponent: TComponent;

Перед вызовом onExecute это свойство содержит указатель на клиента, ини циировавшего действие. После вызова значение свойства очищается.

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

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

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

Возьмем, например, действие TFiieOpen. Оно уже содержит внутри компо нент типа TOpenDialog, показывающий список открываемых файлов. Вместо ручного программирования процедуры задания имени файла теперь нужно написать обработчик события TFiieOpen.OnAccept (если пользователь ввел в диалоге кнопку ОК) или Oncancei (если отказался от открытия файла). Вот так выглядит весь программный код приложения WordPad:

procedure TForml.FileOpenlAccept(Sender: TObject);

begin Глава 8. Действия (Actions) и связанные с ними компоненты RichEditl.Lines.LoadFromFile(FileOpenl.Dialog.FileName);

end;

procedure TForml.FileSaveAslAccept(Sender: TObject);

begin RichEditl.Lines.SaveToFile(FileSaveAsl.Dialog.FileName);

end;

I A Standard Action C s e l ss a A aa l A t n C se:

v i b ci as s le o l :-i ( o C t g r) N ae oy t;

E i dt Td u Et t iC T dC p Et o) ij T dt at E iP ie T dS l cA Ei e tI te • I T dtn o E il d :!

T dD ee Et et il ь Format ]•• TRichEdttBold ! TRichEditltalic •• TRichEditUnderline !•• !• TRichEditStiikeOut • ;

: TRichEditBullets •• TRichEditAlignLeft ;

•• •• TRichEditAlignRight !•• ^ TRichEdiWignCenter : Cancel:;

Рис. 8.4. Окно выбора стандартных действий С точки зрения программирования стандартное действие — это класс потомок TCustomAction. Классы действий описаны в трех модулях: более распространенные в stdActns, менее — в ExtActns, действия с наборами данных содержатся в DBActns. Наконец, два действия, работающие со спи сками, — TStaticListAction И TVirtualLitAction — ОПИСЯНЫ В ОТДеЛЬНОМ Модуле ListActns.

Для выполнения ряда стандартных действий нужно определить понятие "цели" действия (Action Target). Под целью понимается компонент, в отно шении которого будет совершаться данное действие. Так, операции редак тирования могут выполняться, когда на форме активен текстовый элемент управления (TEdit, TMemo И Т. П.). У любого ДеЙСТВИЯ (пОТОМКа TBasicAction) есть три метода:

function HandlesTarget(Target: TObject): Boolean;

virtual;

procedure UpdateTarget(Target: TObject);

virtual;

procedure ExecuteTarget(Target: TObject);

virtual;

182 Часть II. Интерфейс и логика приложения Метод HandiesTarget проверяет, применимо ли действие к цели Target. Если да, то действие производится вызовом метода ExecuteTarget. Если нет, по иск подходящей цели продолжается.

Цель в Delphi 7 определяется по следующему правилу:

• первым кандидатом является активный элемент управления на форме (СВОЙСТВО A c t i v e C o n t r o l ) ;

• еСЛИ такОВОГО Нет ИЛИ ОН Не ЯВЛЯеТСЯ ПОДХОДЯЩИМ (меТОД H a n d i e s T a r g e t вернул значение False), целью становится текущая форма, получившая сигнал о действии;

• если и она не подходит, происходит рекурсивный перебор всех компо нентов на форме в поисках первого подходящего.

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

( Примечание ^ Стандартные действия редактирования, чьи имена начинаются с TEdit, и поис ка (TSearch...) применимы только к потомкам компонента TCustomEdit. Стан дартные действия расширенного редактирования, имена которых начинаются с TRichEdit, применимы только к потомкам TCustomRichEdit. Оконные стан дартные действия (упорядочивание, смена, закрытие дочерних окон;

имена на чинаются с TWindow) применимы только к формам многодокументного интер фейса, чье свойство FormStyle установлено в f sMDlForm (рис. 8.4).

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

property BeforeExecute: TNotifyEvent;

property OnAccept: TNotifyEvent;

property OnCancel: TNotifyEvent;

Первое возникает до показа диалога, второе — после нажатия пользовате лем кнопки ОК, третье — после нажатия Cancel.

) ( Примечание Поскольку диалоги входят в действия в качестве дочерних компонентов, вы мо жете реагировать и на все "дочерние" события, которые происходят в соответ ствующем диалоге (OnShow, OnCanClose, OnClose И Т. П.) Поместив на форму стандартные действия, вы заметите, что все они имеют предопределенное значение свойства imageindex. Если так, то где изображе Глава 8. Действия (Actions) и связанные с ними компоненты ние, на которое эти индексы указывают? Вы можете раздобыть его, открыв демонстрационный проект WordPad (папка Demos\ActionBands в поставке Delphi 7). Откройте редактор компонента imageListl и экспортируйте весь список в виде одного большого файла формата BMP.

КатегорияEdit В эту категорию входят компоненты, которые работают с редактируемыми элементами — потомками TCustomEdit. Это, к примеру, TEdit, тмето, TMaskedEdit, TRichEdit, НОВЫЙ Компонент TLabeledEdit И Др. Причем целью может являться не любой редактируемый элемент, а только тот, что имеет фокус ВВОДа. К категории ОТНОСЯТСЯ: TEditCut, TEditCopy, TEditPaste, TEditSelectAll, TEditDelete, TEditUndo.

Категория Search Действия поиска и замены тоже производятся только над потомками TCustomEdit. И это не прихоть разработчиков Borland: это сделано для ваше го же удобства. Раньше для поиска приходилось самому программировать события onFind и onRepiace соответствующих диалогов, а сейчас требуемый код уже содержится внутри действий.

К Компонентам ЭТОЙ категории ОТНОСЯТСЯ: TSearchFind, TSearchFindFirst, TSearchFindNext, TSearchReplace.

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

Таблица 8.2. Стандартные действия категории Help Компонент Назначение THelpContents Показывает оглавление системы справки THelpOnHelp Показывает подсказку по использованию системы справки THelpContext Показывает справку по контексту активного элемента управ ления (причем он должен быть ненулевым) THelpTopicSearch Показывает окно поиска системы справки КатегорияFile Эти действия скорее всего будут наиболее востребованы разработчиками.

И ОНИ Же ЯВЛЯЮТСЯ ДОВОЛЬНО ПРОСТЫМИ В ИСПОЛЬЗОВанИИ. TFileOpen, 184 Часть II. Интерфейс и логика приложения TFiieSaveAs, TFiiePrintSetup — это оболочки над соответствующими диало гами. О том, как работать с такими действиями, описано выше. Действие TFiieExit вообще не требует комментариев — оно просто завершает прило жение, закрывая его главную форму.

ОсобНЯКОМ СТОИТ ТОЛЬКО TFiieRun!!!

Категория Dialog Эта категория примыкает к предыдущей, в ней содержатся остальные пять ТИПОВЫХ ДеЙСТВИЙ-ДИаЛОГОВ: TPrintDlg, T C o l o r S e l e c t, TFontEdit (ИЗ МОДУЛЯ StdActns), TOpenPicture, TSavePicture (модуль ExtActns).

Категория Window Эти действия стоит включать в интерфейс, только если вы используете мно годокументный интерфейс (MDI). Названия компонентов говорят сами за себя: TWindowClose, TWindowCascade, TWindowTileHorizontal, TWindowTileVertical, TWindowMinimizeAll, TWindowArrange.

Категория Tab Здесь всего два компонента — TNextTab и TPreviousTab. Если цель дейст вия — набор страниц (TPageControi), они переключат его на следующую и предыдущую страницу соответственно.

Категория List В этой категории выделяются две группы действий. Первые пять из них (табл. 8.3) автоматизируют работу с выделенными элементами списков. Ос тавшиеся два — TStaticListAction И TVirtualListAction — Требуют ОТДеЛЬ ного рассмотрения.

Таблица 8.3. Действия по работе с выделенными элементами спис Действие Назначение TListControlSelectAll Выделяет все элементы списка. Активно, только если у списка свойство MultiSelect установлено в значение True TListControlClearSelection Отменяет выделение элементов в списке TListControlDeleteSelection Удаляет выделенные элементы TListControlCopySelection Копирует выделенные элементы списка в список Destination Глава 8. Действия (Actions) и связанные с ними компоненты Таблица 8.3 (окончание) Действие Назначение TListControlMoveSelection Переносит выделенные элементы списка в спи сок D e s t i n a t i o n Действия работают с компонентом TListBox, а в среде Kylix — еще и с TListview (не путать с одноименным компонентом для Windows — он не годится для данной категории). Подходит также и тсотЬоВох.

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

ОсобнЯКОМ С О Т Два ДеЙСТВИЯ — T S t a t i c L i s t A c t i o n И T V i r t u a l L i s t A c t i o n ТЯ По замыслу разработчиков они являются централизованными хранилищами элементов для многих списков. Причем элементы списка могут храниться сразу с заданными картинками (т. е. свойствами imageindex) и указателями на сопутствующие данные.

Дальнейшее просто — разработчик выбирает нужные компоненты TListBox, тсотЬоВох и т. п. и в их свойстве Action указывает на действие — хранили ще. Опубликовано свойство Action у компонента тсотЬоВохЕх (впервые по явившегося в Delphi 6). У остальных потомков TControi это свойство отно сится к группе видимости public, поэтому вы можете сделать присвоение при запуске приложения (в методе onCreate главной формы).

Если действие и компонент-список связаны, то должны происходить две вещи:

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

• когда пользователь выбирает один из элементов списка, выполняется действие, связанное с этим списком, и вызывается метод-обработчик type TltemSelectedEvent = procedure(Sender: TCustomListAction;

Control: TControi) of object;

property OnltemSelected: TltemSelectedEvent;

Категория Internet Здесь всего три — типовых для пользователя Сети — действия.

Действие TBrowseURL открывает URL, заданный в одноименном свойстве.

ПОСКОЛЬКУ ЭТО ПРОИСХОДИТ При ПОМОЩИ фуНКЦИИ S h e l l E x e c u t e, ДЛЯ ПрО смотра открывается браузер, зарегистрированный в системе по умолчанию.

186 Часть II. Интерфейс и логика приложения Действие TSendMaii запускает программу — почтового клиента для отправки письма (с помощью интерфейса MAPI). Текст письма вы можете задать в свойстве Text. Но! Ни получателя, ни тему, ни вложений задать нельзя — это придется делать вручную в почтовой программе. При желании пол ностью автоматизировать процесс отправки вам придется породить дочер ний Компонент ОТ деЙСТВИЯ TSendMaii, Где И Перекрыть метод ExecuteTarget.

Исходные тексты — в модуле ExtActns.

Наконец, самый сложный компонент TDownioadURL. Он позволяет загрузить содержимое с адреса URL и сохранить его на локальной машине под име нем FileName.

Поскольку загрузка — процесс долгий, в то время, пока она происходит, периодически возникает событие property OnDownloadProgress: TDownloadProgressEvent;

TDownloadProgressEvent = procedure(Sender: TDownLoadURL;

Progress, ProgressMax: Cardinal;

StatusCode: TURLDownloadStatus;

StatusText:

String;

var Cancel: Boolean) of object;

Параметры обработчика этого события следующие.

• Progress и ProgressMax — текущее и максимальное значение показателя хода скачивания. Во-первых, не все HTTP-серверы правильно сообщают о размере ответа;

во-вторых, для некоторых типов файлов (например, HTML) эти параметры вычисляются не всегда верно (вы можете это ви деть в Internet Explorer);

в-третьих, из-за маршрутизации пакетов ожи дать ритмичного изменения параметра Progress не следует. Поэтому пользователю надо показывать соотношение Progress/ProgressMax.

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

CD StatusCode и StatusText — код, описывающий текущее состояние опера ции и соответствующий ему текст. Список возможных кодов содержит около 30 значений. Для тех, кто знает протокол HTTP и хочет разо браться в этом глубже, следует обратиться к описанию интерфейса iBindstatusCaiiback в MSDN. Если же вам достаточно показать пользо вателю текст, то он содержится во втором параметре. По содержанию он представляет примерно то же, что вы видите при загрузке файлов с по мощью Internet Explorer.

• cancel — этот параметр одинаков для всех долго продолжающихся опе раций. Установив его в значение True, вы можете прервать выполнение загрузки.

Глава 8. Действия (Actions) и связанные с ними компоненты Категория Format Действия этой категории представляют собой расширенные операции ре дактирования для "продвинутого" редактора TRichEdit. Эти операции долж ны быть знакомы вам по программе WordPad из состава Windows. В крайнем случае откройте демонстрационный пример Delphi с тем же названием — там присутствуют действия настоящей категории и подавляющее боль шинство остальных. В СПИСКе Присутствуют TRichEditBold, TRichEditltalic, TRichEditUnderline, TRichEditStrikeout (установка СТИЛЯ шрифта), TRichEditBullets (ЗНЭЧКИ абзацев), TRichEditAlignLeft, TRichEditAlignRight, TRichEditAiignCenter (выравнивание текста).

Категория Dataset Эти действия можно увидеть, например, в качестве кнопок на любом компоненте TDBNavigator: TDataSetFirst, TDataSetPrior, TDataSetNext, TDataSetLast, TDataSetDelete, TDataSetlnsert, TDataSetEdit, TDataSetPost, TDataSetcancel, TDataSetRefresh. Читатель задаст вопрос: а как действие свя зывается с набором данных? Очень просто: через дополнительное (для дан ной категории) свойство DataSource. Если источник данных существует и связан с имеющимся набором данных (свойство DataSource.DataSet), то дей ствие выполняется над ним.

Категория Tools Здесь содержится один-единственный член: TCustomizeActionBars. Будучи вызванным, это действие вызывает диалог настройки панелей действий, от носящихся к компоненту TActionManager, о котором, собственно, сейчас и пойдет речь.

КомпонентTActionManager Если вы не думаете о переносе своего приложения в среду Linux, то имеют ся все основания воспользоваться потомком TActionList — компонентом TActionManager (далее в тексте — менеджер действий). Более современный и "продвинутый" он обеспечит вас многими дополнительными возможностя ми. Итак, обо всем по порядку.

Будучи применен сам по себе, компонент TActionManager ничем не отлича ется от предшественника. Отличия проявляются, если действия из этого компонента разместить на специальных панелях — TActionMainMenuBar (бу дем называть его панелью главного меню) и TActionTooiBar (далее — панель действий).

На первой странице редактора TActionManager (вызывается двойным щелч ком или командой Customize из контекстного меню;

показан на рис. 8.5) Часть II. Интерфейс и логика приложения как раз и содержится список всех панелей, связанных с данным менедже ром действий. Вы можете добавить новый или убрать компонент TActionTooiBar нажатием кнопок New и Delete соответственно. С компонен том TActionMainMenuBar так по понятным причинам поступить нельзя — ме ню полагается иметь одно.

(•> E ii g F r i. ci n a a el dtn onlA to M n g r T ob r | A t n j O t n | o l as ci s pi s o o : T o t as '•:": [••'•- [ hcmr toggles vsblt ] ob t : Cek ak i i iy Й ActionToolBaii Ш ActionTool8ai Wit ;

Tppbai Options"''™ CapttonQptions/, • S l cie Jet ev j] ii: Г* Apply caption options Jo all toolbars Drag to create Separators I Close :

Рис. 8.5. Первая страница редактора свойств компонента T A c t i o n M a n a g e r Самый простой и рекомендованный Borland способ для связи действий с одной стороны и панелей меню и инструментов с другой — это перетас кивание (Drag-and-Drop). На второй странице редактора содержится список всех действий по категориям. И отдельное действие, и целую категорию можно брать и тащить мышью на нужную панель (рис. 8.6).

Когда вы перетаскиваете действие на панель, на нем появляется специаль ный компонент, похожий на пункт меню или кнопку. Его роль — служить клиентом данного действия. Поэтому, естественно, он сразу и автомати чески получает нужные caption, imageindex, Hint и прочие общие для всех клиентов свойства, о которых говорилось выше. Класс этого клиента — TActionciientitem;

будем называть их псевдокнопками или псевдоэлементами.

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

П при перетаскивании всей категории на панель главного меню она появ ляется в виде пункта меню верхнего уровня и содержит при этом все свои дочерние действия;

• при перетаскивании всей категории на панель действий создаются псев докнопки для всех дочерних действий в категории. Логично поступить по Глава 8. Действия (Actions) и связанные с ними компоненты принципу "одна категория — одна панель действий", это будет полезно для настройки интерфейса пользователем;

• если вы ошиблись при перетаскивании, не нажимайте кнопку Delete — при этом удалится не только псевдокнопка, но и само действие. Перетя ните ненужный псевдоэлемент за пределы панелей действий, тогда он будет удален;

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

I / Editing Forml.ActionManagerl J ob r A t n | O t n j :

o l as ci s pi s o o j (All A t n ) ' ci s o 3 ::

o : > & i • >,• ::

Ctg rs : Y A t n:

ae oe:i ci s o :

C+ tl O r fipen-,:

Lsit (All A t n ) ci s o ig y t e activefile wt a nw name;

':

Swh ih e ;

v o a d a t n t y u - p c t n s p da a d do fo eh r |;

T d ci s o o " a p ao m y r g n r p r1m i e o ii i l l t 'л^Categories o A t n o t a e i t g A t n a,: \ Y, r ci s no n x i ci B r o sn b : Г brag to create Separators I;

;

i Close.

Рис. 8.6. Страница действий редактора свойств компонента TActionManager Изменение и настройка внешнего вида панелей Мы ПОДОШЛИ К СОВСеМ НОВОМУ СВОЙСТВУ п а н е л е й — TActionMainMenuBar. Т е перь — как в Microsoft Office — возможно прятать редко используемые пункты меню. В самом деле, интерфейс программ подчас настолько сложен, что используют его на 100% минимальное количество пользователей. По этому элементы интерфейса, которые пользователь не задействовал в ка ком-то числе предыдущих запусков, автоматически прячутся.

Что и когда прятать, определяется свойством property Priority-Schedule: TStringList;

значение которого по умолчанию приведено в табл. 8.4. В левой колонке содержится общее количество запусков приложения, в течение которых 190 Часть II. Интерфейс и логика приложения пользователь применял данное действие;

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

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

Таблица 8.4. Условия скрытия элементов панелей действий Количество запусков приложения Количество запусков приложения с обращением к действию после последнего обращения О, 1 2 3 4,5 6-8 9-13 14-24 Более 25 Для подсчета величин, указанных в этой таблице, введены такие свойства:

• у объекта TActionBars (дочерний объект TActionManager) есть СВОЙСТВО property SessionCount: Integer;

которое представляет собой глобальный счетчик запусков приложения;

П у каждого объекта TActionCiientitem есть два свойства:

• property LastSession: Integer;

в этом свойстве хранится номер последнего запуска приложения, в течение которого использовался данный элемент (нумерация совпа дает С SessionCount);

• property UsageCount: Integer;

счетчик использования элемента.

Но для того, чтобы оперировать данными о количестве запусков, их надо где-то хранить. Организована система хранения следующим образом. У са мого менеджера действий есть свойство property FileName: TFileName;

Глава 8. Действия (Actions) и связанные с ними компоненты 191_ которое указывает на файл, содержащий все настройки панелей, связанных с данным менеджером. Он имеет формат двоичной формы и считывает ся/записывается при запуске и выходе из приложения. Впрочем, можно это сделать И В любой момент при ПОМОЩИ Методов LoadFormFile И SaveToFile.

Все эти величины меняются автоматически, и их описание приведено для понимания сути происходящего. Сбросить же счетчик статистики запусков можно следующим образом: на этапе разработки на странице Options редак тора свойств менеджера действий есть кнопка Reset Usage Count. На этапе выполнения точно такая кнопка есть в диалоге TCustomizeDig.

Помимо данных для подсчета запусков в этом файле хранится и вся прочая информация о настройках. Последний из не упоминавшихся нами компо нентов — диалог настройки TCustomizeDig. Он представляет собой точную копию редактора свойств TActionManager, но позволяет делать все операции с действиями в режиме выполнения. Вызвать его просто — вызовом метода show. А можно поступить еще проще — есть стандартное действие customize (категория Tools), которое и подразумевает вызов этого диалога.

Ручное редактирование коллекций панелей и действий Перетаскивание имеет много достоинств, однако оно не всегда удобно. По этому было бы странно, если бы не было предусмотрено другого способа.

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

Рассмотрим работу с дочерними объектами менеджера действий, которые упакованы один в другой, как матрешки.

Итак, щелкнем на свойстве ActionManager на форме и посмотрим на содер жимое Инспектора объектов. Внутри него мы обнаружим сразу две "мат решки" — свойство ActionBars содержит коллекцию ссылок на дочерние панели, свойство LinkedActionList может содержать дополнительный спи сок действий, отданных "в управление" данному менеджеру — например, для централизованной установки общих свойств.

Щелкнем на свойстве ActionBars. Появится редактор панелей (рис. 8.7), а в Инспекторе объектов обратим внимание на следующее свойство объекта ActionBars:

property Customizable: Boolean;

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

В коллекции содержатся не сами панели, а их "заместители" — объекты типа TActionBaritem, которые на них указывают. Надпись на рисунке "1-ActionBar -> ActionTooIBar2" показывает, что первый элемент коллекции 192 Часть II. Интерфейс и логика приложения связан с панелью ActionTooiBar2. Вы можете добавлять и удалять элементы этой коллекции, по мере необходимости связывая их через свойство ActionBar с уже существующей панелью.

J i Editing ActionManagerl.ActionBars Щ L3 Йа 0 -ActionBaf ->ActionToolBar 1 - ActionBar->ActionToolBar2_ 2 -ActionBar ->ActionToolBar3' Рис. 8.7. Редактор коллекции панелей компонента T A c t i o n M a n a g e r Через Инспектор объектов вы можете изменять внешний вид объектов типа TActionBaritem и соответствующих им панелей.

Свойство property Color: TColor;

отвечает за фоновый цвет панели. Если вам изменения цвета недостаточно, в качестве фона выберите картинку property Background: Т.Picture;

которая будет расположена на панели в соответствии со значением свойства property BackgroundLayout: TBackgroundLayout;

TBackgroundLayout = ( l o r a, blStretch, blTile, blLeftBanner, bNral blRightBanner);

Помимо внешнего вида можно разрешить/запретить перетаскивание пане лей и их дочерних элементов. Обратимся к свойству property ChangesAllowed: TChangesAllowedSet;

TChangesAllowed = (caModify, caMove, caDelete);

TChangesAllowedSet = set of TChangesAllowed;

Множество из трех возможных значений позволяет запретить те или иные нежелательные изменения для дочерних элементов панели. Если в него не включен режим caDelete, то элемент нельзя убирать (перетаскивать) с пане ли. Если нет режима caMove — нельзя передвигать внутри панели. Наконец, отсутствие режима caModify означает запрет на изменение визуальных свойств (заголовка и т. п.).

Внутри коллекции TActionBaritem спрятаны еще две "матрешки" — свойства items и contextitems. Оба свойства представляют из себя коллекции объек Глава 8. Действия (Actions) и связанные с ними компоненты тов, указывающих на действия (класс коллекции TActionCiients, класс эле мента коллекции TActionCiientitem). Первое свойство указывает непосред ственно на дочерние действия, второе — на действия, которые будут пока заны в качестве всплывающего меню при нажатии правой кнопки мыши.

У коллекции TActionCiients есть заслуживающие особого упоминания свой ства.

Свойство property CaptionOptions: TCaptionOptions;

TCaptionOptions = (coNone, coSelective, coAll);

задает показ/отсутствие заголовков дочерних действий. В случае установки в coNone они не показываются, coAii — показываются все, coSelective — показываются в соответствии со значением showcaption дочернего объекта TActionCiientitem. Это свойство можно также установить на первой страни це редактора менеджера действий в одноименном выпадающем списке.

Свойство property Smalllcons: Boolean;

указывает размер значков, соответствующих действиям. По умолчанию ус тановлено в значение True (маленькие значки). Визуально оно доступно че рез тот же редактор — третья страница, флажок Large Icons.

Свойство property HideUnused: Boolean;

разрешает скрытие редко используемых действий, описанное в предыдущем разделе. Если вы не хотите пользоваться механизмом скрытия, на третьей странице редактора менеджера действий и диалога TCustomizeDig есть фла жок Menu show recent items first. Сбросьте его, и свойства HideUnused у кли ентов действий установятся в значение False.

И, наконец, коллекцию можно сделать нередактируемой. Для этого у нее есть СВОЙСТВО Customizable.

Ну вот, мы уже добрались до самой маленькой матрешки — TActionCiientitem.

Этот объект связывается напрямую с одним действием через свойство Action. Правда в него можно спрятать еще меньшую матрешку — у него также есть свойства items и Context items. Эти свойства используются при организации многоуровневых меню и меню, выпадающих из кнопок (точ нее, псевдокнопок — напомним, объекты TActionCiientitem на панелях не являются ни кнопками, ни компонентами вообще).

7 Зак. 194 Часть II. Интерфейс и логика приложения Резюме Хорошо знакомые со времен Delphi 1 составляющие интерфейса — меню (TMainMenu, TPopupMenu), КНОПКИ (TButton, TSpeedButton), панели TPanel — постепенно уходят, уступая место компонентам с расширенной функцио нальностью. Центральным местом, где обрабатывается весь ввод пользова теля, Становится Хранилище ДеЙСТВИЙ — TActionList И И TAcrionManager.

Л В этой главе мы подробно рассмотрели оба компонента;

читателю решать, на базе чего строить свой интерфейс.

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

ГЛАВА Файлы и устройства ввода/вывода Большинство приложений создаются для того, чтобы обрабатывать дан ные — это прописная истина. С развитием программных технологий мы имеем возможность получать и посылать все более крупные и сложные мас сивы данных;

однако до сих пор 90% из них хранятся в файлах.

Для использования файлов в приложении разработчику приходится решать множество задач. Главные из них — поиск необходимого файла и выполне ние с ним операций ввода/вывода.

Основные принципы и структура файловой системы мало изменились со времен MS-DOS. Файловые системы (FAT32, NTFS), появившаяся в Windows 2000 служба Active Directory не изменяют главного — понятия файла и способов обращения к нему.

Среда Delphi дает вам возможность выбрать один из четырех вариантов ра боты:

• использование традиционного набора функций работы с файлами, унас ледованного от Turbo Pascal;

• использование функций ввода/вывода из Windows API;

• использование потоков (Tstream и его потомки);

О использование отображаемых файлов.

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

Использование файловых переменных.

Типы файлов Зачастую современный программный код Delphi для чтения данных из фай ла удивительно похож на аналогичный, написанный, к примеру, в Turbo 196 Часть II. Интерфейс и логика приложения Pascal 4.O. Это возможно потому, что программисты Borland сохранили не изменным "старый добрый" набор файловых функций, работающих через файловые переменные.

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

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

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

В Delphi имеется возможность создавать нетипизированные файлы. Для их обозначения используется ключевое слово file:

var UntypedFile: file;

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

Типизированные файлы обеспечивают ввод/вывод с учетом конкретного типа данных. Для их объявления используется ключевое слово file of, к кото рому добавляется конкретный тип данных. Например, для работы с файлом, содержащим набор байтов, файловая переменная объявляется так:

var ByteFile: file of byte;

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

type Country = record Name: String;

Capital: String;

Population: Longlnt;

Square: Longlnt;

end;

var CountryFile: file of Country;

Глава 9. Файлы и устройства ввода/вывода Для работы с текстовыми файлами используется специальная файловая пе ременная TextFile И И Text:

Л var F: TextFile;

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

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

1. Объявить файловую переменную необходимого типа.

2. При помощи функции AssignFile связать эту переменную с требуемым файлом.

3. Открыть файл при П М Щ функций Append, Reset, Rewrite.

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

5. Закрыть файл при помощи функции cioseFiie.

Внимание!

По сравнению с Turbo Pascal изменились названия только двух функций:

Assign стала AssignFile, a Close превратилась В CioseFiie.

В качестве примера рассмотрим небольшой фрагмент исходного кода.

var F: TextFile;

S: string;

begin if OpenDlg.Execute then AssignFile(F, OpenDlg.FileName) else Exit;

Reset(F);

while Not EOF(F) do begin Readln(F, S);

Memo.Lines.Add(S) ;

end;

CioseFiie(F);

end;

198 Часть II. Интерфейс и логика приложения Если в диалоге открытия файла openDig был выбран файл, то его имя свя зывается с файловой переменной F при помощи процедуры AssignFiie.

В качестве имени файла рекомендуется всегда передавать полное имя файла (включая его маршрут). Как раз в таком виде возвращают результат выбора файла диалоги работы с файлами TOpenDialog, TOpenPictureDiaiog. Затем при помощи процедуры Reset этот файл открывается для чтения и записи.

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

После завершения чтения файл закрывается.

Такой же исходный код можно использовать и для записи данных в файл.

Необходимо только заменить процедуру чтения на процедуру записи.

Теперь остановимся подробнее на назначении используемых для файлового ввода/вывода функций.

Открытие файла может осуществляться тремя процедурами — в зависимо сти от типа его дальнейшего использования.

Процедура procedure Reset(var F: File [;

RecSize: Word ]);

открывает существующий файл для чтения и записи, текущая позиция уста навливается на первой строке файла.

Процедура procedure Append(var F: Text);

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

Процедура procedure Rewrite(var F: File [;

RecSize: Word ]);

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

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

Если этот параметр опущен, то по умолчанию RecSize равно 128 байт.

Чтение данных из типизированных и текстовых файлов выполняют про цедуры Read И Readin.

Глава 9. Файлы и устройства ввода/вывода Процедура Read имеет различное объявление для текстовых и других типи зированных файлов:

• procedure Read([var F: Text;

] VI [, V2,...,Vn]);

для текстовых файлов;

• procedure Read(F, VI [, V2,...,Vn]);

для других типизированных файлов.

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

Процедура procedure Readln([ var F: Text;

] VI [, V2,...,Vn ]);

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

Процедуры для записи в файл write и writein описаны аналогично:

procedure Write([var F: Text;

] PI [, P2,..., Pn] ) ;

procedure Writein([ var F: Text;

] PI [, P2,...,Pn ]);

Параметры PI, P2,..., Pn могут быть одним из целых или вещественных ти пов, одним из строковых типов или логическим типом. Но у них есть воз можность дополнительного форматирования при выводе. Каждый параметр записи может иметь форму:

Рп [: MinWidth [: DecPlaces ] ] Pn — выводимая переменная или выражение;

Minwidth — минимальная ширина поля в символах, которая должна быть больше 0;

DecPlaces — содержит количество десятичных символов после запятой при отображении вещественных чисел с фиксированной точкой.

Обратите внимание, что для текстовых файлов в функциях Read и Write файловая переменная F может быть опущена. В этом случае чтение и запись осуществляются в стандартные файлы ввода/вывода. Когда программа ком пилируется как консольное приложение (флаг {$APPTYPE CONSOLE}), Delphi автоматически связывает входной и выходной файлы с окном консоли.

200 Часть II. Интерфейс и логика приложения Для контроля за текущей позицией в файле применяются две основные функции. Функция EOF(F) возвращает значение True, если достигнут конец файла. Функция EOLN(F) аналогично сигнализирует о достижении конца строки. Естественно, в качестве параметра в функции необходимо переда вать файловую переменную.

Процедура procedure Seek(var F;

N: Longint);

обеспечивает смещение текущей позиции на N элементов. Размер одного элемента в байтах зависит от типа данных файла (от типизированной пере менной).

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

Для реализации этого режима необходимо использовать только нетипизиро ванные файловые переменные. Размер блока определяется в процедуре от крытия файла (Reset, Rewrite). Непосредственно для выполнения операций используются процедуры BlockRead И BlockWrite.

Процедура procedure BlockRead(var F: File;

var Buf;

Count: Integer [;

var AmtTransferred: Integer]);

выполняет запись блока из файла в буфер. Параметр F ссылается на нети пизированную файловую переменную, связанную с нужным файлом.

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

При использовании блочного чтения или записи размер блока необходимо выбирать таким образом, чтобы он был кратен размеру одного значения того типа, который хранится в файле. Например, если в файле хранятся значения типа Double (8 байт), то размер блока может быть равен 8, 16, 24, 32 и т. д. Фрагмент исходного кода блочного чтения при этом выглядит следующим образом:

var F: File;

DoubleArray: array [0..255] of Double;

Transfered: Integer;

Глава 9. Файлы и устройства ввода/вывода begin if OpenDlg.Execute then AssignFile(F, OpenDlg.FileName) else Exit;

Reset(F, 64);

BlockRead(F, DoubleArray, 32, Transferee!);

CloseFile(F);

ShowMessage('Считано '+IntToStr(Transferee!)+' блоков');

end;

Как видно из примера, размер блока установлен в процедуре Reset и кратен размеру элемента массива DoubleArray, в который считываются данные.

В переменной Transferee! возвращается число считанных блоков. Если раз мер файла меньше заданного в процедуре BiockRead числа блоков, ошибка не возникает, а в переменной Transfered передается число реально считан ных блоков.

Процедура procedure BlockWrite(var f: File;

var Buf;

Count: Integer [;

var AmtTransferred: Integer]);

используется аналогично.

Оставшиеся функции ввода/вывода, работающие с файловыми переменны ми, подробно описаны в табл. 9.1.

Таблица 9.1. Процедуры и функции для работы с файлом Объявление Описание function ChangeFileExt(const Функция позволяет изменить расширение FileName, Extension: string) : файла. При этом сам файл не переименовы string;

вается procedure ChDir(S: s t r i n g ) ;

Процедура изменяет текущий каталог на дру гой, путь к которому описан в строке s procedure CloseFile(var F);

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

Имя этой процедуры изменено из-за кон фликта имен в Delphi (в Borland Pascal ис пользуется процедура Close) function DeleteFile (const Функция производит удаление файла FileName FileName: string) : Boolean;

c диска и возвращает значение False, если файл удалить не удалось или файл не суще ствует Часть II. Интерфейс и логика приложения Таблица 9.1 (продолжение) Описание Объявление Функция возвращает расширение файла function ExtractFileExt(const FileName: string): string;

Извлекает имя и расширение файла, содер function ExtractFileName(const FileName: string): string;

жащегося в параметре FileName Функция возвращает полный путь к файлу function ExtractFilePath(const FileName: string): string;

Удаляет файл, связанный с файловой пере procedure Erase(var F);

менной F Данная процедура производит поиск в ката function FileSearch(const логах D i r L i s t файла Name. Если в процессе Name, DirList: string):

string;

выполнения FileSearch обнаруживается ис комое имя файла, то функция возвращает в строке типа S t r i n g полный путь к найден ному файлу. Если файл не найден, то воз вращается пустая строка function FileSetAttr(const Присваивает файлу с именем FileName атри FileName: string;

Attr: буты A t t r. Функция возвращает 0, если при Integer): Integer;

своение атрибутов прошло успешно. В про тивном случае возвращается код ошибки Возвращает текущую позицию файла. Функ function FilePos(var F):

ция используется для нетекстовых файлов.

Longint;

Перед вызовом FilePos файл должен быть открыт F i l e S i z e возвращает размер файла в байтах function FileSize(var F):

или количество записей в файле, содержа.Integer;

щем записи. Перед вызовом данной функции файл должен быть открыт.

Для текстовых файлов функция F i l e S i z e не используется Процедура очищает буфер текстового файла, procedure Flush(var F: Text), открытого для записи. F— файловая пере менная.

Когда текстовый файл открыт для записи с использованием функции Rewrite или Append, Flush очищает выходной буфер, свя занный с файлом. После выполнения данной процедуры все символы, которые направлены для записи в файл, будут гарантированно записаны в нем Глава 9. Файлы и устройства ввода/вывода Таблица 9.1 (продолжение) Объявление Описание procedure GetDir(D: Byte;

Возвращает число, соответствующее диску, var S: string);

на котором содержится текущий каталог S.

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

• 0 — по умолчанию (текущий);

. 1-А;

. 2-В;

• 3-С и т. д.

Процедура не генерирует код ошибки. Если имя диска в D оказывается ошибочным, то в строке s возвращается значение Х:\, как если бы текущая папка была на этом ошибочно указанном диске Функция возвращает статус последней про function IOResult: Integer;

изведенной операции ввода/вывода, если контроль ошибок выключен { $ ! - } Процедура создает новый каталог, который procedure MkDir(S: string) описывается в строке s Процедура изменяет имя файла, связанного procedure Rename(var F;

с файловой переменной F. Переменная NewName: string);

NewName является строкой типа s t r i n g или PChar (если включена поддержка расширен ного синтаксиса) Процедура удаляет пустой каталог, путь к procedure RmDir(S: string);

которому задается в строке s. Если указан ный каталог не существует или он не пустой, то возникает сообщение об ошибке вво да/вывода Перемещает текущую позицию курсора на N procedure Seek(var F;

N: Longint) ;

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

Началу файла соответствует нулевой номер позиции.

Для добавления новой информации в конец существующего файла необходимо устано вить указатель на символ, следующий за по следним. Для этого можно использовать вы ражение Seek(F, F i l e S i z e ( F ) ) Часть //. Интерфейс и логика приложения Таблица 9.1 (окончание) Описание Объявление function SeekEof[(var F: Возвращает значение True, если указатель Text)]: Boolean;

текущей позиции находится на символе кон ца файла.

SeekEof может быть использован только с открытым текстовым файлом function SeekEoln[(var F: Возвращает значение True, если указатель Text)]: Boolean;

текущей позиции находится на символе кон ца строки.

SeekEoln может быть использован только с открытым текстовым файлом procedure SetTextBuf(var F: Связывает с текстовым файлом буфер вво Text;

var Buf [;

Size: да/вывода. F— файловая переменная тексто Integer]);

вого типа.

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

SetTextBuf позволяет помещать в текстовый файл F информацию об операциях вво да/вывода вместо ее размещения в буфере.

size указывает размер буфера в байтах. Ес ли этот параметр опускается, то полагается размер, равный SizeOf(Buf). Новый буфер действует до тех пор, пока F не будет связана с новым файлом процедурой A s s i g n F i l e procedure Truncate(var F);

Удаляет все позиции, следующие после те кущей позиции в файле. А текущая позиция становится концом файла.

С переменной F может быть связан файл лю бого типа за исключением текстового Ввод/вывод с использованием функций Windows API Для тех, кто переходит на Delphi не с прежних версий Turbo Pascal, а с С, других языков или начинает освоение с "нуля", более привычными будут стандартные функции работы с файлами Windows. Тем более, что возмож Глава 9. Файлы и устройства ввода/вывода ности ввода/вывода в них расширены. Каждый файл в этом наборе функ ций описывается не переменной, а дескриптором (Handle) — 32-разрядной величиной, которая идентифицирует файл в операционной системе.

В Win32 файл открывается при помощи функции, имеющей обманчивое название:

function CreateFile(lpFileName: PChar;

dwDesiredAccess, dwShareMode: DWORD;

lpSecurityAttributes: PSecurityAttributes;

dwCreationDistribution, dwFlagsAndAttributes: DWORD;

hTemplateFile: THandle): THandle;

Хоть ее название и начинается с Create, но она позволяет не только созда вать, но и открывать уже существующие файлы.

Такое огромное количество параметров оправдано, т. к. CreateFile исполь зуется для открытия файлов на диске, устройств, каналов, портов и вообще любых источников ввода/вывода. Назначение параметров описано в табл. 9.2.

Таблица 9.2. Параметры функции CreateFile Параметр Описание lpFileName: pChar Имя открываемого объекта. Может представ лять собой традиционную строку с путем и именем файла, UNC (для открытия объектов в сети, имя порта, драйвера или устройства) Способ доступа к объекту. Может быть равен:

dwDesiredAccess,: D O D WR • GENERIC_READ — ДЛЯ ЧТвНИЯ;

• GENERIC_WRITE — ДЛЯ ЗЭПИСИ.

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

dwShareMode: D O D WR • 0 — совместный доступ запрещен;

• FILE_SHARE_READ — ДЛЯ ЧТвНИЯ;

• FILE_SHARE_WRITE — ДЛЯ ЗЭПИСИ.

Их комбинация — для полного совместного доступа Атрибуты защиты файла. В Windows 95/98 не lpSecurityAttributes:

PSecurityAttributes используются (должны быть равны n i l ).

В Windows NT/2000 этот параметр, равный n i l, дает объекту атрибуты по умолчанию 206 Часть II. Интерфейс и логика приложения Таблица 9.2 (окончание) Описание Параметр Способ открытия файла:

dwCreationDistribution: DWORD;

• C R E A T E _ N E W — создается новый файл, если таковой уже существует, функция возвра щает ОШИбку ERROR_ALREADY_EXISTS;

• CREATE_ALWAYS — создается новый файл, если таковой уже существует, он переза писывается;

• OPEN_EXISTING — открывает существую щий файл, если таковой не найден, функ ция возвращает ошибку;

• OPEN_ALWAYS — открывает существующий файл, если таковой не найден, он созда ется Набор атрибутов (скрытый, системный, сжа dwFlagsAndAttributes: DWORD;

тый) и флагов для открытия объекта. Подроб ное описание см. в документации по Win Файл-шаблон, атрибуты которого используют hTemplateFile: THandle ся для открытия. В Windows 95/98 не исполь зуется и должен быть равен О Функция createFile возвращает дескриптор открытого объекта ввода/вы вода. Если открытие невозможно из-за ошибок, возвращается код INVALID_HANDLE_VALUE, а расширенный код ошибки можно узнать, вызвав ФУНКЦИЮ GetLastError.

Закрывается файл в Win32 функцией closeHandie (не cioseFiie, a cioseHandle! Правда, "легко" запомнить? Что поделать, так их назвали раз работчики Win32).

Приведем из большого разнообразия несколько приемов использования функции CreateFile. Часто программисты хотят иметь возможность органи зовать посекторный доступ к физическим устройствам хранения — напри мер к дискете. Сделать это не так уж сложно, но при этом методы для Windows 98 и Windows 2000 различаются. В Windows 2000 придется откры вать устройство ('\\.\A:'), а в Windows 98 — специальный драйвер доступа (обозначается '\\.\vwin32'). И то и другое делается функцией CreateFile.

Листинг 9.1. Чтение сектора с дискеты при помощи функции C r e a t e F i l e type pDIOCRegs = ATDIOCRegs;

Глава 9. Файлы и устройства ввода/вывода TDIOCRegs = packed record rEBX,rEDX,rECX,rEAX,rEDI, rESI, rFlags : DWORD;

end;

const VWIN32_DIOC_DOS_IOCTL = 1;

VWIN32_DIOC_DOS_INT13 = 4 ;

// Прерьшание SectorSize = 512;

function ReadSector(Head, Track, Sector: Integer;

buffer : pointer;

Floppy: char):Boolean;

var hDevice : THandle;

Regs : TDIOCRegs;

DevName : string;

nb : Integer;

begin if WIN32PLATFORM О VER_PLATFORM_WIN32_NT then begin (win95/98} hDevice := CreateFile('\\.\vwin32', GENERIC_READ, 0, nil, 0, FILE_FLAG_DELETE_ON_CLOSE, 0);

if (hDevice = INVALID_HANDLE_VALUE) then begin Result := FALSE;

Exit;

end;

regs.rEDX := Head * $100 + Ord(Floppy in ['b1, 'B']);

regs.rEAX := $201;

// код операции read sector regs.rEBX := DWORD(buffer);

// buffer regs.rECX := Track * $100 + Sector;

regs.rFlags := $0;

Result := DeviceloControl(hDevice,VWIN32_DIOC_DOS_INT13, Oregs, sizeof(regs), @regs, sizeof(regs), nb, nil) and ((regs.rFlags and $l)=0);

CloseHandle(hDevice) ;

end {win95/98} else begin // Windows NT/ DevName :='\\.\A:';

if Floppy in ['b', 'B'] then DevName[5] := Floppy;

hDevice := CreateFile(pChar(Devnarae), GENERIC_READ, FILE_SHARE_READ or FILE_SHARE_WRITE, nil, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);

if (hDevice = INVALID_HANDLE_VALUE) then begin Result := FALSE;

208 Часть II. Интерфейс и логика приложения Exit;

end;

SetFilePointer(hDevice, (Sector-1)*SectorSize, nil, FILE_BEGIN);

// нумерация с Result := ReadFile{hDevice, buffer^, SectorSize, nb, nil) and (nb=SectorSize);

CloseHandle(hDevice);

end;

// Windows NT/ end;

Для чтения и записи данных в Win32 используются функции:

function ReadFile(hFile: THandle;

var Buffer;

nNumberOfBytesToRead:

DWORD;

var lpNumberOfBytesRead: DWORD;

lpOverlapped: POverlapped): BOOL;

function WriteFile(hFile: THandle;

const Buffer;

nNumberOfBytesToWrite:

DWORD;

var lpNumberOfBytesWritten: DWORD;

lpOverlapped: POverlapped):

BOOL;

Здесь все сходно с BiockRead и BiockWrite: hFile — это дескриптор файла, Buffer — адрес, по которому будут читаться (писаться) данные;

третий па раметр означает требуемое число читаемых (записываемых) байтов, а чет вертый — фактически прочитанное (записанное). Последний параметр — lpOverlapped — обсудим чуть позже.

Функция createFile используется и для доступа к портам ввода/вывода.

Часто программисты сталкиваются с задачей: как организовать обмен дан ными с различными нестандартными устройствами, подключенными к па раллельному или последовательному порту? В Turbo Pascal для DOS был очень хороший псевдомассив Ports: пишешь Port[X] := у;

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

hCom := CreateFile('COM2', GENERIC_READ or GENERIC_WRITE, 0, NIL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, 0);

if hCom = INVALID_HANDLE_VALUE then begin raise EAbort.CreateFmt ('Ошибка открытия порта: %d', [GetLastError]);

end;

Самое большое отличие от предыдущего примера — в скромном флаге FILE_FLAG_OVERLAPPED. О роли этих изменений — в следующем разделе.

Отложенный (асинхронный) ввод/вывод Эта принципиально новая возможность введена впервые в Win32 с появле нием реальной многозадачности. Вызывая функции чтения и записи дан ных, вы на самом деле передаете исходные данные одному из потоков Глава 9. Файлы и устройства ввода/вывода (threads) операционной системы, который и осуществляет фактические обя занности по работе с устройством. Время доступа всех периферийных уст ройств гораздо больше доступа к ОЗУ, и ваша программа, вызвавшая Read или write, будет дожидаться окончания операции ввода/вывода. Замедление работы программы налицо.

Выход был найден в использовании отложенного (overlapped) ввода/вывода.

До начата отложенного ввода/вывода инициализируется дескриптор объекта типа события (функция createEvent) и структура типа TOveriapped. Вы вы зываете функцию ReadFile или writeFiie, в которой последним параметром указываете на TOveriapped. Эта структура содержит дескриптор события Windows (event).

ОС начинает операцию (ее выполняет отдельный программный поток, скрытый от программиста) и немедленно возвращает управление;

вы можете не тратить время на ожидание. Признак того, что операция началась и про должается — получение кода возврата ERROR_IO_PENDING. Пусть вас не пугает слово "error" в названии — это совершенно нормально. Если операция про должается долго (а чтение и запись файлов на дискете, да и на диске, име нованных каналов можно отнести к "длинным" операциям), то программа может спокойно выполнять последующие операторы. Событие будет "взве дено" ОС тогда, когда ввод/вывод закончится.

Когда, по мнению программиста, ввод/вывод должен быть завершен, можно проверить ЭТО, ИСПОЛЬЗОВав функцию WaitForSingleObject.

function WaitForSingleObject(hHandle: THandle;

dwMilliseconds: DWORD):

DWORD;

Объект ожидания (параметр hHandle) в этом случае — тот самый, который создан нами, указан в структуре TOveriapped и передан в качестве параметра в функцию ReadFile или WriteFiie. Можно указать любое время ожидания, в том числе бесконечное (параметр Timeout при этом равен константе INFINITE). Признаком нормального завершения служит получение кода воз врата WAIT_OBJECT_0.

j Листинг 9.2, Пример отложенной операции чтения function TMyClass.Read(var Buffer;

Count: Longint): Longint;

var succ : boolean;

nb : Cardinal;

LastError : Longint;

Overlap: TOveriapped;

begin FillChar(Overlap,SizeOf(Overlap),0);

Overlap.hEvent := CreateEvent(nil, True, False, nil);

Result := Maxlnt;

succ := ReadFile(FHandle, Buffer, Count, nb, SOverlapRd);

210 Часть II. Интерфейс и логика приложения II II Здесь можно вставить любые операторы, которые // могут быть выполнены до окончания ввода/вывода // if not succ then begin LastError := GetLastError;

if LastError = ERROR_IO_PENDING then begin if WaitForSingleObject(OverlapRd.hEvent, INFINITE)=WAIT_OBJECT_ then GetOverlappedResult(FHandle, OverlapRd, nb, TRUE);

end else raise EAbort.Create(Format('Read failed, error %d',[LastError]));

end;

Result := nb;

CloseHandle(hEvent);

end;

Если вы задали конечный интервал в миллисекундах, а операция еще не закончена, WaitForSingleObject вернет код завершения WAIT_TIMEOUT. Функ ция GetOverlappedResult возвращает в параметре nb число байтов, действи тельно прочитанных или записанных во время отложенной операции.

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

Контроль за ошибками ввода/вывода зависит от применяемых функций.

При использовании доступа через Win32 API все функции возвращают код ошибки Windows, который и нужно проанализировать.

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

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

П {$!+) — контроль включен (установлен по умолчанию);

• {$!-}—контроль отключен.

Глава 9. Файлы и устройства ввода/вывода Класс EinOutError отличается тем, что у него есть поле ErrorCode. При воз никновении этой исключительной ситуации вы можете получить его значе ние и принять решение. Основные коды имеют такие значения:

О 2- файл не найден;

• 3 — неверное имя файла;

• 4 — слишком много открытых файлов;

• 5 — доступ запрещен;

• 100 — достигнут конец файла;

• 101 — диск переполнен;

• 106 — ошибка ввода.

При отключенном контроле в случае возникновения ошибки выполнение программы продолжается без остановки. Однако в этом случае устранение возможных последствий ошибки возлагается на разработчика. Для этого применяется функция function IOResult: Integer;

которая возвращает значение 0 при отсутствии ошибок.

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

Запись type TFileName = string;

TSearchRec = record Time: Integer;

{Время и дата создания} Size: Integer;

{Размер файла} Attr: Integer;

{Параметры файла} Name: TFileName;

{Полное имя файла} ExcludeAttr: Integer;

{He используется} FindHandle: THandle;

{Дескриптор файла} FindData: TWin32FindData;

{He используется} end;

обеспечивает хранение характеристик файла после удачного поиска. Дата и время создания файла хранятся в формате MS-DOS, поэтому для получения 212 Часть II. Интерфейс и логика приложения этих параметров в принятом в Delphi формате TDateTime необходимо ис пользовать следующую функцию:

function FileDateToDateTime(FileDate: Integer): TDateTime;

Обратное преобразование выполняет функция function DateTimeToFileDate(DateTime: TDateTime): Integer;

Свойство Attr может содержать комбинацию следующих флагов-значений:

• faReadOniy — только для чтения;

П faDirectory — каталог;

• faHidden — скрытый;

• faArchive — архивный;

• faSysFile — Системный;

• faAnyFile — Любой.

П faVoiumeio — метка тома;

Для определения параметров файла используется оператор AND:

if (SearchRec.Attr AND faReadOniy) > then ShowMessage('Файл только для чтения');

Непосредственно для поиска файлов используются функции FindFirst И FindNext.

Функция function FindFirst(const Path: string;

Attr: Integer;

var F: TSearchRec):

Integer;

находит первый файл, заданный полным маршрутом Path и параметрами Attr (см. выше). Если заданный файл найден, функция возвращает 0, ина че — код ошибки Windows. Параметры найденного файла возвращаются В записи F типа TSearchRec.

Функция function FindNext(var F: TSearchRec): Integer;

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

Для освобождения ресурсов, выделенных для выполнения поиска, применя ется функция:

procedure FindClose(var F: TSearchRec);

В качестве примера организации поиска файлов рассмотрим фрагмент ис ходного кода, в котором маршрут поиска файлов задается в однострочном текстовом редакторе DirEdit, а список найденных файлов передается в ком понент TListBox.

Глава 9. Файлы и устройства ввода/вывода procedure TForml.FindBtnClick(Sender: TObject);

begin ListBox.Items.Clear;

FindFirst(DirEdit.Text, faArchive + faHidden, SearchRec);

while FindNext(SearchRec) = 0 do ListBox.Items.Add(SearchRec.Name);

FindClose(SearchRec);

end;

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

Многие Классы VCL И е Т унифицированные МеТОДЫ LoadFromStream И МЮ saveTostream, которые обеспечивают обмен данными с потоками. От того, с каким физическим носителем работает поток, зависит место хранения данных.

Базовые классы TStream и THandleStream В основе иерархии классов потоков лежит класс Tstream. Он обеспечивает выполнение основных операций потока безотносительно к реальному носи телю информации. Основными из них являются чтение и запись данных.

Класс Tstream порожден непосредственно от класса TObject.

Потоки также играют важную роль в чтении/записи компонентов из файлов ресурсов (DFM). Большая группа методов обеспечивает взаимодействие компонента и потока, чтение свойств компонента из ресурса и запись зна чений свойств в ресурс.

Таблица 9.3. Свойства и методы класса Tstream Объявление Описание property Position: Longint;

Определяет текущую позицию в потоке property Size: Longint;

Определяет размер потока в байтах 214 Часть II. Интерфейс и логика приложения Таблица 9.3 (окончание) Описание Объявление Копирует из потока Source Count бай function CopyFrom(Source: TStream;

ты, начиная с текущей позиции. Воз Count: Longint): Longint;

вращает число скопированных байтов Абстрактный класс, перекрываемый в function Read(var Buffer;

Count:

наследниках. Считывает из потока Longint): Longint;

virtual;

Count байты в буфер Buffer. Возвра abstract;

щает число скопированных байтов Считывает из потока Count байты в бу procedure ReadBuffer(var Buffer;

фер Buffer. Возвращает число скопи Count: Longint);

рованных байтов Абстрактный класс, перекрываемый в function Seek(Offset: Longint;

наследниках. Смещает текущую пози Origin: Word): Longint;

virtual;

цию в реальном носителе данных на abstract;

Offset байтов в зависимости от усло вия Origin (см. ниже) function Write(const Buffer;

Абстрактный класс, перекрываемый в Count: Longint): Longint;

наследниках. Записывает в поток Count virtual;

abstract;

байты из буфера Buffer. Возвращает число скопированных байтов procedure WriteBuffer(const Buffer;

Записывает в поток Count байты из бу Count: Longint);

фера Buffer. Возвращает число скопи рованных байтов function ReadComponent(Instance: Передает данные из потока в компонент TComponent): TComponent;

Instance, заполняя его свойства зна чениями function ReadComponentRes(Instance: Считывает заголовок ресурса компо TComponent): TComponent;

нента instance и значения его свойств из потока.

procedure ReadResHeader;

Считывает заголовок ресурса компо нента из потока procedure WriteComponent(Instance: Передает в поток значения свойств ком TComponent);

понента Instance procedure WriteComponentRes(const Записывает в поток заголовок ресурса ResName: string;

Instance: компонента instance и значения его TComponent);

свойств Итак, в основе операций считывания и записи данных в потоке лежат методы Read и Write. Именно они вызываются для реального выполне ния операции внутри методов ReadBuffer И WriteBuffer, ReadComponent И Глава 9. Файлы и устройства ввода/вывода writeComponent. Так как класс TStream является абстрактным, то методы Read и write также являются абстрактными. В классах-наследниках они перекры ваются, обеспечивая работу с конкретным физическим носителем данных.

Метод seek используется для изменения текущей позиции в потоке. "Точка отсчета" позиции зависит от значения параметра origin:

• soFromBeginning — смещение должно быть положительным и отсчитыва ется от начала потока;

• soFromCurrent — смещение относительно текущей позиции в потоке;

• soFromEnd — смещение должно быть отрицательным и отсчитывается от конца потока.

Группа методов обеспечивает чтение и запись из потока ресурса компонен та. Они используются при создании компонента на основе данных о нем, сохраненных в формате файлов ресурсов. Для чтения ресурса используется метод ReadComponentRes, в котором последовательно вызываются:

• метод ReadResHeader — для считывания заголовка ресурса компонента из потока;

• метод ReadComponent — для считывания значений свойств компонента.

Для записи ресурса в поток применяется метод writeComponentRes.

Класс THandiestream инкапсулирует поток, связанный с физическим носите лем данных через дескриптор.

Для создания потока используется конструктор constructor Create(AHandle: Integer);

в параметре которого передается дескриптор. Впоследствии доступ к деск риптору осуществляется через свойство:

property Handle: Integer;

Класс TFileStream Класс TFileStream позволяет создать поток для работы с файлами. При этом поток работает с файлом без учета типа хранящихся в нем данных (см.

выше).

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

constructor Create(const FileName: string;

Mode: Word);

Параметр Mode определяет режим работы с файлом. Он составляется из фла гов режима открытия:

П fmCreate — файл создается;

П fmOpenRead — файл открывается для чтения;

216 Часть II. Интерфейс и логика приложения • fmopenwrite — файл открывается для записи;

• fmOpenReadWrite — файл открывается для чтения и записи.

И флагов режима совместного использования:

• fmShareExciusive — файл недоступен для открытия другими приложе ниями;

• fmshareDenyWrite — другие приложения могут читать данные из файла;

• fmShareDenyRead — другие приложения могут писать данные в файл;

• fmShareDenyNone — другие приложения могут производить с файлом лю бые операции.

Для чтения и записи из потока используются методы Read и write, унасле дованные ОТ класса THandleStream:

procedure TForml.CopyBtnClick(Sender: TObject);

var Streaml, Stream2: TFileStream;

IntBuf: array[0..9] of Integer;

begin if Not OpenDlg.Execute then Exit;

try Streaml := TFileStream.Create(OpenDlg.FileName, fmOpenRead);

Streaml.ReadBuffer(IntBuf, SizeOf(IntBuf));

try Stream2 := TFileStream.Create('TextFile.tmp', fmOpenWrite);

Stream2.Seek(0, soFromEnd);

Stream2.WriteBuffer(IntBuf, SizeOf(IntBuf));

finally Stream2.Free;

end;

finally Streaml.Free;

end;

end;

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

При необходимости копирования одного файла в другой целиком использу ется метод copyFrom, унаследованный от класса Tstream:

procedure TForml.CopyBtnClick(Sender: TObject);

var Streaml, Stream2: TFileStream;

begin if Not OpenDlg.Execute then Exit;

Глава 9. Файлы и устройства ввода/вывода try Streaml := TFileStream.Create(OpenDlg.FileName, fmOpenRead);

Stream2 := TFileStream.Create('Sample.tmp1, fmOpenWrite);

Stream2.Seek(0, soFromEnd);

Stream2.CopyFrom(Streaml, Streaml.Size);

finally Streaml.Free;

Stream2.Free;

end;

end;

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

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

Свойство property Memory: Pointer;

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

Изменение размера отведенной памяти осуществляется методом procedure SetSize(NewSize: Longint);

override;

Для очистки памяти потока используется метод procedure Clear;

Чтение/запись данных в память выполняется привычными методами Read И Write.

Также запись данных в память может осуществляться методами:

П procedure LoadFromFile(const FileName: string);

— И З файла;

• procedure LoadFromStream(Stream: TStream) ;

— И З Другого потока.

Дополнительно можно использовать методы записи данных в файл или поток:

procedure SaveToFile(const FileName: string);

procedure SaveToStream(Stream: TStream);

218 Часть II. Интерфейс и логика приложения Класс TStringStream Так как строковые константы и переменные широко применяются при раз работке приложений, то для удобства работы с ними создан специальный класс TStringStream. Он обеспечивает хранение строки и доступ к ней во время выполнения приложения.

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

Свойство только для чтения property DataString: string;

обеспечивает доступ к хранимой строке.

Методы function Read(var Buffer;

Count: Longint): Longint;

override;

И function Write(const Buffer;

Count: Longint): Longint;

override;

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

Метод function ReadString(Count: Longint): string;

обеспечивает чтение count байтов строки потока, начиная с текущей пози ции.

Метод procedure WriteString(const AString: string);

дописывает к строке строку AString, начиная с текущей позиции.

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

Класс EFCreateError Возникает при ОШИбке создания файла, a EFOpenError — при открытии файла.

При чтении/записи данных в поток могут возникнуть исключительные Ситуации EReadError И EWriteError.

Оповещение об изменениях в файловой системе Многие программисты задавались вопросом: как получить сигнал от опера ционной системы о том, что в файловой системе произошли какие-то из менения? Такой вид оповещения позаимствован из ОС UNIX и теперь дос тупен программистам, работающим с Win32.

Глава 9. Файлы и устройства ввода/вывода Для организации мониторинга файловой системы нужно использовать три функции — FindFirstChangeNotification, FindNextChangeNotification И FindcioseChangeNotification. Первая из них возвращает дескриптор объекта файлового оповещения, который можно передать в функцию ожидания.

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

Так может выглядеть код метода Execute потока, созданного для монито ринга:

var DirName : string;

procedure TSimpleThread.Execute;

var r: Cardinal;

fn : THandle;

Pages:     | 1 | 2 || 4 | 5 |   ...   | 11 |



© 2011 www.dissers.ru - «Бесплатная электронная библиотека»

Материалы этого сайта размещены для ознакомления, все права принадлежат их авторам.
Если Вы не согласны с тем, что Ваш материал размещён на этом сайте, пожалуйста, напишите нам, мы в течении 1-2 рабочих дней удалим его.