WWW.DISSERS.RU

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

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

Pages:     | 1 |   ...   | 9 | 10 ||

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

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

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

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

критическая секция всегда ждет столько, сколько потребуется.

Возьмем класс TCriticaisection (модуль SYNCOBJS.PAS). Логика использо вания его проста — "держать и не пущать". В многопотоковом приложении создается и инициализируется общая для всех потоков критическая секция.

Когда один из потоков достигает критически важного участка кода, он пы тается захватить секцию вызовом метода Enter:

MySection.Enter;

try DoSomethingCritical;

finally MySection.Leave;

end;

Когда другие потоки доходят до оператора захвата секции Enter и обнару живают, что она уже захвачена, они приостанавливаются вплоть до осво бождения секции первым потоком путем вызова метода Leave. Обратите внимание, что вызов Leave помещен в конструкцию t r y.. f i n a l l y — здесь требуется стопроцентная надежность. Критические секции являются сис темными объектами и подлежат обязательному освобождению — впрочем, как и остальные рассматриваемые здесь объекты.

Процесс. Порождение дочернего процесса Объект типа процесс (process) может быть использован для того, чтобы при остановить выполнение потока в том случае, если он для своего продолже ния нуждается в завершении процесса. С практической точки зрения такая проблема встает, когда нужно в рамках вашего приложения исполнить при ложение, созданное кем-то другим, или, к примеру, сеанс MS-DOS.

726 Часть VII. Технологии программирования Рассмотрим, как, собственно, один процесс может породить другой. Вместо устаревшей и поддерживаемой только для совместимости функции winExec, перекочевавшей из прежних версий Windows, гораздо правильнее использо вать более мощную:

function CreateProcess(lpApplicationName: PChar;

lpCommandLine: PChar;

lpProcessAttributes, lpThreadAttributes: PSecurityAttributes;

blnheritHandles: BOOL;

dwCreationFlags: DWORD;

lpEnvironment: Pointer;

lpCurrentDirectory: PChar;

const lpStartupInfo: TStartupInfo;

var lpProcessinformation: TProcessInformation): BOOL;

Первые два параметра ясны — это имя запускаемого приложения и переда ваемые ему в командной строке параметры. Параметр dwCreationFlags со держит флаги, определяющие способ создания нового процесса и его буду щий приоритет. Использованные в приведенном ниже листинге флаги озна чают: CREATE_NEW_CONSOLE — будет запущено новое консольное приложение с отдельным окном;

NORMAL_PRIORITY_CLASS — нормальный приоритет.

Структура TStartupInfo содержит сведения о размере, цвете, положении ок на создаваемого приложения. В нижеследующем примере (листинг 29.1) ис пользуется поле wshowwindow: установлен флаг SW_SHOWNORMAL, означающий визуализацию окна с нормальным размером.

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

i Листинг 29.1. Порождение дочернего процесса j var lpStartupInfo: TStartupInfo;

lpProcessinformation: TProcessInformation;

begin FillChar(lpStartupInfo,Sizeof(lpStartupInfo),#0) ;

lpStartupInfo.cb := Sizeof(lpStartupInfo) ;

lpStartupInfo.dwFlags := STARTF_USESHOWWINDOW;

lpStartupInfo.wShowWindow := SW_SHOWNORMAL;

if not CreateProcess(nil, PChar('ping localhost'), nil, nil, false, Глава 29. Потоки и процессы CREATE_NEW_CONSOLE or NORMAL_PRIORITY_CLASS, nil, nil, lpStartupInfo, lpProcessInformation) then ShowMessage(SysErrorMessage(GetLastError)) else begin WaitForSingleObject(lpProcessInformation.hProcess, 10000);

CloseHandle(lpProcessInformation.hProcess);

end;

end;

Поток Поток может ожидать другой поток точно так же, как и другой процесс.

Ожидание можно организовать с помощью функций API (как в только что рассмотренном примере), но удобнее это сделать при помощи метода TThread.WaitFor.

Консольный ввод Консольный ввод (console input) годится для потоков, которые должны ожи дать отклика на нажатие пользователем клавиши на клавиатуре. Этот тип ожидания может быть использован в программе дуплексной связи (chat).

Один поток при этом будет ожидать получения символов;

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

Оповещение об изменении в файловой системе Этот вид объекта ожидания очень интересен и незаслуженно мало известен.

Мы рассмотрели практически все варианты того, как один поток может по дать сигнал другому. А как получить сигнал от операционной системы? Ну, например, о том, что в файловой системе произошли какие-то изменения?

Такой вид оповещения позаимствован из ОС UNIX и доступен программи стам, работающим с Win32.

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

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

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

var DirName : string;

procedure TSimpleThread.Execute;

var r: Cardinal;

fn : THandle;

begin fn := FindFirstChangeNotification(pChar(DirName), True, FILE_NOTIFY_CHANGE_FILE_NAME);

repeat r := WaitForSingleObject(fn,2000);

if r = WAIT_OBJECT_0 then Synchronize(Forml.UpdateList);

if not FindNextChangeNotification(fn) then break;

until Terminated;

FindCloseChangeNotification(fn);

end;

На главной форме должны находиться компоненты, нужные для выбора обследуемой папки, а также компонент TListBox, в который будут записы ваться имена файлов:

procedure TForml.ButtonlClick(Sender: TObject);

var dir : string;

begin if SelectDirectoryfdir,[],0) then begin Editl.Text := dir;

DirName := dir;

end;

end;

procedure TForml.UpdateList;

var SearchRec: TSearchRec;

begin ListBoxl.Clear;

FindFirst(Editl.Text+'\*.*\ faAnyFile, SearchRec);

repeat ListBoxl.Iterns.Add(SearchRec.Name);

Глава 29. Потоки и процессы u n t i l FindNext(SearchRec) <> 0;

FindClose(SearchRec);

end;

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

Локальные данные потока Интересная проблема возникает, если в приложении будет несколько оди наковых потоков. Как избежать совместного использования одних и тех же переменных несколькими потоками? Первое, что приходит на ум, — доба вить и использовать поля объекта — потомка TThread, которые можно доба вить при его создании. Каждый поток соответствует отдельному экземпляру объекта, и их данные пересекаться не будут. (Кстати, это одно из больших удобств использования класса TThread.) Но есть функции API, которые знать не знают об объектах Delphi и их полях и свойствах. Для поддержки разделения данных между потоками на нижнем уровне в язык Object Pascal введена специальная директива — threadvar, которая отличается от дирек тивы описания переменных var тем, что применяется только к локальным данным потока. Следующее описание:

Var datal: Integer;

threadvar data2: Integer;

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

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

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

730 Часть VII. Технологии программирования Пример такого ресурса — общий блок в файле, отображаемом в память.

Поскольку этот ресурс имеет имя, можно сделать его уникальным именно для вашего приложения:

var UniqueMapping : THandle;

FirstWindow : THandle;

begin UniqueMapping := CreateFileMapping($ffffffff, nil, PAGE_READONLY, 0, 32,'MyMap');

if UniqueMapping = 0 then begin ShowMessage(SysErrorMessage(GetLastError));

Halt;

end else if GetLastError = ERROR_ALREADY_EXISTS then begin FirstWindow := FindWindowEx(0, 0, TfmMain.ClassName, nil);

if FirstWindowOO then SetForegroundWindow(FirstWindow);

Halt;

end;

// Нет других копий — продолжение Application.Initialize;

Примерно такие строки нужно вставить в начало текста проекта до созда ния форм. Блок совместно используемой памяти выделяется в системном страничном файле (об этом говорит первый параметр, равный — 1, см. опи сание функции CreateFileMapping). Его имя — МуМар. Если при создании бло ка будет получен код ошибки ERROR_ALREADY_EXISTS, ЭТО свидетельствует о наличии работающей копии приложения. В этом случае приложение пере ключает фокус на главную форму другого экземпляра и завершается;

в про тивном случае процесс инициализации продолжается.

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

• Если потоки работают только с переменными, объявленными внутри их собственного класса, то ситуации гонок и тупиков крайне маловероятны.

Глава 29. Потоки и процессы Другими словами, избегайте использования в потоках глобальных пере менных и переменных других объектов.

• Если вы обращаетесь к полям или методам объектов VCL, делайте это только посредством метода synchronize.

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

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

но они также усложняют и без того непростой процесс отладки. И все же преимущества потоков однозначно перевешивают их не достатки.

ГЛАВА 3 Многомерное представление данных Помимо стандартных компонентов отображения данных в VCL Delphi име ются дополнительные компоненты, которые позволяют представлять дан ные в виде кросстаба. При этом заставить работать кросстаб с двумя и более полями почти так же просто, как и обычный компонент TDBGrid. Эти ком поненты расположены на странице Decision Cube Палитры компонентов.

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

В настоящей главе рассматриваются следующие вопросы:

П для чего необходим кросстаб;

• особенности запросов SQL для многомерного представления;

• компоненты многомерного представления и их взаимосвязь.

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

Создать подобную двумерную структуру отображения данных при помощи обычных компонентов со страницы Data Controls Палитры компонентов очень непросто и хлопотно.

Глава 30. Многомерное представление данных 1997 18470.00 19000. Geo Tech Inc. — 3400. 120000.00 93773220. 3D-Pad Corp.

7349.50 76300. — MPM Corporation Рис. 30.1. Пример кросстаба В общем случае горизонтальную и вертикальную структуры кросстаба могут составлять несколько полей одновременно, которые сгруппированы относи тельно более общих полей.

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

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

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

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

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

О перед отображением данных необходимо настроить параметры размерно стей кросстаба;

• непосредственный показ данных в кросстабе;

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

734 Часть VII. Технологии программирования Для этого в форме приложения требуется разместить как минимум пять компонентов со страницы Decision Cube Палитры компонентов.

Для создания запроса SQL можно использовать компонент TDecisionQuery И И обычный компонент TQuery.

Л Запрос должен быть связан с компонентом TDecisionCube, который осуще ствляет подготовку набора данных запроса к многомерному показу.

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

Непосредственный показ многомерного набора данных проводится при по мощи КОМПОНеНТОВ TDecisionGrid И TDecisionGraph. Они Д Л Н ПОДДерЖИ ОЖ Ы вать соединение С компонентом TDecisionSource.

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

Допустим, что на форме расположены следующие компоненты:

• TDecisionQuery ПО имени DecisionQueryl;

• TDecisionCube ПО ИМеНИ DecisionCubel;

• TDecisionSource ПО имени DecisionSourcel;

П TDecisionGrid ПО имени DecisionGridi;

• TDecisionPivot ПО имени DecisionPivotl.

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

Таблица 30.1. Как связать компоненты многомерного представления данных Свойство Значение Описание TBeci s ionCube j DecisionQueryl ] Определяет компонент доступа к данным, DataSet I | который создает набор данных TDecisionSourсе | Указывает на компонент формирования DecisionCubel DecisionCube многомерного набора данных TDecisionGrid DecisionSource I DecisionSourcel I Ссылается на компонент TDecisionSource Глава 30. Многомерное представление данных Таблица 30.1 (окончание) Свойство Значение Описание TOecisionPivot DecisionSourcel DecisionSource Ссылается на компонент TDecisionSource Если задать текст запроса SQL и открыть набор данных, то вся цепочка за работает, причем ее поведение ничем не отличается от поведения во время выполнения приложения.

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

Подготовка набора данных Компоненты многомерного представления данных работают со специально созданным и подготовленным набором данных. Эта работа выполняется специальным компонентом доступа к данным — TDecisionQuery. Его непо средственным предком является компонент TQuery.

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

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

2. Поля запроса должны быть сгруппированы при помощи оператора GROUP BY.

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

Компонент TDecisionQuery должен только обеспечить выполнение запроса и создание набора данных, он не имеет никаких дополнительных свойств или методов. Поэтому для создания набора данных можно использовать и обыЧНЫЙ компонент TQuery. Преимущество компонента TDecisionQuery СО С О Т в том, что он имеет специализированный редактор для создания тек ТИ ста запроса (рис. 30.2). Он вызывается командой Decision Query Editor из всплывающего меню компонента или двойным щелчком на компоненте.

Элементы управления страницы Dimensions/Summaries позволяют создавать текст запроса, манипулируя именами полей таблиц. Псевдоним базы дан ных выбирается в комбинированном списке Database. После этого в списке Table задается нужная таблица. Если в запросе требуется использовать не Часть VII. Технологии программирования сколько таблиц, то для их выбора можно воспользоваться утилитой SQL Builder, которая вызывается щелчком на одноименной кнопке.

( e i i n Q ey E io D cso u r dt r D esn/ u mrs I S L Qe J mnosSm a Q ur ii ei y COE STD OER.SRT.D_E UM RA v ia l Fed :

al be i l s S O U BR.. ME PN НИШ*— S AE RP. LS E S S R E SAU O DR TT S.

SMsU) Sm N u Sr T mS(a.iO e I:

S R E DT O DR AE.

UD C S H DT. P AE SI S AE NE E. T " EDD D SA.D PI S T ODRD. Y R EE Q Ш:

SOA V L E. T L AU T A d d SS ON. IC U T D :T S E TP. M YE I Da as te b : Je al:

b zl Г" Count J"t for Averages • J jCube O:

K Cancel;

РИС. 30.2. Специализированный редактор компонента T D e c i s i o n Q u e r y Из списка доступных полей при помощи кнопок Add требуемые поля мож но перенести в список полей — размерностей Dimensions и список сумми рующих полей Summaries. Поля из этих списков используются при созда нии запроса.

Запрос формируется автоматически при работе с описанными элементами управления. Текст запроса доступен для просмотра и редактирования на странице SQL Query.

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

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

Компонент TDecisionCube формирует размерности при открытии набора данных, причем созданное многомерное представление данных полностью работоспособно уже во время разработки. Для этого достаточно присвоить СВОЙСТВУ Active компонентов TDecisionQuery ИЛИ TQuery значение True. По сле этого любой визуальный компонент многомерного представления начи нает работать так же, как и во время выполнения.

Компонент TDecisionCube также позволяет управлять использованием памя ти многомерного представления данных. Дело в том, что при добавлении к многомерному представлению новой размерности объем занимаемой па мяти возрастает в арифметической профессии. Поэтому возможность огра Глава 30. Многомерное представление данных ничения размеров используемой памяти особенно актуальна для больших наборов данных.

Все основные настройки компонента выполняются при помощи специали зированного редактора свойства DecisionMap (рис. 30.3).

Available fields (Заказчик Display Цате ORDER_DATE" SUM Type d JAs Needed Active Type Fonjat |None firouping Inrfa' Va OK Cancel Help Рис. 30.3. Специализированный редактор свойства D e c i s i o n M a p компонента T D e c i s i o n C u b e (страница Dimension Settings) Для настроек размерностей используется страница Dimension Settings этого редактора. В расположенном слева списке Available Fields содержатся все поля набора данных. В элементах управления справа приведены параметры размерности для выбранного поля.

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

Неактивный список выбора Туре показывает, является ли поле основой для размерности или суммы.

Список выбора Active Type определяет, когда данные поля появляются в визуальных компонентах. Его элементы обозначают следующее:

П Active — данные поля видны сразу после открытия формы и набора данных во время выполнения или сразу после открытия набора данных во время разработки;

• As Needed — данные поля становятся видны после выполнения пользова телем во время выполнения или разработчиком во время разработки дей ствий по отображению данных;

• inactive — данные поля не видны.

Часть VII. Технологии программирования Однострочный редактор Format содержит строку форматирования для дан ных поля.

Комбинированный список Grouping необходим для того, чтобы определить, какие значения будут показаны. Варианты Year, Quarter, Month возможны только для полей с календарным типом данных.

Однострочный редактор Initial Value задает начальное значение для поля.

Страница Memory Control используется для управления расходом памяти для нужд компонента (рис. 30.4). Однострочные редакторы Dimensions, Summaries и Cells в ряду Maximum позволяют задать максимальное число размерностей, сумм и ячеек, соответственно.

Decision C b Editor ue. m ho s m as Q e s n : u me ji f i ' О&Ц :

- Maximum •60;

: r Design*' Data : |: '. •;

;

' Г /Display Dini*nsion JJames • ;

:;

• : j'...: :: C/DisplayNamesahdSalues': ' ;

;

j / f Display Names, Values, and Iptals ••..| ;

1;

|i:• i f -C;

fiUhTimeDispl^1'Rhly ;

:• ? •:'•: ;

;

/ • ;

,. ' ;

• Рис. 30.4. Специализированный редактор свойства D e c i s i o n M a p компонента T D e c i s i o n C u b e (страница Memory Control) Аналогичные значения в ряду Current показывают текущее число этих структур.

Аналогичные значения в ряду Active+Needed показывают общее возможное число размерностей, сумм и ячеек.

Значения в ряду Active показывают число видимых размерностей сумм и ячеек.

Кнопка Get Cell Counts выполняет запрос, который возвращает число ячеек в кросстабе.

Группа радиокнопок Designer Data Options задает режим показа данных во время разработки:

Глава 30. Многомерное представление данных П Display Dimension Names — отображаются только названия размерностей;

• Display Names and Values — отображаются названия размерностей и зна чения;

• Display Names, Values, and Totals — отображаются названия, значения и суммы размерностей;

О Run Time Display Only — визуализация данных осуществляется только во время выполнения.

Подготовленный к использованию для многомерного отображения набор данных необходимо связать с визуальными компонентами. Это делается при помощи компонента TDecisionSource. Через один такой компонент с набором данных можно связать несколько визуальных компонентов (см. табл. 30.1).

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

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

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

КомпонентTDecisionCube Компонент TDecisionCube осуществляет преобразование набора данных, ко торый Содержится В Компоненте TDecisionQuery, К ВИДУ, ДОСТуПНОМу ДЛЯ отображения визуальными компонентами многомерного представления данных (табл. 30.2). Обычную таблицу набора данных компонент преобразу ет в многомерный кросстаб. Число размерностей создаваемого кросстаба зависит от числа полей данных набора данных. Значения в ячейках кросста ба зависят от типа агрегатной функции в запросе SQL.

Таблица 30.2. Свойства и методы компонента TDecisionCube Объявление | Тип ! Описание Свойства Разрешает или запрещает преобра property Active: Boolean;

j Pu зование набора данных в кросстаб 740 Часть VII. Технологии программирования Таблица 30,2 (продолжение) | Тип Описание Объявление property BinData: Boolean;

Значение True означает, что хотя бы Ro одна размерность находится в свер нутом состоянии (данные не отобра жаются) Определяет число байтов, используемых property Capacity: Integer;

Pu для хранения многомерного массива Содержит индекс текущей суммы property CurrentSuramary: Integer;

Pu кросстаба property DataSet: TDataSet;

Ссылка на экземпляр набора данных, Pb который отображается в кросстабе Задает режим отображения данных type TCubeDesignState = Pu в кросстабе:

(dsNoData, dsMetaData, dsDimensionData, dsAHData) ;

• dsNoData — во время разработки property DesignState: данные не видны;

TCubeDesignState;

• dsMetaData — видны названия размерностей;

• dsDimensionData — видны назва ния размерностей и значения, суммы не видны;

• dsAHData — видны все данные Возвращает число размерностей property DimensionCount: Integer;

Ro Индексированный список ссылок property DimensionMap: TCubeDims;

Pb на объекты параметров размерностей Общее число полей набора данных, property DimensionMapCount: Ro включая поля размерностей и сумм Integer;

Задает максимальное число ячеек Pb property MaxCells: Integer;

кросстаба Задает максимальное число Pb property MaxDimensions: Integer;

размерностей Задает максимальное число сумм property MaxSummaries: Integer;

Pb При значении True при подготовке property ShowProgressDialog: Pb кросстаба отображается индикатор Boolean;

Возвращает число активных сумм property SuiranaryCount: Integer;

RO кросстаба Методы function Pu ] Возвращает текст запроса SQL, GetDetailSQL(ValueArray: [ который может быть использован TSmalllntArray;

SelectList: | для создания набора данных, string;

bActive: Boolean): | включающего данные из кросстаба string;

I без сумм Глава 30. Многомерное представление данных Таблица 30.2 (окончание) Объявление Тип Описание Возвращает текст запроса SQL, кото function GetSQL(ValueArray: Pu рый может быть использован для соз TSmalllntArray;

bActive:

| дания набора данных, включающего Boolean): string;

j данные из кросстаба без сумм Вызывает специализированный procedure ShowCubeDialog;

Pu редактор компонента | Обновляет список объектов парамет procedure Refresh(DimensionMap | ров размерностей TCubeDims;

bForce: Boolean);

Методы-обработчики событий !Вызывается сразу после закрытия type TCubeRefreshEvent = Pb procedure(DataCube: компонента (Active := False) TCustomDataStore;

DimMap:

TCubeDims) of object;

property OnRefresh:

TCubeRefreshEvent;

property AfterClose: TCubeNotifyEvent;

property AfterOpen: Вызывается сразу после открытия Pb TCubeNotifyEvent;

компонента(Active := False) property BeforeClose: Вызывается перед закрытием Pb TCubeNoti fyEvent;

компонента(Active : = False) property BeforeOpen: Pb | Вызывается перед открытием TCubeNotifyEvent;

компонента(Active := False) Pb | Вызывается после того, как занимае TErrorAction = (eaFail, мый кросстабом объем памяти eaContinue);

превысит заданный предел TCapacityErrorEvent = procedure(var EAction:

TErrorAction) of object;

property OnLowCapacity:

TCapacityErrorEvent ;

При помощи методов GetDetaiiSQL и GetsQL можно получить тексты запро сов, которые возвращают набор данных, соответствующий кросстабу с за данным параметрами состояниями. Массив vaiueArray содержит условия для полей размерностей. Первой размерности соответствует первый элемент массива, второй размерности — второй элемент и т. д. Если значение эле мента меньше нуля, то в результат запроса попадают все значения поля размерности. Значение элемента, равное или больше нуля, определяет ин декс значения поля размерности. Параметр seiectList содержит разделенный запятыми список дополнительных полей, которые нужно включить в за Часть VII. Технологии программирования прос. Параметр bActive накладывает дополнительное ограничение на раз мерности. При значении True в результат запроса автоматически (без ис пользования параметра selectList) попадают только активные размерности.

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

Это свойство представляет собой экземпляр класса TCubeDims, который ин капсулирует индексированный список экземпляров объектов TCubeDim, каж дый из которых содержит информацию о параметрах одной размерности.

Основные свойства этого класса представлены в табл. 30.3.

Таблица 30.3. Основные свойства класса TCubeDim Тип I Описание Объявление Определяет режим отображения Pb type TActiveFlags = (diActive, данных размерности diAsNeeded, d i l n a e t i v e ) ;

property ActiveFlag:

TActiveFlags;

Содержит имя поля размерности property BaseName: s t r i n g ;

Pb в таблице базы данных Определяет способ форматирования property BinFormat: s t r i n g ;

Pu диапазона значений размерности Определяет способ группирования type TBinType = (binNone, Pb данных в размерности binYear, binQuarter, binMonth, binSet, binCustom);

property BinType: TBinType;

Определяет тип размерности type TDimFlags = (dimDimension, Pb dimSum, dimCount, dimAverage, dimMin, dimMax, dimGenericAgg, dimUnknown);

property DimensionType:

TDimFlags;

property FieldName: String;

Содержит имя поля в наборе данных Pb property FieldType: TFieldType;

Определяет тип поля Pu property Format: String;

Pu Задает форматирование данных размерности property Loaded: Boolean;

Ro Значение True говорит о том, что данный элемент загружен в многомерный набор данных Глава 30. Многомерное представление данных Таблица 30.3 (окончание) Объявление Тип Описание Pu Определяет начальный элемент property StartDate: TDate;

для группировки по дате Pu | Определяет начальный элемент property StartValue: String;

I для группировки по значению Pb | Возвращает число уникальных property ValueCount: Integer;

элементов в размерности Компонент TDecisionSource Компонент TDecisionSource предназначен для связывания визуальных ком понентов многомерного представления с компонентом TDecisionCube (табл. 30.4). Кроме того, за счет возможности подключения к этому компо ненту нескольких визуальных компонентов одновременно, при изменении состояния одного визуального компонента осуществляется синхронизация многомерного представления во всех остальных компонентах.

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

Таблица 30.4. Основные свойства компонента TDecisionSource Объявление Описание Определяет способ управления type TDecisionControlType = отдельной размерностью в компоненте (xtCheck, xtRadio, xtRadioEx), TDecisionGrid property ControlType:

TDecisionControlType;

Содержит индекс текущей суммы property CurrentSum: Integer;

в компоненте TDecisionGrid Связывает данный компонент property DecisionCube:

С компонентом TDecisionCube.

TDecisionCube;

Содержит ссылку на экземпляр компонента TDecisionCube Значение True означает, что данный property Ready: Boolean;

компонент связан с активным компонен том TDecisionCube При значении True из компонента property SparseCols: Boolean;

TDecisionGrid удаляются пустые колонки 744 Часть VII. Технологии программирования Таблица 30.4 (окончание) Объявление Описание p r o p e r t y SparseRows: Boolean;

При значении True из компонента TDecisionGrid удаляются пустые строки Компонент имеет средства для управления состоянием многомерного пред ставления в визуальных компонентах. Для этого используется свойство ControiType. Рассмотрим его возможные значения:

П xtCheck — ЩеЛЧОК На КНОПКаХ размерности В компонентах TDecisionGrid и TDecisionPivot приводит к открытию или закрытию размерности;

• xtRadio — щелчок на кнопках размерности в компонентах TDecisionGrid и TDecisionPivot приводит к открытию или закрытию данной размерно сти и закрытию всех остальных в этом направлении;

• xtRadioEx — ЩеЛЧОК на К О К Х раЗМерНОСТИ В компонентах TDecisionGrid НПа и TDecisionPivot приводит к открытию или закрытию данной размер ности и закрытию или открытию всех остальных размерностей в этом направлении.

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

С компонентом TDecisionCube они связаны при помощи компонента TDecisionSource (см. выше).

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

Компонент TDecisionGrid Компонент TDecisionGrid предназначен для многомерного представления данных в табличном виде. Параметры отображаемого набора данных (какие размерности будут видны при открытии, как группировать данные, как управлять размерностями) настраиваются при помощи других компонентов многомерного представления. В компоненте TDecisionGrid можно настроить только свойства самой сетки.

Для управления графиком во время выполнения можно использовать ком понент TDecisionPivot.

Глава 30. Многомерное представление данных В табл. 30.5 приведены основные свойства и методы компонента.

Таблица 30.5. Свойства и методы компонента TDecisionGrid Объявление Тип | Описание Свойства Ro | Индексированный массив значений property Cells[ACol, ARow:

I всех ячеек компонента в строковом Integer]: string;

I формате Возвращает общее число колонок property ColCount: Integer;

Ro в сетке РЬ | Указывает на компонент property DecisionSource:

TDecisionSource;

| TDecisionSource, через который | осуществляется связь с набором |данных property Dimensions: РЬ Объект TDisplayDims представляет TDisplayDims;

индексированный список объектов визуальных свойств размерностей property FixedCols: Integer;

| Возвращает число фиксированных Ro I колонок, которые используются для | отображения информации о размер I ностях (названия, значения, обозна I чения) I Возвращает число фиксированных property FixedRows: Integer;

RO | строк, которые используются для I отображения информации о размер | ностях (названия, значения, | обозначения) РЬ | Определяет общие настройки компо type i нента:

TDecisionGridOption = (cgGridLines, cgOutliner, I• cgGridLines — отображаются cgPivotable);

| вертикальные и горизонтальные | разделительные линии;

TDecisionGridOptions = set of TDecisionGridOption;

| • cgOutliner — отображаются property Options: | элементы управления в виде TDecisionGridOptions;

знаков "+" и "-" для открытия и закрытия размерностей;

cgPivotable — размерности мож но переупорядочивать при помощи перетаскивания Возвращает общее число строк property RowCount: Integer;

Ro в сетке Часть VII. Технологии программирования Таблица 30.5 (окончание) Объявление I Тип I Описание property ShowCubeEditor: Boolean;

i Pb Разрешает или запрещает использо вание специализированного редакто ра компонента TDeclsionCube property Totals: Boolean;

При значении True сетка имеет Pu промежуточные суммы по каждой колонке и строке Методы | Позволяет определить назначение type I Pu | любой ячейки сетки. Параметры ACol TDecisionDrawStates = ] j и ARow определяют положение ячейки (dsGroupStart, dsRowCaption, I | в сетке.

dsColCaption, dsSum, i dsRowValue, dsColValue, j [ В параметре Value возвращается dsData, dsOpenAfter, I | строка, содержащая значение в том dsCloseAfter, dsCloseBefore, I | виде, как оно представлено в ячейке.

dsOpenBefore, dsRowIndicator, I | Параметр DrawState возвращает dsColIndicator, dsRowPlus, | I информацию о назначении ячейки dsColPlus, dsNone);

I TDecisionDrawState = s e t | of TDecisionDrawStates;

j function CellDrawState(ACol, | ARow: I n t e g e r ;

var Value: \ s t r i n g ;

var DrawState: | TDecisionDrawState): boolean;

| Возвращает индексы всех полей, дан function CellValueArray(ACol, | Pu ные которых суммированы в ячейке.

ARow: Integer;

var ValueArray:

Параметр ValueArray содержит TValueArray): boolean;

индексы полей Компонент TDecisionGrid является предком класса TCustomGrid и поэтому обладает всеми базовыми свойствами и методами, присущими сетке.

Для доступа к значению каждой ячейки используется свойство ceils. Адре сация ячеек осуществляется с левой верхней ячейки, которая имеет индексы [0,0].

Свойство Dimensions является экземпляром объекта TDispiayDims, который инкапсулирует индексированный список указателей на экземпляры объек тов TDispiayDim. Каждый такой объект содержит важнейшие визуальные свойства размерностей. При щелчке на кнопке в однострочном редакторе свойства в Инспекторе объектов разворачивается список всех таких объ ектов.

Глава 30. Многомерное представление данных КомпонентTDecisionGraph Компонент TDecisionGraph создает график на основе многомерного пред ставления набора данных. Конкретный вид графика (назначение горизон тальной и вертикальной осей) зависит от настроек компонентов TDecisionCube и TDecisionPivot. По умолчанию к оси абсцисс привязывается первая вер тикальная размерность, к оси ординат — первая сумма. Первая горизон тальная размерность отображается в легенде графика.

Одним из предков компонента TDecisionGraph является класс TChart, от ко торого унаследованы все многочисленные свойства и методы для настройки графика.

Для подключения к графику набора данных используется свойство property DecisionSource: TDecisionSource;

которое ссылается на экземпляр компонента источника данных.

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

Для управления графиком во время выполнения можно использовать ком понент TDecisionPivot.

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

Взаимное положение и расположение размерностей по сторонам кросстаба никак не связано с местом полей в запросе компонента TDecisionQuery.

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

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

748 Часть VII. Технологии программирования КомпонентTDecisionPivot Компонент TDecisionPivot предоставляет пользователю средства управления размерностями многомерного представления данных. В стандартном со стоянии компонент представляет собой панель, разделенную на три части (табл. 30.6). Каждая часть имеет собственный набор кнопок.

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

Выбор поля приводит к изменению значений в ячейках кросстаба. Напри мер, выбор поля с функцией SUM изменит значения в ячейках на суммы полей, поле с функцией COUNT произведет подсчет количества элементов в полях.

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

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

Кнопка в нажатом состоянии показывает размерность. Одновременно ото бражается и общая сумма по размерности.

Размерности можно менять местами и перемещать с вертикали на горизон таль и обратно. Для этого можно выбрать команду Moved to Column Area из всплывающего меню кнопки. Во время выполнения можно использовать обычное перетаскивание кнопок при помощи мыши.

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

Таблица 30.6. Свойства и методы компонента TDecisionPivot Объявление Тип ! Описание Свойства property DecisionSource: | Pb j Определяет компонент TDecisionSource;

| | TDecisionSource, через который | | осуществляется управление многомер | ! ным представлением данных Глава 30. Многомерное представление данных Таблица 30.6 (окончание) Объявление Тип ! Описание Pb i Задает способ расположения кнопок type | на панели:

TDecisionButtonPosition = (xtHorizontal, xtVertical, 1 • xtHorizontal — в ряд слева направо;

xtLeftTop);

xtVertical — в колонку сверху вниз;

property GroupLayout:

xtLeftTop — кнопки вертикальных TDecisionButtonPosition;

| размерностей вдоль верхнего края, I кнопки горизонтальных размер | ностей вдоль левого края, кнопка i сумм в левом верхнем у л гу Pb i Управляет видимостью type | трех групп кнопок TDecisionPivotOption = (xtRows, xtColumns, xtSummaries);

TDecisionPivotOptions = set of TDecisionPivotOption;

property Groups:

TDecisionPivotOptions;

РЬ | Определяет размер в пикселах property GroupSpacing:

Integer;

I промежутка между группами кнопок Методы procedure SetBounds(Left, I Переустанавливает размеры компонента Pu Top, Height, Width: | в соответствии с параметрами метода Integer);

override;

Методы-обработчики компонента унаследованы от класса TwinControi.

Пример многомерного представления данных В качестве примера использования многомерного представления данных рассмотрим демонстрационное приложение DemoMDCube (рис. 30.5).

В качестве исходного набора данных используется запрос к таблицам SALES и CUSTOMER общедоступной базы данных EMPLOYEE.GDB в со ставе поставки InterBase следующего вида:

SELECT С.CUSTOMER, S.ORDER_DATE, SUM(S.DISCOUNT) FROM SALES S INNER JOIN CUSTOMER С ON (C.CUST_NO = S.CUST_NO) GROUP BY C.CUSTOMER, S.ORDER DATE Часть VII. Технологии программирования Запрос удовлетворяет всем требованиям для обеспечения многомерного представления данных. Для выполнения запроса и создания набора данных В проекте существует компонент DecisionQueryl.

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

С ним связан компонент DecisionCubei, для которого свойство имеет сле дующее значение:

DecisionCubei.DataSet := DecisionQueryl;

p e o D ue jDmM Cb i 1, Buttle. Griffith and Со 1. 0,1000000014Э0116 0. Central Bank 0,200000002980232 0.050000000745058 0, DT Systems. LTD.

0, Dallas Technologies 0. 0,100000001490116 0, DataSeive Internafa 0 4 0 0 0 5 Е 4 40 4 0 0 0 5 6 4 4„I.0000906. Dynamic Intelligence Рис. 30.5. Главная форма проекта DemoMDCube Компонент DecisionCubei выполняет всю работу по созданию многомерного представления набора данных компонента DecisionQueryl. Причем, практи чески все делается без вмешательства пользователя.

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

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

Глава 30. Многомерное представление данных Управление осуществляется компонентом DecisionPivoti. Основной визу альный компонент DecisionGridi представляет многомерный набор данных в табличном виде. В исходном состоянии по горизонтали расположены раз мерности номеров накладных и наименований, по вертикали размещаются размерности дат заказов и покупателей.

Дополнительного программного кода проект не имеет.

Резюме Многомерное представление данных позволяет проводить сложный анализ информации, содержащейся в базах данных. Основой многомерного пред ставления является группирующий запрос (с оператором GROUP BY). С точки зрения пользователя анализ с помощью многомерного представления дан ных очень прост в использовании.

ГЛАВА Использование возможностей Shell API Разобравшись с механизмами СОМ, вам наверняка захочется "испытать ра дость общения" с объектами, имеющимися в составе ОС. Microsoft уверенно идет к тому, чтобы все составные части своих операционных систем, как и прочих продуктов, превратить в СОМ-объекты. В этом направлении сдела ны большие шаги, и оболочка Windows, и ее файловая система предостав ляют интерфейсы СОМ. В Windows 2000, судя по заверениям представите лей фирмы, все новые возможности представлены и доступны в виде интер фейсов.

В качестве примера работы с интерфейсом ShellLink вместе с Delphi постав ляется приложение Virtual ListView. Но, во-первых, в нем безо всякого до кументирования вводятся достаточно сложные структуры и интерфейсы;

во вторых, оно содержит только минимум функций для работы с объектами.

В этой главе мы постараемся объяснить применяемые там приемы.

( Примечание ~") Интерфейсы функций и СОМ-объектов s h e l l содержатся в модулях SHELLAPI.PAS и SHLOBJ.PAS, которые имеются в поставке Delphi.

Понятие пространства имен Необходимость как-то упорядочить все те сущности, с которыми имеет дело современная ОС, всегда вставала перед разработчиками. Довольно успеш ный подход к этому реализован в платформе Windows. Вооружившись идея ми объектного подхода, в Microsoft разбили интерфейс ОС на две части:

средства поддержки пространства имен и средства его просмотра.

Под пространством имен оболочки (Shell Namespace) мы будем понимать иерархически упорядоченную совокупность имен всех объектов, которые Глава 31. Использование возможностей Shell API могут быть просмотрены через средства просмотра — файлы, устройства памяти, принтеры, сетевые ресурсы. В этой совокупности могут встречаться как реально существующие объекты (папки файловой системы), так и вир туальные объекты (папки Принтеры, Мой компьютер и т. п.). Типовым средством просмотра пространства имен является Explorer (Проводник), но можно заменить его на другое средство, в том числе собственноручно раз работанное. Обе составные части являются совокупностями СОМ-объектов, они обладают полиморфизмом и легко расширяемы. Об использовании этих объектов и функций API оболочки ОС и пойдет речь в данной главе.

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

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

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

function Shell_NotifyIcon(dwMessage: DWORD;

ipData: PNotifylconData): BOOL;

PNotifylconData = TNotifylconData;

TNotifylconData = record cbSize: DWORD;

Wnd: HWND;

uID: UINT;

uFlags: UINT;

uCallbackMessage: UINT;

hlcon: HICON;

szTip: array [0..63] of AnsiChar;

end;

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

25 Зак. 754 Часть VII. Технологии программирования Ход операции зависит от того, какие поля структуры TNotifyiconData будут заполнены.

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

( Примечание ^) Изменить главный значок приложения можно в диалоговом окне Project/ Options на странице Application. Он будет доступен через свойство A p p l i c a t i o n, icon. Тут же можно отредактировать и строку для подсказки — свойство A p p l i c a t i o n. T i t l e.

Наконец, в поле uFiags вы должны сообщить системе, что именно вы от нее хотите, ИЛИ, другими словами, какие ИЗ полей hlcon, uCailbackMessage и szTip вы на самом деле заполнили. В этом поле предусмотрена комбинация трех флагов: NIF_ICON, NIF_MESSAGE И NIFJTIP. ВЫ можете заполнить, скажем, поле szTip, но если вы при этом не установили флаг NIFJTIP, созданный вами значок не будет иметь строки с подсказкой.

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

const WM_MYTRAYNOTIFY = WM_USER + 123;

procedure TForml.CreateTraylcon(n:Integer) ;

var nidata : TNotifyiconData;

begin with nidata do begin cbSize := SizeOf(TNotifyiconData) ;

Wnd := Self.Handle;

uID : n;

= uFiags : NIF_ICON or NIF_MESSAGE or NIFJTIP;

= uCallBackMessage : WM_MYTRAYNOTIFY;

= hlcon := Application.Icon.Handle;

szTip := 'THis is Traylcon Example';

end;

Глава 31. Использование возможностей Shell API Shell_NotifyIcon(NIM_ADD, @nidata);

end;

procedure TForml.DeleteTraylcon(n:Integer);

var nidata : TNotifylconData;

begin with nidata do begin cbSize := SizeOf(TNotifylconData);

Wnd := Self.Handle;

uID := n;

end;

Shell_NotifyIcon(NIM_DELETE, Snidata);

end;

Примечание He забывайте уничтожать созданные вами значки на System Tray. Это не де лается автоматически даже при закрытии приложения. Значок будет удален только после перезагрузки системы.

Внешний вид значка, помещенного нами на System Tray, ничем не отлича ется от значков других приложений (рис. 31.1).

Рис. 31.1. Над значком, помещенным на панель System Tray, видна строка подсказки Сообщение, задаваемое в поле uCaiibackMessage, по сути дела является единственной ниточкой, связывающей вас со значком после его создания.

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

procedure TForml.WMICON(var msg: TMessage);

var P : TPoint;

begin case msg.LParam of 756 Часть VII. Технологии программирования WM_LBUTTONDOWN:

begin GetCursorPos(p);

SetForegroundWindow(Application.MainForm.Handle);

PopupMenul. Popup (P. X, P. Y) ;

end;

WM_LBUTTONUP :

end;

end;

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

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

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

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

procedure TForml.HideMainForm;

begin Appiication.showMainForm := False;

ShowWindow(Application.Handle, SWHIDE) ;

ShowWindow(Application.MainForm.Handle, SW_HIDE);

end;

procedure TForml.RestoreMainForm;

var i,j : Integer;

begin Appiication.showMainForm := True;

Глава 31. Использование возможностей Shell API ShowWindow(Application.Handle, SW_RESTORE);

ShowWindow(Application.MainForm.Handle, SW_RESTORE);

if not ShownOnce then begin for I := 0 to Application.MainForm.ComponentCount -1 do if Application.MainForm.Components[I] is TWinControl then with Application.MainForm.Components[I] as TWinControl do if Visible then begin ShowWindow (Handle, SWSHOWDEFAULT) ;

for J := 0 to ComponentCount -1 do if Components[J] is TWinControl then ShowWindow((Components[J] as TWinControl).Handle, SW_SHOWDEFAULT);

end;

ShownOnce := True;

end;

end;

procedure TFormi.WMSYSCOMMAND(var msg: TMessage);

begin inherited;

if (Msg.wParam=SC_MINIMIZE) then begin HideMainForm;

CreateTraylcon(l) ;

end;

end;

procedure TFormi.FileOpenltemlClick(Sender: TObject);

begin RestoreMainForm;

DeleteTraylcon(l);

end;

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

758 Часть VII. Технологии программирования ИнтерфейсIShellLink Этот интерфейс представляет собой средство для создания и управления ярлыками (shortcuts). Все читатели этой главы наверняка создавали и пере мещали ярлыки для наиболее нужных программ, файлов и папок — на ра бочем столе, в главном меню и т. д. С точки зрения ОС эти действия — не что иное, как создание и изменение свойств СОМ-объекта.

Каждый ярлык содержит следующую информацию:

• путь к объекту, на который ссылается ярлык (Path);

• рабочий каталог для этого объекта (Working Directory);

• список параметров, передаваемый объекту при его активизации (Arguments);

• начальное состояние окна, соответствующего объекту (нормальное, ми нимизированное, максимизированное) (ShowCmd);

• путь к значку, соответствующему объекту (Icon Location);

• описание объекта (Description);

П сочетание "горячих" клавиш (HotKey).

Для всех этих свойств ярлыка в интерфейсе дано по паре методов — один для чтения, другой для установки значения:

IShellLink = interface(IUnknown) { si } [SID_IShellLinkA] function GetPath(pszFile: PAnsiChar;

cchMaxPath: Integer;

var pfd: TWin32FindData;

fFlags: DWORD): HResult;

stdcall;

function GetlDList(var ppidl: PItemlDList): HResult;

stdcall;

function SetlDList(pidl: PItemlDList): HResult;

stdcall;

function GetDescription(pszName: PAnsiChar;

cchMaxName: Integer):

HResult;

stdcall;

function SetDescription(pszName: PAnsiChar): HResult;

stdcall;

function GetWorkingDirectory(pszDir: PAnsiChar;

cchMaxPath: Integer):

HResult;

stdcall;

function SetWorkingDirectory(pszDir: PAnsiChar): HResult;

stdcall;

function GetArguments(pszArgs: PAnsiChar;

cchMaxPath: Integer):

HResult;

stdcall;

function SetArguments(pszArgs: PAnsiChar): HResult;

stdcall;

function GetHotkey(var pwHotkey: Word): HResult;

stdcall;

function SetHotkeytwHotkey: Word): HResult;

stdcall;

function GetShowCmd(out piShowCmd: Integer): HResult;

stdcall;

function SetShowCmd(iShowCmd: Integer): HResult;

stdcall;

function GetIconLocation(pszIconPath: PAnsiChar;

cchlconPath: Integer;

out pilcon: Integer): HResult;

stdcall;

Глава 31. Использование возможностей Shell API function SetlconLocationfpszIconPath: PAnsiChar;

ilcon: Integer):

HResult;

stdcall;

function SetRelativePath(pszPathRel: PAnsiChar;

dwReserved: DWORD):

HResult;

stdcall;

function Resolve(Wnd: HWND;

fFlags: DWORD): HResult;

stdcall;

function SetPath(pszFile: PAnsiChar): HResult;

stdcall;

end;

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

procedure TForml.ButtonlClick(Sender: TObject);

var NewLink : IShellLink;

fn, fp : string;

ws : WideString;

hRes : THandle;

pf : IPersistFile;

begin NewLink := CreateComObject(CLSID_ShellLink) as IShellLink;

fn := ParamStr(O);

NewLink.SetPath(pchar(fn));

fp : ExtractFilePath(fn);

= NewLink.SetWorkingDirectory(pchar(fp));

NewLink.SetDescription(pChar(Application.Title));

ws := fp+Application.Title+'.Ink';

hRes := NewLink.Querylnterface(IID_IPersistFile, pf) ;

if Succeeded(hRes) then pf.Save(pWideChar(ws), False);

end;

В этом примере помимо IShellLink нужно получить доступ к интерфейсу IPersistFile, который "умеет" записывать данные. Задав параметры ярлыка, мы записываем его на диск. При этом проверяется тот факт, что созданный нами объект поддерживает интерфейс IPersistFile. Если указатель на этот интерфейс получен, вызывается его метод save.

Среди перечисленных выше методов IShellLink особое внимание уделим методу Resolve. Он понадобится вам при получении указателя на интерфейс уже существующих ярлыков. Windows пытается вести себя "разумно" и от слеживает перемещения и переименования объекта, на который указывает существующий IShellLink. Но если вы записали содержимое ярлыка в по ток (или на диск), то отследить соответствие ярлыка объекту должны сами, вызвав метод Resolve. Если объект, на который ссылается ярлык, по прежнему находится на своем месте, метод немедленно завершается с нор Часть VII. Технологии программирования мальным кодом возврата. Если файл или объект перемещен или переимено ван, начинается его поиск (рис. 31.2).

Ms i g S ot u i sn h rc t Wd w is s ac i g for S m A pee to:::' no s e r h i n o e p.x.

locate the file yourself, click Browse.;

: : i л ;

:

-Cancel' Bo s..

r w e, Рис. 31.2. Поиск объекта, на который указывает ярлык Знакомая картина, не правда ли? Особенно часто она наблюдается в том случае, если пользователь не выработал у себя привычки правильно деин сталлировать раздобытый где-то "софт", стирая его "по старинке". Между тем, за привычным диалоговым окном на рисунке стоит вызов метода isheiiLink. Resolve. Если в пределах досягаемости поиска окажется файл с тем же именем и размерами, ярлык будет автоматически переадресован на него;

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

Если вы внимательно изучили рабочий стол своего компьютера, то должны были заметить там ярлыки, ссылающиеся не на файлы, а на специальные объекты — "Мой компьютер", "Сетевое окружение", "Принтеры" и т. п. Что бы создать такой ярлык самому, нужно обращение к методу setiDList.

В качестве параметра ему передается структура PitemiDList (pidi). О том, где ее взять и как заполнить, рассказано в следующем разделе.

Интерфейс IShellFolder Этот интерфейс соответствует папке — одному из основных элементов про странства имен Проводника. Зачем было вводить термин "папка", когда су ществовали уже общепринятые "каталог" и "директория"? В отличие от по следних двух, папка может быть не просто обычным элементом файловой системы. Она может быть виртуальной — как папки Принтеры, Документы или Панель управления. Любая папка может содержать коллекцию объектов из состава пространства имен.

Получив указатель на интерфейс isheiiFoider, соответствующий папке, вы можете работать с ней, как с объектом СОМ. "Верхушкой" (корневой пап Глава 31. Использование возможностей Shell API кой) пространства имен является папка Рабочий стол (Desktop). Получить интерфейс isheiiFoider этой папки можно путем вызова функции:

function SHGetDesktopFolder(var ppshf: isheiiFoider): HResult;

Логика работы с описываемым интерфейсом такова: сначала необходимо получить интерфейс нужной папки, а затем можно переходить к работе с ее содержимым. Содержимое представляет собой список, а каждый элемент папки представлен структурой pitemiDList. Эта структура не типизирована;

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

( Примечание ^ Все служебные функции работы со структурами PitemiDList — создание, уничтожение, копирование, перемещение по цепочке и т. п. — содержатся в примере Virtual ListView, поставляемом с Delphi. Если вы намерены писать про граммы, работающие с i s h e i i F o i d e r, целесообразно взять их на заметку.

В дальнейшем для простоты эти структуры будем именовать p i d i.

Рассмотрим функции интерфейса isheiiFoider. Под "текущей папкой" в табл. 31.1 понимается та папка, которая в данный момент представляет интерфейс IShellFolder.

Таблица 31.1. Функции интерфейса isheiiFoider Описание Метод Эта функция позволяет получить function ParseDisplayName(hwndOwner:

указатель на элемент ppidl, зная HWND;

pbcReserved: Pointer;

только его полное имя (с путем) lpszDisplayName: POLESTR;

out pchEaten: ULONG;

out ppidl: lpszDisplayName PitemiDList;

var dwAttributes:

ULONG): HResult;

Возвращает указатель на специаль function EnumObjects(hwndOwner: HWND;

ный интерфейс IEnumlDList, пред grfFlags: DWORD;

out EnumlDList:

назначенный для организации цикла IEnumlDList): HResult;

по всем элементам списка в текущей папке function BindToObject(pidi: Возвращает интерфейс папки pidi, PitemiDList;

pbcReserved: Pointer;

которая должна находиться в теку const riid: TIID;

out ppvOut: щей папке (на которую ссылается Pointer): HResult;

интерфейс, вызвавший этот метод) Сравнивает два первых элемента function ComparelDs(lParam: LPARAM;

в списках pidll и pidl pidll, pidl2: PitemiDList): HResult;

762 Часть VII. Технологии программирования Таблица 31.1 (окончание) Метод Описание Создает визуальный объект для function CreateViewObject(hwndOwner:

текущей папки и возвращает указа HWND;

const riid: TIID;

out ppvOut:

тель на него в параметре ppvOut Pointer): HResult;

Возвращает атрибуты элемента function GetAttributesOf(cidl: UINT;

под номером cidl в списке apidl.

var apidl: PItemlDList;

Результат — набор флагов, устанав var rgflnOut: UINT): HResult;

ливаемых в параметре rgfinOut Создает объект пользовательского function GetUIObjectOf(hwndOwner:

интерфейса, связанный с элементом HWND;

cidl: UINT;

var apidl:

списка aplidl под номером cidl PItemlDList;

const riid: TIID;

prgflnOut: Pointer;

out ppvOut:

Pointer): HResult;

Возвращает имя элемента pidl.

function GetDisplayNameOf(pidl:

Полнота возвращаемой информации PItemlDList;

uFlags: DWORD;

var lpName: TStrRet): HResult;

определяется параметром uFlags Задает новое имя lpszName для function SetNameOf(hwndOwner: HWND;

pidl: PItemlDList;

lpszName: списка pidl. При этом возвращается POLEStr;

uFlags: DWORD;

новый указатель на список — var ppidlOut: PItemlDList): HResult;

ppidlOut Два метода — ParseDisplayName И GetDisplayNameOf — взаимно ДОПОЛНЯЮТ друг друга. Первый из них нужен, если вы имеете указатель на isheiiFoider и хотите связать его с конкретной папкой. На практике это сводится к зада че в три действия:

1. Получить указатель на интерфейс какой-либо папки, скажем, рабочего СТОЛа, При ПОМОЩИ ShGetDesktopFolder.

2. Получить указатель (pidl) нужного вам элемента. Это осуществимо мно гими способами. Первый из них — как раз через вызов метода IShellFolder. ParseDisplayName. Если ВЫ хотите получить доступ К ОДНОЙ из виртуальных (специальных) папок, то незаменимой будет следующая функция:

function SHGetSpecialFolderLocation(hwndOwner: HWND;

nFolder: Integer;

var ppidl: PItemlDList): HResult;

В параметре nFolder вы задаете константу, соответствующую выбранной специальной папке. На выходе будет указатель на элемент ppidl, соот ветствующий этой папке.

Глава 31. Использование возможностей Shell API Примечание Во многих функциях Shell API и методах его интерфейсов встречается пара метр hwndOwner. Он должен задавать дескриптор окна на тот случай, если при дется выводить диалоговое окно или окно с сообщением об ошибке.

Возможные значения параметра nFoider перечислены в табл. 31.2. В ком ментариях к ним "виртуальная" папка является особым объектом, кото рый предоставляется пользователю при помощи Shell API. Просто "пап ка" реально существует где-то в файловой системе.

Таблица 31.2. Константы, определяющие специальные папки Значение Комментарий Корзина (Recycle bin) — специальная папка для CSIDL BITBUCKET удаленных файлов. Пути к Recycle bin нет в сис темном реестре во избежание перемещения или удаления, и его не узнать иным методом Панель инструментов (Control Panel) — виртуаль CSIDL CONTROLS ная папка, содержащая значки апплетов Панели инструментов Виртуальная папка Рабочий стол (Desktop), кор CSIDL DESKTOP невая в пространстве имен Папка файловой системы, реально содержащая CSIDL DESKTOPDIRECTORY объекты рабочего стола Виртуальная папка Мой компьютер (My Computer), CSIDL DRIVES содержащая элементы для всех накопителей на компьютере подключенных сетевых устройств, пап ки Принтеры, Панель инструментов, Удаленный доступ к сети Виртуальная папка Шрифты CSIDL_FONTS Папка, содержащая объекты сетевого окружения CSIDL_NETHOOD Виртуальная папка Сетевое окружение CSIDL_NETWORK (Network Neighborhood) CSIDL_PERSONAL Папка Мои документы CSIDL_PRINTERS Виртуальная папка Принтеры (Printers) CSIDL_PROGRAMS Папка Программы из главного меню, содержащая папки установленных на компьютере программ Папка, содержащая ссылки на последние исполь CSIDL_RECENT зовавшиеся документы (Recent) Папка, содержащая элементы контекстного меню CSIDL SENDTO Send To...

764 Часть VII. Технологии программирования Таблица 31.2 (окончание) Значение Комментарий Папка, содержащая элементы главного меню Пуск CSIDL STARTMENU (Start) Папка, содержащая элементы меню Автозапуск CSIDL STARTUP (Startup) Папка, содержащая шаблоны типовых документов CSIDL TEMPLATES Третий вариант получить pidi нужной папки — интерактивный, с по мощью функции Shell API.

function ShBrowseForFolder(var lpbi: TBrowselnfo): PItemlDList;

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

ЩЩ\ browse for Folder M:

В-ЩЗ Desktop !- g | My Computer "П '+ -j 3 5 Floppy (A:) —';

• 1- 1 |ЯЯ|ЙИЯ«Я1 ':

(> _J Documents and Settings • r^ _J Inetpub t ^ '_J Inprise : ::'• 1, _ll Mark •' ' t _ j MSSQL7 ••' •• • < H _J Petr :

[4 _lj Program Files t W l"3 0FrvriFn. JZS t ^ i i " 1 •• • : л. • " ' '••. •..• ^ : ' -. ' -, •.•.. •.Г'.. • • • • • • • • : •...

O K |, C a n c e l :: | Рис. 31.3. Диалоговое окно выбора папки, созданное при вызове функции ShBrowseForFolder В данном примере корневой служит виртуальная папка My Computer.

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

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

Глава 31. Использование возможностей Shell API procedure TForml.ButtonlClick(Sender: TObject);

var BI : TBrowselnfo;

Image : integer;

StartPIDL, ResPIDL : PltemlDList;

S, Path : Array[0..max_path-l] Of WideChar;

begin 01eCheck(SHGetSpecialFolderLocation(Handle, CSIDL_DRIVES, StartPIDL));

With BI do Begin = Application.Handle;

hwndOwner • pszDisplayName = @S;

= 'Выберите необходимую папку';

lpszTitle = BIF_RETURNONLYFSDIRS;

ulFlags = StartPIDL;

pidlRoot = nil;

lpfn iImage end;

ResPIDL := SHBrowseForFolder(BI);

if SHGETPathFromlDList(ResPIDL, @Path[0]) then Label1.Caption := StrPas(@Path[0]);

end;

Полученное имя здесь отображается при помощи компонента Labeii.

3. Наконец, перейдем к третьему действию нашей задачи. Теперь, зная pidi папки, с которой вы будете работать, можно получить указатель на ин терфейс isheiiFoider вызовом метода BindToObject. Мы еще не рассмот рели такой важный аспект работы с папками, как просмотр их содержи мого. Верные правилу СОМ: "каждый должен заниматься своим делом", разработчики Shell предоставили для просмотра еще один интерфейс — iEnumiDList. Пугаться нечего, набор возможностей этого интерфейса да же меньше, чем у пульта ДУ в магнитофоне. Его четыре метода — Next, skip, Reset и clone — позволяют организовать просмотр списка в одном направлении, а также возврат к началу и дублирование (Clone) выбран ного элемента списка. Вот как это выглядит на практике.

Memol.Clear;

try 01eCheck(SHGetDesktopFolder(DeskTop));

if not Succeeded(DeskTop.ParseDisplayName(Self.Handle,nil, StringToWideChar (Editl.Text,ws, MAX_PATH),n, pidl, attr)) 766 Часть VII. Технологии программирования then begin ShowMessage('Неизвестное имя');

Exit;

end;

OleCheck(DeskTop.BindToObject(pidl,nil, IID_IShellFolder, Pointer(NewShellFolder)));

OleCheck(NewShellFolder.EnumObj ects(Self.Handle, SHCONTF_FOLDERS or SHCONTF_NONFOLDERS, Enumerator));

while Enumerator.Next(1, pidl, Numpidls) = S_OK do begin NewShellFolder.GetDisplayNameOf(PIDL, SHGDN_FORPARSING, StrRet);

case StrRet.uType of STRRET_CSTR:

s := StrRet.cStr;

STRRET_OFFSET:

begin P := @PIDL.mkid.abID[StrRet.uOffset - SizeOf(PIDL.mkid.cb)];

SetString(s, P, PIDL.mkid.cb - StrRet.uOffset);

end;

STRRET_WSTR:

s := StrRet.pOleStr;

end;

//case Memol.Lines.Add(s);

end;

except on E:E01eSysError do ShowMessage('');

end;

В этом примере имя нужной папки извлекается из компонента Editi.

Получив указатель на интерфейс isheiiFoider и затем интерфейс iEnumiDList, программа заполняет полученными именами файлов список Memol.Lines.

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

Способов для достижения этой цели несколько. Самый простой — через вызов функции:

function SHGetFilelnfofpszPath: PAnsiChar;

dwFileAttributes: DWORD;

var psfi: TSHFilelnfo;

cbFilelnfo, uFlags: UINT): DWORD;

Параметр pszPath может быть указателем как на строку с именем файла, так и на структуру вида pidl. Функция заполняет структуру psfi (тип TSHFilelnfo) длиной cbFilelnfo байт. В зависимости от значения слова фла гов (параметр uFlags) на выходе может быть разнообразная информация.

В частности, если в параметре uFlags заданы значения SHGFI_SYSICONINDEX И SHGFI_ICON, то в структуру psfi будет записан номер значка для данного Глава 31. Использование возможностей Shell API файла в системном списке изображений, а результатом выполнения функ ции будет дескриптор этого списка. Воспользоваться им можно (например, для панели инструментов) так:

procedure TForml.FormCreate(Sender: TObj ect);

var Filelnfo: TSHFilelnfo;

ImageListHandle: THandle;

begin ImageListHandle := SHGetFilelnfo('С:\', 0, Filelnfo, SizeOf(Filelnfo), SHGFI_SYSICONINDEX or SHGFI_ICON);

SendMessage(ToolBarl.Handle, TB_SETI№\GELIST, 0, ImageListHandle);

end;

Точно так же можно извлечь значок, соответствующий конкретному файлу.

В составе Shell есть другие функции, созданные для извлечения значков:

• function Extractlcon(hlnst: HINST;

lpszExeFileName: PChar;

nlconlndex: UINT): HICON;

Эта функция извлекает значок из файла lpszExeFileName (это должен быть файл типа EXE, DLL или ICO) и возвращает его дескриптор. Если значок не найден, возвращаемое значение равно 0.

О function ExtractAssociatedlconfhlnst: HINST;

lpIconPath: PChar;

var lpilcon: Word): HICON;

Эта функция может работать с файлами разных форматов. Сначала она, как и предыдущая, ищет значок в теле файла. Если его там нет, пред принимается попытка отыскать значок в приложении, связанном с дан ным типом файлов. Например, из файла с расширением doc будет из влечен один из значков Microsoft Word.

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

Эта возможность предоставляется оболочкой Windows.

Когда пользователь щелкает правой кнопкой мыши на любом объекте в пространстве имен, система создает контекстное меню из двух частей: стан дартного меню для объектов данного типа и пунктов меню, добавляемых Часть VII. Технологии программирования зарегистрированными обработчиками. Зарегистрированные обработчики — это СОМ-серверы, запускаемые в адресном пространстве процесса (in process servers) и реализованные в виде динамических библиотек.

Ваш СОМ-объект, который расширяет системное контекстное меню, дол жен поддерживать как минимум два интерфейса — isheliExtinit и iContextMenu. Существуют и два новых интерфейса — icontextMenu2 и icontextMenu3, но они вносят в логику работы контекстных меню лишь не большие дополнения и здесь рассмотрены не будут. Интерфейс isheliExtinit отвечает за инициализацию меню, а интерфейс IContextMenu — за выполне ние основных функций.

Методы интерфейса IContextMenu приведены в табл. 31.3.

Таблица 31.3. Методы интерфейса IContextMenu Метод Описание Добавляет пункт к системному function QueryContextMenu (Menu: H E U MN ;

контекстному меню indexMenu, idCmdFirst, idCmdLast, uFlags: UINT): HResult;

s t d c a l l ;

Осуществляет вызов обработчика function InvokeCommand(var l p i c i :

TCMInvokeCoinmandlnfo) : HResult;

stdcall;

Возвращает описание добавлен function GetCommandString(idCmd, ного пункта меню (подсказку или uType: UINT;

pwReserved: PUINT;

полное название) PszName: LPSTR;

cchMax: UINT):

HResult;

s t d c a l l ;

Рассмотрим их подробнее. Параметры метода QueryContextMenu означают следующее:

• Menu — дескриптор системного меню;

П indexMenu — позиция в меню, в которую следует вставить пункт (пункты);

• idCmdFirst, idCmdLast — диапазон допустимых значений для идентифика торов вставляемых пунктов меню;

• uFlags — набор флагов, главные из которых означают:

• CMF_NORMAL — обычный вызов контекстного меню, пункты могут быть добавлены. Значение этого флага нулевое, проверять его следует, очи стив все биты в параметре uFlags, кроме пяти младших (маска $1F);

• CMF_DEFAULTONLY — устанавливается, если пользователь задал с объек том действие по умолчанию (например, двойной щелчок). В этом слу чае пункты меню добавляться не должны;

Глава 31, Использование возможностей Shell API • CMF_VERBSONLY — устанавливается, если меню создается для ярлыка объекта, а не для самого объекта. В этом случае многие пункты меню создаваться не должны;

• CMF_EXPLORE — устанавливается, если меню создается для объекта, на ходящегося на левой панели Проводника.

Для иллюстрации объектов — расширений контекстного меню — выберем пример ContMenu (поставляется с Delphi в папке DEMOS\ACTIVEX \SHELLEXT). В этом примере для объектов типа "проект Delphi" добавляет ся возможность запуска компилятора в командной строке. При вызове метода QueryContextMenu нужный пункт добавляется с помощью функции InsertMenu:

function TContextMenu.QueryContextMenu(Menu: HMENU;

indexMenu, idCmdFirst, idCmdLast, uFlags: UINT): HResult;

begin Result := 0;

// или использовать MakeResult(SEVERITY_SUCCESS, // FACILITY_NULL, 0);

if ((uFlags and $0000000F) = CMF_NORMAL) or ((uFlags and CMF_EXPLORE) <> 0) then begin // Добавить один пункт меню во всплывающее меню InsertMenu(Menu, indexMenu, MF_STRING or MF_BYPOSITION, idCmdFirst, 'Compile...');

Result := 1;

// или использовать MakeResult(SEVERITY_SUCCESS, // FACILITY_NULL, 1) end;

end;

Метод Getcommandstring предоставляет системе данные о пункте меню, в частности, текст подсказки;

эта подсказка будет отображаться в строке состояния Проводника, когда курсор находится в нужном месте меню.

Параметры Getcommandstring просты. Первый — idcmd — соответствует иден тификатору пункта меню, второй — итуре — запрос на тип информации (GCS_HELPTEXT — текст подсказки, GCS_VERB — полное название пункта ме ню). Наконец, параметры pszName и cchMax задают буфер, в который будут копироваться текстовые данные. Полное название необходимо системе, чтобы с его помощью вызывать предусмотренные в пункте действия про граммно. В примере ContMenu возврат названия (т. е. обработка запроса GCS_VERB) не предусмотрен, а в ответ на запрос GCS_HELPTEXT возвращается текстовая строка "Compile the selected Delphi project".

Наиболее сложным является метод invokeCommand. Он вызывается при выбо ре пользователем вставленного вами пункта меню. По сути дела метод invokeCommand представляет собой прямой аналог обработчика onclick обыч ных пунктов меню (объектов TMenuitem) в Delphi.

770 Часть VII. Технологии программирования Единственным параметром метода является структура типа TCMinvoke coramandinfo, поля которой имеют такое предназначение:

• cbsize — размер структуры в байтах;

• hwnd — задает дескриптор окна, которое будет владельцем диалоговых окон, вызываемых из метода;

• fMask — определяет, заданы ли параметры dwHotkey/hicon;

• lpverb — вызываемая команда;

• lpParameters — параметры (если есть);

• lpDirectory — рабочая папка (поле не обязательно);

П nshow — флаг состояния окна, который будет передан в функцию ShowWindow (SW_*);

• dwHotKey — "горячая" комбинация клавиш, которая будет сопоставляться приложению, запускаемому из этого пункта меню (только если в пара метре fMask установлен флаг CMIC_MASK_HOTKEY);

• hicon — значок, который будет сопоставляться приложению, запускае мому из этого пункта меню (только если в параметре fMask установлен флаг CMIC_MASK_ICON);

• hMonitor — монитор по умолчанию (поле не обязательно).

Отдельно следует остановиться на описании параметра lpverb. Как уже го ворилось, он может представлять из себя как идентификатор пункта меню, так и его текст — строку, заканчивающуюся нулем. Чтобы выяснить это, нужно проверить старшее слово этого 32-разрядного параметра на равенство нулю. В примере ContMenu вызов по тексту не предусмотрен:

if (HiWord(Integer(lpici.lpVerb)) о 0) then begin Exit ;

end;

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

TContextMenu = class(TComObject, IShellExtlnit, IContextMenu) private FFileName: array[0..MAX_PATH] of Char;

protected { IShellExtlnit } Глава 31. Использование возможностей Shell API function IShellExtlnit.Initialize = SEIInitialize;

function SEIInitialize(pidlFolder: PltemlDList;

lpdobj: IDataObject;

hKeyProgID: HKEY): HResult;

stdcall;

{ IContextMenu } function QueryContextMenu(Menu: HMENU;

indexMenu, idCmdFirst, idCmdLast, uFlags: UINT): HResult;

stdcall;

function InvokeCommand(var lpici: TCMInvokeCommandlnfo): HResult;

stdcall;

function GetCommandString(idCmd, uType: UINT;

pwReserved: PUINT;

pszName: LPSTR;

cchMax: UINT): HResult;

stdcall;

end;

Вас может насторожить конструкция, описывающая переименование метода i n i t i a l i z e интерфейса IShellExtlnit. На самом деле одноименный метод имеется у объекта TComObject, и приведенный синтаксис как раз и предна значен для выхода из подобных ситуаций.

Последняя часть работы — регистрация созданного обработчика. Самое подходящее место для этого — метод updateRegistry фабрики класса. Разра ботчики примера ContMenu ПОРОДИЛИ КЛаСС TContextMenuFactory, КОТОрЫЙ при регистрации СОМ-сервера регистрирует создаваемые фабрикой объекты:

ClassID := GUIDToString(Class_ContextMenu);

CreateRegKey('DelphiProjectXshellex', '', ! I ) ;

CreateRegKey('DelphiProject\shellex\ContextMenuHandlers', '', '') ;

CreateRegKey('DelphiProject\shellex\ContextMenuHandlers\ContMenu', '', ClassID);

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

Резюме Несколько тем, затронутых в этой главе, могут дать лишь начальные пред ставления о принципах работы с оболочкой Windows. Вы можете изучить составляющие ее объекты практически сколь угодно глубоко — б-ьгли бы потребность да желание. Разумеется, чтобы не "наломать дров", перед этим надо отдать себе отчет в полном и правильном понимании механизмов СОМ.

ПРИЛОЖЕНИЕ Описание дискеты Уважаемый читатель!

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

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

Некоторые приложения используют в качестве источника данных демонст рационную базу данных Delphi, которая при стандартной инсталляции на ходится в папке \Program Files\Common Files\Barland Shared\Data. При ра боте с такими приложениями вам необходимо самостоятельно настроить соответствующие свойства компонентов доступа к данным. Они преднаме ренно обнулены, т. к. на вашем компьютере база данных Delphi может рас полагаться в другом месте.

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

Ниже приводится описание демонстрационных приложений.

Папка Приложение 05_1 Пример использования компонентов TTreeView и TTreeList для про смотра информации из системного реестра Windows 05_2 Простой пример разработки собственного компонента 06_1 Ресурсы манифеста Windows XP 07_1 Пример использования в приложении списка объектов на основе класса TList 774 Приложение (продолжение) Папка Приложение 07_2 Пример использования в приложении списка строк на основе класса TStringList 10_1 Пример приложения, использующего компоненты Delphi для отображе ния графики 10_2 Приложение для просмотра растровых приложений JPG, JPEG, BMP 11_1 Пример простейшего приложения баз данных 12_1 Пример использования параметров компонентов запросов SQL и взаи модействия таких компонентов на основе передачи значений пара метров 14_1 Приложение баз данных, использующее отношение "один-ко-многим" между таблицами базы данных 14_2 Приложение баз данных, демонстрирующее варианты поиска записей в таблице базы данных 14_3 Пример использования закладок (класс TBookmark) в наборах данных Delphi 15_1 Приложение баз данных, использующее компоненты синхронного про смотра 16_1 Приложение баз данных, напрямую использующее API BDE для полного удаления записей из таблиц базы данных 16_2 Приложение баз данных, напрямую использующее API BDE для пред ставления данных 17_1 Приложение баз данных, демонстрирующее возможности технологии dbExpress 19_1 Приложение баз данных, демонстрирующее возможности технологии ADO 21_1 Пример простого распределенного приложения баз данных 25_1 Пример использования компонента проекта отчета Rave Reports и раз работки простых отчетов в визуальной среде Rave Reports 26_1 Пример использования настраиваемого соединения на основе компо нента TRvCustomConnection 26_2 Пример отчетов Rave Reports для приложений баз данных 27_1 Пример реализации Drag-and-Drop 27_2 Пример реализации Drag-and-Dock 27_3 Пример реализации управления мышью 28_1 Пример создания динамической библиотеки Описание дискеты (окончание) Папка Приложение 29_1 Приложение, использующее отдельный поток к памяти для расчета числа л 30_1 Приложение, использующее компоненты многомерного представления данных 31 _1 Пример приложения, использующего Shell API Предметный указатель dynamic 28, А Dynamic Method Table Abort E Action Action client Action target 175 EAbort ADO 483 EAssertionFailed API BDE 392 EFCreate Error as 39 EFOpenError EInOutError ElntEnor В EReadError EReconcileError BDE Event brcc32.exe EWriteError ExceptAddr С Exception ExceptObject Comctl32.dll ConnectionString 492 • Critical section IAccessor D IAppServer 555, 558, IColumnsInfo DataSnap 570, 575 ICommand dbExpress 424 ICommandPrepare default 22 ICommandProperties Device Dependent Bitmap 239 ICommandText Device Independent Bitmap 239 ICommandWithParameters DLL PROCESS ATTACH 691 IConvertType DLL PROCESS DETACH 691 IDBCreateSession DLL THREAD ATTACH 691 IDBInitialize DLL_THREAD_DETACH 692 IDBProperties Drag-and-Dock 669 inherited 19, Drag-and-Drop 664 IOpenRowset Предметный указатель IOResult 69 С IProviderSupport 558, IRowset 488 Self 17, IRowsetChange 489 Semaphore IRowsetldentity 489 Sender IRowsetlndex 489 ShowException IRowsetlnfo 488 SQL Links IRowsetLocate IRowsetUpdate T is IShellFolder TAction 175, IShellLink TActionList ISourcesRowset TADOCommand ISQLCommand TADOConnection ISQLConnection TADODataSet ISQLCursor TADOQueiy ISQLDriver TADOStoredProc ITransaction TADOTable ITransactionJoin TAnimate ITransactionLocal TBDEDataSet ITransactionObject TBitmap TBrush J TCanvas 63, TCaption JPEG 243 TChart TClass M TClientDataSet 572, IWI TClipboard TCollection Microsoft ActiveX Data Objects TCollectionltem Mutex TColor TColumn О TCommonCalendar TComponent OLE DB TConnectionBroker OnException 75 TControl overload 34 TControlState override 33 TControlStyle TCORBADataModule P TCubeDim TCubeDims private 35 TCursor property 20 TCustomADODataSet protected 36 TCustomClientDataSet public 35 TCustomControl published 36 TCustomSQLDataSet TCustomTabControl TDatabase R TDataSet279, 281, 305, TDataSetField raise 68, 72, TDataSetProvider 545, 554, reintroduce 778 Предметный указатель TDataSource 268, 269, 274, 300, 371 TJPEGImage TDateTimePicker 127 TList TDBChart 372 TListView TDBCheckBox 365 TLocalConnection TDBComboBox 366 TMemoryStream TDBCtrlGrid 359 TMetafile TDBDataSet412 TMonthCalendar TDBGrid 349 TMTSDataModule TDBGridColumns 349 TNotifyEvent TDBImage 367 TObject28, 41, TDBListBox 366 TOpenPictureDialog TDBLookupComboBox 372 TPageControl TDBLookupControl 369 TParam TDB Lookup ListBox 372 TParameter516, TDBMemo 367 TPen TDBNavigator 277, 362 TPicture TDBRadioGroup 366 TQuery TDBRichEdit 368 TRect TDBText 364 TRemoteDataModule TDCOMConnection 539 TRvNDRWriter TDecisionCube 736, 739 TRvProject601, 602, TDecisionGraph 744, 747 TRvRenderHTML TDecisionGrid 744 TRvRenderRTF TDecisionPivot 747, 748 TRvRenderText TDecisionQueiy 735, 739 TRvSystem601, 605, TDecisionSource 743 try..except TField313 try..finally TFileStream 215 TSavePicture Dialog TFont 57, 225 TScreen TFontDialog 226 TSearchRec TGraphic 233 TSharedConnection TGraphicControl 63 TSimplcDataSet THandleStream 215 TSimpleObjectBroker TliemeAPI 149 TSOAPDataModule TIBClientDataSet 573 TSocketConnection TIBDatabase 458 TSQLClientDataSet TIBDatabaselnfo 479 TSQLConnection 426, 431, TIBDataSet 472 TSQLDataSet TIBEvents 478 TSQLMonitor TIBGeneratorField 471 TSQLQuery TIBQuery 470 TSQLStoredProc TIBSQL 474 TSQLTable TIBSQLMonitor481 TStandardColorMap TIBStoredProc 471 TStatusBar TIBTable 469 TStatusPanel TIBTransaction 461 TStoredProc TIcon 238 TStream TImage 245 TStringList TImageList 110 TStrings TIndexDef295 TStringStrcam Предметный указатель TTabControI V TTable TTabSheet 101 virtual 28, TThread711 Virtual Method Table TToolBar TTool Button w TTreeView TTwilightColorMap widget 88, TWebConnection TWebDataModule TWidgetStyle 91 X TWinControl TXPColorMap 151 XSQLDA TXPManifest 145 XSQLVAR Д Действие Агрегатные поля Делегирование Агрегаты Дескриптор окна Агрегаты-объекты Деструктор Атрибуты файла Диапазон Динамическая библиотека ресурсов Динамические поля Динамический приоритет Базовый приоритет 708 Директива:

Битовая карта 238 0 pascal Блок защиты ресурсов 73 О register Буфер Delta 577 0 safecall О stdcall В 0 cdecl Дочерний класс Драйверы dbExpress Взаимное исключение 721, Визуальный стиль Виртуальный метод Вложенные наборы данных Внешний провайдер данных 573 Закладка 342, Внутренний провайдер данных 573 Значок Вызовы в DLL Вычисляемое поле И Индекс Инициализация DLL Инкапсуляция Группировка агрегатов Предметный указатель К Объект-транзакция ADO Объекты:

О в списках Кисть О синхронизации Класс Объектный тип Клиент многозвенных приложений Ограничения данных Коллекции Опережающее объявление класса Колонка Отложенный ввод/вывод Команда ADO Отношение 334, Компоненты доступа к данным Отображаемый файл Компоненты отображения данных 276, п Конструктор Контекст устройства Конфигурация BDE 383 Пакет данных Критическая секция 722, 724 Панель инструментов Кросстаб 732 Папка обмена Параметры запроса SQL м Первичный индекс 296, Перо Позднее связывание Манифест Поиск данных Маска Поиск файла Метафайл Поле 17, Метод 17, Полиморфизм О абстрактный Пользовательский интерфейс О виртуальный Потоки 213, О динамический Провайдер ADO 485, О класса 18, Провайдер данных О перегружаемый Проект CLX О статический Проект динамической библиотеки Метод-обработчик событий Процесс Модуль ShareMem Процессор баз данных н Псевдоним базы данных Набор данных 268, Наследование Родительский класс Неявный вызов DLL Нормальный приоритет Свойство Семафор 721, Область видимости Сервер приложений 533, Обработка исключительных ситуаций Синхронизация потоков Объект Синхронный просмотр данных О поля Ситуация гонок Объект-источник данных ADO Событие 23, Объект-команда ADO Соответствие типов Объект-набор рядов ADO Списки Объект-перечислитель ADO 0 указателей Объект-сессия ADO Предметный указатель Статические поля 311 Фоновые процедуры Страничный файл 220 Фоновый приоритет Функция:

О IOResult О блочного ввода/вывода О ввода/вывода Тема О обратного вызова Типы:

О данных ц О полей 314, Тонкий клиент Трехзвенная модель 534 Цвет Тупики ш Шрифт 57, Удаленный модуль данных 537, э Указатель на метод Ф Экземпляр класса Экспорт функций DLL Файл DPR Файловые переменные Файлы, отображаемые в память Фильтр 340, Явный вызов DLL Фокус ввода soft!on.u 119991 г. Москва, w w fe w. t r sil ул. Губкина, д. тел.: (095) 232- e-mail: info@softline.ru Все для разработки ПО Почему опытные разработчики приобретают нужные для их работы программы в компании SoftLine? Microsoft И Их привлекают низкие цены, т.к. компания работает напрямую с вендо рами. Borland Ш Их привлекает имеющаяся возможность получения демо-версий и об новлений.

• В выборе программ им помогают каталог SoftLine-direct и сайт www.softline.ru.

• Большая часть ассортимента SoftLine для разработчиков недоступна в других компаниях.

К а к и е этапы разработки охватывает программное COMPAQ обеспечение, поставляемое SoftLine?

• Проектирование программ (Microsoft, CA/Platinum, Rational, SilverRun, Quest).

• Совместная работа (Centura, Merant, Microsoft).

• Управление проектами (PlanisWare, PlanView, Microsoft).

• Написание кода (среды разработки Allaire, Borland, IBM, Microsoft, ком поненты Allround Automation, ComponentOne, Crystal Decisions, Janus, Sitraka, Stingray).

• Оптимизация кода (Compaq, Fuji, Intel, MainSoft, Sun, Sybase, Tenberry).

• Отладка и тестирование (NuMega, Intuitive Systems, Segue).

• Упаковка приложений (InstallShield, Wise Solutions).

• Развертывание и поддержка (Remedy, RoyalBlue, CA, Network Associates).

• Обучение пользователей (Adobe, Allen Communications, click2learn.com, sitraka "• eHelp, Macromedia, Quest, Ulead).

SoftLine — это свобода выбора Обратившись в SoftLine, вы в кратчайшие сроки решите проблемы с про rC»,., граммным обеспечением. Получив консультацию менеджеров, часть из ко ilGA торых знакома с работой разработчиков не понаслышке (на собственном опыте), вы подберете все необходимое для работы в вашей области — от InstallShield интегрированной среды RAD — до готовых компонент. При этом мы оста вим выбор идеологии разработки за вами - например, для регулярного sonwm COEPOUTIQN получения информации о продуктах и технологиях, вы сможете подписать ся на Microsoft Developer Network, Sun Developer Essentials или на нашу соб ственную рассылку компакт-дисков - SoftLine Support Subscription, предо ставляющую обновления и демо-версии всех ведущих производителей.

I SYBASE Компания SoftLine также поможет вам в выборе обучающих курсов.

INFORMATION ANYWHERE.

ВЕСЬ МИР компьютерных книг Более 1600 наименований книг в И Н Т Е Р Н Е Т - М А Г А З И Н Е www.computerbook.ru 3 tomputeiBUUK. ru Miciosotl Internet Explore* Справка '. Цзйпамое "Остановить ССииокгь Домой | Поис* Игранное *'хиат Гочга Ссылки ^JJabLBlru ^ыультят понсга вагнс I расширенный п о и с к - - » • Как купить книгу Глажкая страница • Прайс-лист С т к н а л к з и р о и н к ц й интернет-магазин компьютерной Microsoft Office XP • Новинки в целом литературы ComputeAook.ro предлагает большой • Готовятся к печати добор книг компьютерной тематики.

• Расширенный поиск • ТОР 2 • Электронные книги количество книг: юпичаствд эпектронкмх книг: • Обзоры : • Главная страница Издательство "БХВ-Саюгг Петербург" Ефре Справочник Web-мастера. XML Ищатепьстяо "БХВ-Саккт Петербург" C3I Copyright ©computeAook.iu. BECbi МИР КОМПЬЮТЕРНЫХ КНИГ КНИГ ПО КОМПЬЮТЕРНОЙ ТЕХНИКЕ, ПРОГРАММНОМУ ОБЕСПЕЧЕНИЮ И ЭЛЕКТРОНИКЕ ВСЕХ РУССКОЯЗЫЧНЫХ ИЗДАТЕЛЬСТВ УВАЖАЕМЫЕ ЧИТАТЕЛИ!

ДЛЯ ВАС ОТКРЫЛСЯ ОТДЕЛ "КНИГА - ПОЧТОЙ" Заказы принимаются:

=> По телефону: (812) 541-85-51 (отдел "Книга — почтой") => По факсу: (812) 541 -84-61 (отдел "Книга — почтой") = => По почте: 199397, Санкт-Петербург, а/я => По E-mail: trade@bhv.spb.su Если у Вас отсутствует Internet — по почте, БЕСПЛАТНО, высылается дискета с прайс-листом (цены указаны с учетом доставки), аннотациями и оглавлениями к книгам и, конечно, условиями заказа.

Pages:     | 1 |   ...   | 9 | 10 ||



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

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