Текст книги "Delphi. Трюки и эффекты"
Автор книги: Александр Чиртик
Жанр: Программирование, Компьютеры
сообщить о неприемлемом содержимом
Текущая страница: 16 (всего у книги 24 страниц)
Для демонстрации некоторых других приемов работы с реестром, например перемещение по иерархии разделов реестра, определение списка параметров, их типа и значений), рассмотрим реализацию приложения, предоставляющего соответствующие возможности.
В итоге у нас получится этакая альтернатива программе Редактор реестра, правда, пригодная только для просмотра, но не для редактирования реестра. Главная форма программы выглядит так, как показано на рис. 7.11.
Рис. 7.11. Программа для просмотра реестра
Рассмотрим функции и процедуры, формирующие основу этого приложения, в порядке их использования. Итак, при запуске формы составляется список корневых разделов реестра (листинг 7.23).
Листинг 7.23. Первоначальная инициализация дерева разделов реестра
procedure TForm1.FormCreate(Sender: TObject);
var
item: TTreeNode;
begin
//Формирование списка корневых разделов реестра
item := keys.Items.AddChild(nil, 'HKEY_CLASSES_ROOT');
item.Data := Pointer(HKEY_CLASSES_ROOT);
CheckSubKeys(item);
item := keys.Items.AddChild(nil, 'HKEY_CURRENT_USER');
item.Data := Pointer(HKEY_CURRENT_USER);
CheckSubKeys(item);
item := keys.Items.AddChild(nil, 'HKEY_LOCAL_MACHINE');
item.Data := Pointer(HKEY_LOCAL_MACHINE);
CheckSubKeys(item);
item := keys.Items.AddChild(nil, 'HKEY_USERS');
item.Data := Pointer(HKEY_USERS);
CheckSubKeys(item);
item := keys.Items.AddChild(nil, 'HKEY_CURRENT_CONFIG');
item.Data := Pointer(HKEY_CURRENT_CONFIG);
CheckSubKeys(item);
end;
Процедура CheckSubKeys, вызываемая для каждого нового элемента дерева (листинг 7.23), реализована следующим образом (листинг 7.24).
Листинг 7.24. Оформление элемента дерева в зависимости от наличия вложенных разделов
procedure TForm1.CheckSubKeys(item: TTreeNode);
var
reg: TRegistry;
begin
reg := TRegistry.Create();
//Проверка, есть ли в разделе реестра вложенные подразделы
reg.RootKey := GetRootKey(item);
if reg.OpenKeyReadOnly(GetKeyPath(item)) then
begin
if reg.HasSubKeys() then
begin
//Добавляем фиктивный элемент (чтобы показывался "+" для
//разворачивания раздела). Одновременно помечаем
//фиктивный элемент
keys.Items.AddChild(item, '').Data := Pointer(-1);
end;
reg.CloseKey();
end;
reg.Free();
end;
По сравнению с примером (дерево каталогов), рассмотренным в подразд. «Построение дерева каталогов» разд. 4.2, определение наличия дочерних разделов реестра – относительно легковесная операция, поэтому эту проверку производим сразу при составлении списка подразделов. Как и в только что упомянутом примере из гл. 4, мы добавляем в дерево фиктивный дочерний элемент для тех элементов дерева, для которых соответствующие им разделы реестра содержат подразделы.
Важно то, что фиктивный элемент помечается значением -1. Как раз по наличию дочернего элемента с полем Data, равным -1, можно определить, зачитывалось ли содержимое раздела, соответствующего определенному элементу дерева. Содержимое раздела читается при разворачивании элемента дерева (листинг 7.25).
Листинг 7.25. Составление списка дочерних разделов
procedure TForm1.keysExpanding(Sender: TObject; Node: TTreeNode;
var AllowExpansion: Boolean);
var
reg: TRegistry;
subkeys: TStrings;
i: Integer;
begin
if Integer(Node.getFirstChild.Data) <> -1 then
//Список подразделов был зачитан ранее
Exit;
Node.DeleteChildren(); //Удаление фиктивного элемента дерева
reg := TRegistry.Create();
//Загрузка списка подразделов выбранного раздела
reg.RootKey := GetRootKey(Node);
if reg.OpenKey(GetKeyPath(Node), False) then
begin
//Получение списка подразделов
subkeys := TStringList.Create();
reg.GetKeyNames(subkeys);
for i := 0 to subkeys.Count – 1 do
begin
//Добавление элемента для дочернего раздела (не забываем
//проверять подразделы у каждого дочернего раздела)
CheckSubKeys(keys.Items.AddChild(Node, subkeys[i]));
end;
subkeys.Free();
reg.CloseKey();
end;
reg.Free();
end;
В листинге 7.25 используются две дополнительные функции: для определения полного пути раздела, соответствующего элементу дерева (без имени ко рневого раздела), и для получения дескриптора корневого раздела (хранится в пoлeData корневого элемента каждой ветви дерева).
Путь раздела определить несложно: просто поднимаемся к корню соответствующей верви дерева, собирая по ходу имена элементов дерева (листинг 7.26).
Листинг 7.26. Определение пути раздела в дереве
function GetKeyPath(item: TTreeNode): String;
var
temp: TTreeNode;
path: String;
begin
temp := item;
while temp.Parent <> nil do
begin
path := temp.Text + '' + path;
temp := temp.Parent;
end;
GetKeyPath := path;
end;
Аналогичным образом, даже проще, определяется дескриптор корневого раздела определенной ветви реестра: для этого нужно просто добраться до корня ветви дерева и прочитать значение поля Data корневого элемента (листинг 7.27).
Листинг 7.27. Определение дескриптора корневого раздела ветви
function GetRootKey(item: TTreeNode): HKEY;
var
temp: TTreeNode;
begin
temp := item;
while temp.Parent <> nil do
temp := temp.Parent;
GetRootKey := HKEY(temp.Data);
end;
При выделении элемента дерева происходит отображение параметров соответствующего раздела в списке в правой части формы. Как заполнять список, представлено в листинге 7.28.
Листинг 7.28. Составление списка параметров раздела реестра
procedure TForm1.keysChange(Sender: TObject; Node: TTreeNode);
var
reg: TRegistry;
valueItem: TListItem;
item: TTreeNode;
valueNames: TStrings;
i: Integer;
begin
item := keys.Selected;
if item <> nil then
begin
//Зачитаем содержимое выбранного раздела в ListView (values)
values.Clear;
reg := TRegistry.Create();
reg.RootKey := GetRootKey(item);
if reg.OpenKeyReadOnly(GetKeyPath(item)) then
begin
valueNames := TStringList.Create();
//Получение списка названий параметров
reg.GetValueNames(valueNames);
//Добавление каждого параметра в список
for i := 0 to valueNames.Count – 1 do
begin
valueItem := values.Items.Add();
if valueNames[i] = '' then
valueItem.Caption := '<По умолчанию>'
else
valueItem.Caption := valueNames[i];
//Получение типа и значения параметра
case reg.GetDataType(valueNames[i]) of
rdUnknown:
valueItem.SubItems.Add('Неизвестно');
rdString, rdExpandString:
begin
valueItem.SubItems.Add('Строка');
valueItem.SubItems.Add(reg.ReadString(valueNames[i]));
end;
rdInteger:
begin
valueItem.SubItems.Add('Число');
valueItem.SubItems.Add(IntToStr(
reg.ReadInteger(valueNames[i])));
end;
rdBinary:
valueItem.SubItems.Add('Двоичные данные');
end;
end;
valueNames.Free();
reg.CloseKey();
end;
reg.Free();
end;
end;
Процедура, приведенная в листинге 7.28, не считывает значения двоичных параметров. Так сделано для упрощения этого и так громоздкого фрагмента кода. В считывании значений двоичных параметров на самом деле нет ничего сложного: нужно лишь заранее определить размер данных (метод GetDataSize) и создать буфер соответствующего размера.
Глава 8
Обмен данными между приложениями
• СообщениеWM_COPYDATA
• Использованиебуфераобмена
• Проецируемыевпамятьфайлы
Организация обмена данными между приложениями, а именно между процессами этих приложений, является достаточно трудоемкой задачей. Архитектура Win32 подразумевает максимальную изоляцию выполняющихся приложений друг от друга. Каждое приложение исполняется в своем виртуальном адресном пространстве, которое изолировано и не имеет доступа к памяти других процессов приложений. Но довольно часто возникает необходимость передачи данных из одного выполняющегося процесса в другой. Это вызвано тем, что функциональные приложения и пакеты программ исполняются не в одном процессе, поэтому для нормальной работы используются основные возможности межпроцессного взаимодействия. Наиболее простым, понятным, но не всегда удобным является передача данных с использованием сообщения WM_COPYDATA. Также для передачи данных между приложениями широко используются проецируемые в память файлы (Mapping Files). Существуют и такие высокоуровневые средства, как буфер обмена или уже рассмотренная технология СОМ. Перечисленные способы будут подробно рассматриваться в этой главе. За рамки этой книги выходит рассмотрение способа передачи данных через каналы (трубы, или Pipe), который считается устаревшим и по этой причине не вызывает интереса.
8.1. Сообщение WM_COPYDATA
Сообщение WMCOPYDATA позволяет приложениям копировать данные между их адресными пространствами. Для передачи сообщения должна использоваться функция синхронной отправки сообщения SendMessage, а не PostMessage, которая асинхронным образом передает сообщение. Данные, предназначенные для передачи, не должны содержать указателей или других ссылок на объекты, недоступные для программы, принимающей эти данные. Рассмотрим параметры, передаваемые с сообщением WM_COPYDATA:
//дескриптор передающего окна
wParam = (WPARAM) (HWND) hwnd;
//указатель на структуру с данными
lParam = (LPARAM) (PCOPYDATASTRUCT) pcds;
На использование сообщения налагаются следующие ограничения:
• данные, которые будут приняты, должны быть только для чтения, так как изменение структуры с данными может привести к непредсказуемым последствиям;
• если приложению, получающему данные, требуется использовать их после возврата из обработчика WMCOPYDATA, оно должно скопировать их в локальный буфер.
Итак, приступим к созданию приложения, демонстрирующего работу WM_COPYDATA Для создания хорошего примера потребуется создать два приложения. Первое будет отправлять данные (например, строку текста), другое приложение будет их получать. На главной форме первого приложения помещаем элемент управления TextBox, в который будет записываться передаваемая строка, и кнопку, нажатие которой инициирует передачу данных. Для второго приложения достаточно элемента для отображения текстовой информации типа Label. Перейдем к рассмотрению исходных текстов созданных приложений.
Мы будет посылать сообщение окну, и сообщений может быть различное количество, поэтому для уникальной идентификации операции введем специальную константу:
const
CMD_SETLABELTEXT = 1; // Задаем ID команды
На форме находится кнопка отправки данных другому приложению, ее обработчик выглядит следующим образом (листинг 8.1).
Листинг 8.1. Отправка данных другому приложению
procedure TDataSender.bnSendClick(Sender: TObject);
var
CDS: TCopyDataStruct;
begin
//Устанавливаем тип команды
CDS.dwData := CMD_SETLABELTEXT;
//Устанавливаем длину передаваемых данных
CDS.cbData := Length(StringEdit.Text) + 1;
//Выделяем память буфера для передачи данных
GetMem(CDS.lpData, CDS.cbData);
try
//Копируем данные в буфер
StrPCopy(CDS.lpData, StringEdit.Text);
// Отсылаем сообщение в окно с заголовком StringReciever
SendMessage(FindWindow(NIL, 'StringReciever'),
WM_COPYDATA, Handle, Integer(@CDS));
Finally
//Высвобождаем буфер
FreeMem(CDS.lpData, CDS.cbData);
end;
end;
Подробного комментария данный листинг не требует. Обратите лишь внимание на вызов функции SendMessage, которая использует FindWindow для задания одного из своих параметров. Процедура FindWindow в случае успешного выполнения возвращает HWND окна, заголовок которого задается в параметре этой функции (строка StringReciever из предыдущего примера). Синхронная отправка сообщения WM_COPYDATA с набором данных, которые помещены в структуру CDS, осуществляется вызовом SendMessage.
Рассмотрим второе приложение, которое принимает строку и отображает ее в надписи (Label). Для начала в блок объявления помещаем обработчик сообщения и объявляем само сообщение WM_COPYDATA:
type
TStringReciever = class(TForm)
LabelStr: TLabel;
private
//Обработчик сообщения WM_COPYDATA
procedure WMCopyData(var MessageData: TWMCopyData);
message WM_COPYDATA;
Как и в случае первого приложения, нам необходима константа, которая будет идентифицировать тип операции:
const
CMD_SETLABELTEXT = 1;
Далее рассмотрим тело функции обработчика сообщения WM_COPYDATA (листинг 8.2).
Листинг 8.2. Обработка сообщения WM_COPYDATA
procedure TStringReciever.WMCopyData(var MessageData: TWMCopyData);
begin
//Устанавливаем свойства метки, если заданная команда совпа-
дает
if MessageData.CopyDataStruct.dwData = CMD_SETLABELTEXT then
begin
//Устанавливаем текст из полученных данных
LabelStr.Caption := PChar(MessageData.CopyDataStruct.lpData);
MessageData.Result := 1;
end else
MessageData.Result := 0;
end;
Если окну второго приложения, которое носит название StringReciver (получатель строки), приходит сообщение WM_COPYDATA, то происходит вызов WMCopyData. В качестве параметра эта процедура получает структуру данныхМеБ sage Data типа TWMCopyData, содержащую идентификатор операции и данные (передаваемую строку). После проверки типа операции в случае совпадения его с константой CMD_SETLABELTEXT полученные данные преобразуются в строку. Преобразование происходит при помощи функции PChar. Полученная строка устанавливается в качестве заголовка для метки с именем LabelStr. Затем полю Result структуры MessageData присваивается значение 1 или 0, в зависимости от успеха операции.
Таким образом, для передачи данных (строки) записываем передаваемую строку в текстовое поле первой формы и нажимаем кнопку Отправить. Результат работы приложений можно увидеть на рис. 8.1.
Рис. 8.1. Вид приложений посылки и получения строки
Необходимо добавить, что передача данных посредством сообщения WM_COPYDATA является удобным и простым способом. Но WMCOPYDATA можно передавать только функцией SendMessage, и это является существенным недостатком такого метода. SendMessage «замораживает» работу приложения-отправителя, поэтому такой способ применяется для передачи небольших объемов данных, которые не требуют сложной обработки на стороне программы-приемника. К тому же на использование WMCOPYDATA налагаются некоторые существенные ограничения, о которых говорилось выше.
8.2. Использование буфера обмена
Буфер обмена представляет собой область оперативной памяти, которая используется операционной системой для временного хранения данных. Он выступает в роли общего хранилища данных для всех приложений системы, фактически любая программа может записывать данные в буфер обмена и считывать их оттуда. Он способен хранить данные различных типов и, кроме данных, содержит сведения о типе хранимой информации. Буфер обмена является неотъемлемым компонентом операционной системы типа Windows.
Буфер обеспечивает простейший обмен данными между приложениями. Одно приложение помещает туда данные, другое – считывает данные из буфера. Как правило, эти действия (чтение и запись) выполняются при непосредственном участии пользователя. В использовании буфера может участвовать и одно приложение, в этом случае проходит обмен данными внутри его.
Для выполнения операции обмена данными через буфер в Delphi предназначен специальный класс TClipboard. В Delphi также имеется глобальный объект Clipboard, который является экземпляром класса TClipboard и представляет буфер обмена Windows.
При помощи свойств и методов объектаСЫрЬоа^ возможно осуществление различных операций обмена или анализа хранимых данных. Для доступа к объекту буфера в разделе uses модуля, в котором выполняются операции с объектом буфера обмена, указывается модуль Clipboard.
Общее количество форматов, поддерживаемых буфером обмена, содержится в свойстве FormatCount типа Integer. Для отображения количества форматов, которые распознает буфер, можно использовать следующий листинг:
//В разделе uses указываем модуль Clipboard
InformationClipLabel.Caption := IntToString(Clipboard.FormatCount);
Буфер обмена поддерживает самые разнообразные типы данных. Приведем список поименованных констант некоторых форматов.
• CF_TEXT – обычный текст (коды ANSI). Символ окончания строки – #1С и #13, окончание текста – #0.
• CF_BITMAP – рисунок ВМР-формата.
• CF_MetaFilePic – рисунок WMF-формата.
• CF_TIFF – рисунок TIFF-формата.
• CF_OEMTEXT – текст.
• CF_DIB – рисунок DIB-формата.
• CF_Wave – звук.
• CF_UniCodeText – текст (коды Unicode).
• CF_Picture – объект типа TPicture.
При необходимости можно создать и зарегистрировать свои форматы данных в дополнение к имеющимся базовым.
При использовании нестандартных форматов данных, помещаемых в буфер обмена и извлекаемых оттуда, программы должны соблюдать устанавливаемые разработчиками соглашения об обмене такими данными.
В листинге 8.3 приводится обработчик нажатия кнопки, загружающий в список ListBoxInf о значения констант, идентифицирующих каждый формат данных буфера обмена.
Листинг 8.3. Отображение значений форматов буфера
procedure TFormClipboard.bnInfoCipClick(Sender: TObject);
var i: integer;
begin
ListBoxInfo.Clear;
for i := 0 to Clipboard.FormatCount – 1 do
ListBoxInfo.Items.Add(IntToStr(Clipboard.Formats[i]));
end;
Приложение может помещать информацию в буфер обмена и извлекать ее только в тех форматах, которые будет поддерживать буфер. Список поддерживаемых форматов создается при инициализации приложения.
Перед доступом к данным, содержащимся объектом Clipboard, может потребоваться анализ формата данных, для этого служит метод HasFormat. Процедура HasFormat (Format: Word): Boolean используется для запроса к буферу обмена и позволяет узнать, можно ли извлечь хранимые в нем данные в заданном формате, указанном параметром Format. При положительном ответе возвращаемое значение равно True, в противном случае – False.
Как правило, различные приложения используют буфер обмена. Но в случае, когда необходимо получить монопольный доступ к буферу, приложение должно открыть его для себя в специальном режиме. Для этого вызывается метод Open, позволяющий программе получить полный (исключительный) доступ к общей области обмена. После вызова метода Open содержимое буфера не может быть изменено другими приложениями, поэтому после окончания монопольного использования приложение должно вызвать метод Close объекта Clipboard. Если открытый буфер не был закрыт с помощью метода Close, то он будет автоматически закрыт системой после завершения программы, открывшей буфер обмена.
Для очистки содержимого буфера обмена используется метод Clear. Он вызывается автоматически при изменении содержимого в буфере. Перед записью новых данных, помещаемых в буфер, старая информация удаляется.
Класс TClipboard используется многими другими классами и компонентами, которые поддерживают обмен данными через буфер обмена. К примеру, компоненты Memo и Edit имеют специальные методы для обмена текстовой информацией посредством буфера. Методы CopyToClipBoardH CutToClipBoard помещают текстовые данные в буфер обмена, копируя и вырезая их из источника (компонента) соответственно, а метод PasteFromClipBoard вставляет текстовый фрагмент из буфера в текстовое поле.
Только при использовании подобных методов лучше проверять, является ли содержимое буфера обмена текстовой информацией. В листинге 8.4 показан пример копирования в буфер обмена всего текста, введенного в текстовое поле.
Листинг 8.4. Копирование текста из поля редактора Memo в буфер обмена
procedure TFormClipboard.bnCopyTextClick(Sender: TObject);
begin
//Выделяем весь текст в поле редактора
MemoText.SelectAll;
//Копируем текст
MemoText.CopyToClipBoard;
end;
Буфер обмена часто используется для хранения текста, поэтому объект Clipboard имеет специальное свойство AsText типа String, предназначенное для обработки содержимого буфера как данных текстового формата. Свойство AsText предназначено как для чтения, так и для записи. При чтении свойства данные извлекаются из буфера, а при записи – заносятся в буфер обмена (листинг 8.5).
Листинг 8.5. Копирование текстовой информации
procedure TFormClipboard.bnCopyTextAsTextClick(Sender: TObject);
begin
//Если в буфере текст, то выводим его в поле редактора
if Clipboard.HasFormat(CF_Text)
then MemoText := Clipboard.AsText
end;
При работе с графическими компонентами для операций, связанных с обменом информацией через общую область, удобно использовать метод Assign. Процедура Assign (Source: TPers is tent) присваивает буферу обмена объект, указанный параметром Source. Если объект является изображением и принадлежит таким графическим классам, как TBitmap, TPicture или TMetaf ile, то в буфер обмена копируется изображение установленного формата. Для извлечения изображения также может использоваться метод Assign.
Пример использования буфера обмена для копирования изображений проводится в листинге 8.6.
Листинг 8.6. Обмен изображением через буфер обмена
procedure TFormClipboard.bnCopyImageClick(Sender: TObject);
begin
//Открываем монопольный доступ
Clipboard.Open;
//Заносим изображение в буфер
Clipboard.Assign(ImageMyPic1.Picture);
//Проверям формат находящихся в буфере данных
if Clipboard.HasFormat(CF_Picture)
then ImageMyPic2.Picture.Assign(Clipboard);
//Закрываем монопольный доступ к буферу
Clipboard.Close;
end;
Изображение, находящееся в образе ImageMyPicl, помещается в буфер обмена, откуда затем копируется в образ ImageMyPic2. Для выполнения этих операций устанавливается монопольный доступ к объекту Clipboard.
Таким образом, использование объекта Clipboard находит широкое применение в программировании приложений, которым необходим обмен данными с другими программами. Необходимо отметить, что буфер обмена ориентирован на работу с пользователем (пользователь инициирует обмен данными между приложениями), поэтому такой способ обмена данными наиболее удобен с точки зрения пользователя. К тому же буфер обмена поддерживает множество форматов представления информации, что позволяет сделать обмен данными более гибким и эффективным.
Правообладателям!
Это произведение, предположительно, находится в статусе 'public domain'. Если это не так и размещение материала нарушает чьи-либо права, то сообщите нам об этом.