Excel. Трюки и эффекты
Шрифт:
Процедура 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;
По
Важно то, что фиктивный элемент помечается значением -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. Перейдем к рассмотрению исходных текстов созданных приложений.