В одном из последних постов я рассказывал о том как в Delphi XE можно сформировать дерево каталогов и документов Google для своей программы. Сегодня продолжим немного начатую тему.
Если вы зайдете в свой аккаунт Документов Google и посмотрите на иконки загруженных документов, то заметите, что для каждого документа Google присваивает одну из своих иконок. Если же Google «не понимает» формат документа (например, если Вы загрузите в аккаунт архив rar), то документу присваивается дефолтная иконка. Но Windows-то знает намного больше форматов файлов и, соответственно мы можем сделать дерево документов Google в нашей программе ещё более красивым и удобным. Вот этим мы сегодня и займемся.
Похожий компонент мы когда-то начинали писать с SeregaAltmer’ом, но тема заглохла и до релиза компонент не дожил, поэтому сегодня я постараюсь довести начатое дело до конца и предоставить Вам уже готовый к использованию компонент для отображения иконок документов в дереве.
Введение
Вначале немного слов о том как и на чем будет основываться работа нашего документа.
По большому счёту вывести «родные» иконки Google в дереве TreeView — пустяк и для этого достаточно скачать уже готовые иконки из Google, вставить их в простой ImageList и чуть-чуть проанализировать, какой именно узел добавляется в дерево. Сделать это, используя компонент дерева каталогов Google — элементарно (достаточно посмотреть, что записано в Data узла TTreeNode).
Но как в этом случае быть с файлами иконок которых у Google нет, а Windows их знает? Ручками вытягивать каждую иконку и сохранять в ImageList — затратно по времени и в принципе бесполезно. Намного удобнее организовать работу следующим образом:
- для документов Google храним иконки как обычно, загрузив их в ImageList.
- для файлов Windows при первом построении дерева вытаскиваем иконки, ассоциированные с файлом и запоминаем их для последующего использования в программе
- для документов иконку которых не удалось определить двумя предыдущими способами назначаем дефолтную иконку.
Получается, по-моему, вполне удобная схема работы — на первом построении программа немного «поыхтит» и выгрузит необходимые иконки, а в дальнейшем будет работать, используя уже готовый набор.
Наш компонент, как Вы уже поняли, будет наследником класса TImageList, что позволит без проблем прицепить его к любому TreeView и его наследникам.
Что касается первого пункта работы, то здесь вопросов никаких быть не должно. Надо будет только скачать иконки документов, например, архивом, который вы найдете в конце поста и загрузить все картинки в наш компонент. А вот со вторым пунктом мы разберемся немного подробнее.
Как получить иконку, ассоциированную с файлом?
Ответ на этот вопрос может найти только самый внимательный читатель тот, кто знает, что такое MSDN. Ну, а если не знаете, что читайте далее. Итак, небольшой экскурс в ShellAPI и MSDN. В Shell API есть несколько методов, возвращающих иконку, ассоциированную с файлом (ExtractIcon, ExtractAssociatedIcon и т.д.), однако эти функции в нашем случае являются практически не нужными, т.к. сам документ может храниться в аккаунте Google, а иконку мы выводим здесь — в программе. Поэтому наиболее приемлемым вариантом работы остается использование довольно мощной функции — SHGetFileInfo.
Функция SHGetFileInfo
Возвращает сведения об объекте в файловой системе, например, о файле, папке, каталоге и и т.д. Выглядит функция следующим образом:
SHGetFileInfo(pszPath: LPCTSTR; dwFileAttributes:DWORD; psfi: _SHFILEINFO; cbFileInfo: UINT; uFlags:UINT);
Возможность использования различных флагов позволяет получить практически любую информацию по файлу, включая и иконки, ассоциированные с ним. Рассмотрим параметры функции:
pszPath: LPCTSTR (PChar) — указатель на строку, содержащую имя и путь к файлу. Функция принимает как абсолютные так и относительный путь к файлу. Если параметр uFlags включает SHGFI_PIDL флаг, то pszPath должен содержать быть адрес структуры ITEMIDLIST (PIDL), которая содержит список идентификаторов однозначно идентифицируеющих файл в пространстве имен Shell.PIDL. Относительная PIDLs не допускается.
Если параметр uFlags включает SHGFI_USEFILEATTRIBUTES флаг, то параметр не обязан быть допустимым именем файла. Функция будет выполнятся также, как если бы был задан файл с заданным именем и атрибутами в параметре dwFileAttributes. Это позволяет получить информацию о типе файла, передавая только расширение для pszPath и FILE_ATTRIBUTE_NORMAL в dwFileAttributes.
dwFileAttributes: DWORD — комбинация из одного или более флагов, определяющих необходимые атрибуты файла . Если uFlags не включает SHGFI_USEFILEATTRIBUTES флаг, то этот параметр игнорируется.
psfi: _SHFILEINFO — указатель на структуру SHFILEINFO для получения информации о файле.
cbFileInfo: UINT — размер структуры _SHFILEINFO переданной в параметре psfi
uFlags:UINT — комбинация флагов, определяющих тот набор информации о файле, который нам необходим. Всего допускается использование 18 различных флагов информацию о которых Вы можете найти на MSDN. Я же приведу только те значения флагов, которые нам будут необходимы для работы:
SHGFI_ICON — получить дескриптор значка, который ассоциирован с файлом и индекс значка в списке. Handle будет скопирован как член HICON структуры указанный psfi, а индекс копируется iIcon членом.
SHGFI_SMALLICON — изменить SHGFI_ICON, для получения маленькой иконки файла. Для использования этого флага необходимо включить флаг SHGFI_ICON и/или SHGFI_SYSICONINDEX.
SHGFI_USEFILEATTRIBUTES — указывает, что функция не должна пытаться получить доступ к файлу, указанному в pszPath. Этот флаг не может быть объединен с SHGFI_ATTRIBUTES, SHGFI_EXETYPE или SHGFI_PIDL флагами.
Что касается флагов, задаваемых в параметре dwFileAttributes, то применительно к нашему случаю значение этого параметра можно оставлять равным нулю.
Результат, возвращаемый функцией SHGetFileInfo, зависит от заданных флагов в параметре uFlags. Так как нам придется работать не с файлами как таковыми. а только с их названиями, включающими в себя разве что расширения, то функция будет возвращать значения отличные от нуля в случае успешного выполнения и ноль в случае провала.
Функция получения хэндла произвольного файла может выглядеть следующим образом:
function ExtractHandleIcon(FileName: string; FileExist: Boolean): Cardinal; var FileInfo: SHFILEINFO; Flags: Cardinal; begin Flags := SHGFI_ICON or SHGFI_SMALLICON; if not(FileExist) then Flags := Flags or SHGFI_USEFILEATTRIBUTES; if SHGetFileInfo(PWideChar(FileName), 0, FileInfo, SizeOf(FileInfo), Flags)>0 then Result := FileInfo.hIcon end;
Если выполнение SHGetFileInfo пройдет успешно, то функция вернет хэндл ассоциированной с файлом иконки и останется только правильно добавить эту иконку в ImageList, чтобы при повторном обращении не требовалось её снова грузить. Вот этим мы сейчас и займемся.
Пишем компонент TGoogleImageList
Итак, наша задача сделать так, чтобы компонент один раз загрузил себе все иконки файлов, необходимых для отображения в дереве и в дальнейшем использовал полученный список.
Ключевое слово здесь — список. Как его организовать — дело второе можно использовать динамический массив, можно сделать что-нибудь с TList, да хоть те же Generics пристроить — роли в принципе не играет. В свое время мы остановились с Серегой на таком классе списка:
type TFileIcon = record Ext: string; IconIndex: Integer; end; TFileIconList = class private FFileIcons: array of TFileIcon; FfCount: Integer; function Get(Index: Integer): TFileIcon; function GetCount: Integer; public function IconIndexOf(const Ext: string): Integer; function ExtOf(const IconIndex: Integer): string; function Add(const Ext: string; IconIndex: Integer): Integer; procedure Clear; property Item[Index: Integer]: TFileIcon read Get; default; property Count: Integer read GetCount; end;
Как видите, список представляет из себя динамический массив записей TFileIcon где каждая иконка описывается парой значений: «расширение файла — индекс в списке изображений». В классе TFileIconList определены следующие методы:
function Get(Index: Integer): TFileIcon;
Возвращает запись TFileIcon из массива FFileIcons:
function TFileIconList.Get(Index: Integer): TFileIcon; begin Result := fFileIcons[Index]; end;
function GetCount: Integer
Возвращает количество записей в массиве (его длину)
function IconIndexOf(const Ext: string): Integer;
По известному расширению файла Ext проводит поиск в массиве и выводит индекс изображения в списке изображений:
function TFileIconList.IconIndexOf(const Ext: string): Integer; var i: Integer; begin for i := 0 to Count - 1 do if Get(i).Ext = Ext then begin Result := Get(i).IconIndex; Exit; end; Result := -1; end;
function ExtOf(const IconIndex: Integer): string;
Функция обратная предыдущей — по индексу изображения выводит расширение файла:
function TFileIconList.ExtOf(const IconIndex: Integer): string; var i: Integer; begin for i := 0 to Count - 1 do if Get(i).IconIndex = IconIndex then begin Result := Get(i).Ext; Exit; end; Result := ''; end;
function Add(const Ext: string; IconIndex: Integer): Integer;
Добавляет в массив новую запись TFileIcon и возвращает её индекс:
function TFileIconList.Add(const Ext: string; IconIndex: Integer): Integer; begin Result := Count; SetLength(fFileIcons, Count + 1); FFileIcons[Count].Ext := Ext; FFileIcons[Count].IconIndex := IconIndex; inc(fCount); end;
procedure Clear;
Чистит массив от всех записей.
Теперь попробуем применить этот класс в нашем компоненте. Создадим наследника TImageList с такими полями и методами:
TGoogleImageList = class(TImageList) private FIcon: TIcon; FList: TFileIconList; FExistedFiles: Boolean; FDefaultImageIndex: TImageIndex; function Get(FileExt: string): Integer; procedure SetDefaultImageIndex(const Value: TImageIndex); public property List: TFileIconList read fList; function CountFileIcon: Integer; constructor Create(AOwner: TComponent); override; destructor Destroy; override; procedure Clear; property ExtIcon[FileExt: string]: Integer read Get; default; published property ColorDepth default cd32Bit; property ExistedFiles: Boolean read fExistedFiles write fExistedFiles default false; property DefaultImageIndex : TImageIndex read FDefaultImageIndex write SetDefaultImageIndex; end;
Рассмотрим что и для чего здесь используется:
FIcon: TIcon — это поле создается для того, чтобы иметь возможность без лишних хлопот добавлять новые иконки в список изображений. Поле создается один раз в конструкторе компонента и уничтожается в деструкторе.
FList: TFileIconList — список иконок, добавленных для файлов не относящихся к Google Docs.
FExistedFiles: Boolean — флаг, определяющий с какими файлами будет работать компонент — существующими на диске (True) или нет (False). В целом, т.к. наш компонент рассчитан больше на работу с документами из аккаунта Google, то этот флаг будет всегда равен False, но Вы с помощью него сможете, в случае необходимости вывести в дерево и иконки любых доступных в системе файлов.
FDefaultImageIndex: TImageIndex — поле служит для хранения номера иконки, которая будет использоваться в качестве дефолтной (когда не смогли получить иконку Google, а SHGetFileInfo вернула значение равное нулю)
Методы компонента
function Get(FileExt: string): Integer;
Возвращает индекс иконки из списка изображений по расширению файла. Если такая иконка отсутствует в списке, то пробует её загрузить, используя функцию, рассмотренную в примере использования SHGetFileInfo. Если оба варианта не увенчались успехом — возвращает индекс дефолтной иконки:
function TGoogleImageList.Get(FileExt: string): Integer; begin Result := List.IconIndexOf(FileExt); if Result = -1 then begin FIcon.Handle := ExtractHandleIcon(FileExt, ExistedFiles); if FIcon.Handle
procedure Clear;
Полностью очищает список всех изображений и иконок компонента:
procedure TGoogleImageList.Clear; begin inherited Clear; List.Clear; end;
Думаю, что остальные методы рассматривать нет смысла, т.к. в случае необходимости Вы всегда сможете заглянуть в исходник и увидеть, что методы работают очень просто. Лучше рассмотрим пример использования компонента.
Пример использования компонента
Итак, копируем приведенный ниже листинг модуля с кодом компонента в отдельный файл и сохраняем его в файл с названием, например, GoogleImagesLst.pas:
unit GoogleImageList; interface uses Controls, Classes, ShellAPI, ImgList, Graphics, SysUtils, Dialogs; type TFileIcon = record Ext: string; IconIndex: Integer; end; TFileIconList = class private FFileIcons: array of TFileIcon; FCount: Integer; function Get(Index: Integer): TFileIcon; function GetCount: Integer; public function IconIndexOf(const Ext: string): Integer; function ExtOf(const IconIndex: Integer): string; function Add(const Ext: string; IconIndex: Integer): Integer; procedure Clear; property Item[Index: Integer]: TFileIcon read Get; default; property Count: Integer read GetCount; end; TGoogleImageList = class(TImageList) private FIcon: TIcon; fList: TFileIconList; fExistedFiles: Boolean; FDefaultImageIndex: TImageIndex; function Get(FileExt: string): Integer; procedure SetDefaultImageIndex(const Value: TImageIndex); public property List: TFileIconList read fList; function CountFileIcon: Integer; constructor Create(AOwner: TComponent); override; destructor Destroy; override; procedure Clear; property ExtIcon[FileExt: string]: Integer read Get; default; published property ColorDepth default cd32Bit; property ExistedFiles: Boolean read fExistedFiles write fExistedFiles default false; property DefaultImageIndex : TImageIndex read FDefaultImageIndex write SetDefaultImageIndex; end; procedure Register; function ExtractHandleIcon(FileName: string; ExistedFiles: Boolean): Cardinal; implementation procedure Register; begin RegisterComponents('WebDelphi.ru', [TGoogleImageList]); end; function ExtractHandleIcon(FileName: string; ExistedFiles: Boolean): Cardinal; var FileInfo: SHFILEINFO; Flags: Cardinal; begin Flags := SHGFI_ICON or SHGFI_SMALLICON; if not(ExistedFiles) then Flags := Flags or SHGFI_USEFILEATTRIBUTES; if SHGetFileInfo(PWideChar(FileName), 0, FileInfo, SizeOf(FileInfo), Flags)>0 then Result := FileInfo.hIcon else Result:=0; end; { TFileIconImageList } procedure TGoogleImageList.Clear; begin inherited Clear; List.Clear; end; function TGoogleImageList.CountFileIcon: Integer; begin Result := List.Count; end; constructor TGoogleImageList.Create(AOwner: TComponent); begin inherited Create(AOwner); ColorDepth := cd32Bit; fList := TFileIconList.Create; FIcon := TIcon.Create; fExistedFiles := False; end; destructor TGoogleImageList.Destroy; begin FIcon.Free; List.Free; inherited Destroy; end; function TGoogleImageList.Get(FileExt: string): Integer; begin Result := List.IconIndexOf(FileExt); if Result = -1 then begin FIcon.Handle := ExtractHandleIcon(FileExt, ExistedFiles); if FIcon.Handle Value then begin FDefaultImageIndex := Value; end; end; { TFileIconList } function TFileIconList.Add(const Ext: string; IconIndex: Integer): Integer; begin Result := Count; SetLength(fFileIcons, Count + 1); fFileIcons[Count].Ext := Ext; fFileIcons[Count].IconIndex := IconIndex; inc(fCount); end; procedure TFileIconList.Clear; begin SetLength(fFileIcons, 0); fCount := 0; end; function TFileIconList.ExtOf(const IconIndex: Integer): string; var i: Integer; begin for i := 0 to Count - 1 do if Get(i).IconIndex = IconIndex then begin Result := Get(i).Ext; Exit; end; Result := ''; end; function TFileIconList.Get(Index: Integer): TFileIcon; begin Result := fFileIcons[Index]; end; function TFileIconList.GetCount: Integer; begin Result := fCount; end; function TFileIconList.IconIndexOf(const Ext: string): Integer; var i: Integer; begin for i := 0 to Count - 1 do if Get(i).Ext = Ext then begin Result := Get(i).IconIndex; Exit; end; Result := -1; end; end.
Создаем новый проект Package — Delphi и добавляем в него полученный модуль. Пакет называем любым понятным именем. Устанавливаем пакет (Клик правой кнопкой мыши на пакете в Project Manager -> Install). В палитре компонентов на вкладке WebDelphi.ru появится новый компонент TGoogleImageList. Теперь, чтобы не копипастить пример построения самого дерева я даю ссылку на уже готовый компонент дерева каталогов Google и пример его использования — вот она. Создаем по примеру свой проект с деревом каталогов. На форму добавляем новый компонент.
Теперь необходимо добавить в компонент иконки Google. Так как API Google Docs не предоставляет штатной возможности сохранить иконки на диск, то я наковырял всевозможных иконок и сохранил их отдельно. Скачать архив с иконками Google можно по ссылке.
Загружаем все эти изображения в компонент обычным образом через его редактор. У меня получился следующий состав иконок:
Свойство ExistedFiles компонента устанавливаем в False, а для DefaultImageIndex выбираем любую загруженную иконку (у меня она оказалась под индексом 7).
Свойству Images дерева TreeView присваиваем значение TGoogleImageList. Теперь действуем следующим образом — в обработчике OnCustomDrawItem пишем следующий обработчик:
procedure TfMain.tvDocsCustomDrawItem(Sender: TCustomTreeView; Node: TTreeNode; State: TCustomDrawState; var DefaultDraw: Boolean); begin if Node.Data<>nil then //узел дерева содержит информацию по документу Google begin {подбираем иконку в зависимости от типа документа} case TGoogleDocument(Node.Data).DocType of tdFolder:Node.ImageIndex:=6; tdDocument:Node.ImageIndex:=0; tdSpreadsheet:Node.ImageIndex:=1; tdPresentation:Node.ImageIndex:=3; tdDrawing:Node.ImageIndex:=4; tdPdf:Node.ImageIndex:=5; {Иконки для таких документов в Google отсутствуют - подгружаем либо ищем в уже готовом списке} tdFile, tdUnknown: Node.ImageIndex:=GoogleImageList1.ExtIcon[ExtractFileExt(TGoogleDocument(Node.Data).Title)]; end; end end;
После этого запускаем приложение, авторизуемся в аккаунте и строим дерево. У меня получился следующий вид дерева:
Красота :). Ну и теперь мне остается только привести Вам ссылки, которые могут пригодится для более детального изучения работы с Google Docs в Delphi (не все же постоянно читают блог). Вот, что я бы посоветовал просмотреть по теме:
- Дерево каталогов и документов Google Docs.
- Загрузка файлов в Google Docs средствами Delphi.
- Библиотека Synapse. Работа с модулем HTTPSend.pas. (для тех, кто не в курсе, что такое Synapse)
- Список публикаций с меткой: ClientLogin (для тех, кто не в курсе, как происходит авторизация в сервисах Google для Desktop-приложений)
Книжная полка
Описание: Рассмотрены практические вопросы по разработке клиент-серверных приложений в среде Delphi 7 и Delphi 2005 с использованием СУБД MS SQL Server 2000, InterBase и Firebird. Приведена информация о теории построения реляционных баз данных и языке SQL. Освещены вопросы эксплуатации и администрирования СУБД.
|
||
Название: О чем не пишут в книгах по Delphi
Описание: Рассмотрены малоосвещенные вопросы программирования в Delphi. Описаны методы интеграции VCL и API. Показаны внутренние механизмы VCL и приведены примеры вмешательства в эти механизмы. Рассмотрено использование сокетов в Delphi: различные режимы их работы, особенности для протоколов TCP и UDP и др.
|
Вот интересно, Владислав, у вас разный стиль программирования. В данных примерах кода у вас нет отступов между оператором присваивания в одном примере а другом есть, между оператором конкатенации, пропуски между строками — та же история :) Интересно — это получается случайно или просто код из разных источников?
И не менее важный вопрос — планируете ли вы сделать связь с Google Task? API — документация уже в сети есть с мая, интересно было бы посмотреть на вашу реализацию, если таковая будет. (А таковая нужна, а именно увязать свой аккаунт гугл таск с отправкой Смс)
Николай, ну вот такой я разный — то ставлю пробелы, то не ставлю =) Может и случайность, а может и просто перед копированием не тыкал Ctrl+D, а в другом месте нажимал. А весь код из одного источника — моего компьютера и Delpi XE =). По поводу Google Tasks API я уже давно в курсе, что есть документация и даже делал небольшой модуль для работы с этим API (в блоге его не выкладывал), НО работу решил заморозить до лучших времен по следующим причинам: 1. На тот момент Google «зарубил» кучу лабораторных API, объяснив это недобросовестным использованием со стороны разработчиков, неактуальностью и… Подробнее »
1. Ознакомившись вкратце с имеющейся докой по GT (Google Task), увидел что операций достаточно для работы с задачами (создание, редактирование, удаление) или я ошибаюсь? Интересно, Владислав, что у вас получился за модуль и какие операции вы в нем реализовали. Если конечно можете, не могли бы вы мне его на почту скинуть, для ознакомления (в каком бы виде он не был). 2. По поводу лимита на количество запросов: на сколько я понял, лимит устанавливается на определенный IP и то по количеству запросов в день, и на разные сервисы он разный. На GT походу там до 100 тык запросов в день расширили… Подробнее »
Николай, судя по всему API расширили — увеличили количество запросов, что радует. Модуль поищу — на сколько помню там я реализовал только чтение/добавление нового задания, а до редактирования так и не добрался. В работе использовал SuperObject (для разбора JSON) и Synapse (для выполнения HTTP-запросов).
Буду благодарен если найдете. Меня очень заинтересовали возможности связки Google API и Delphi. Я хотел бы участвовать с вами в разработках по мере возможности, и был бы благодарен если вы возьмете меня к себе в команду разработчиков. Если не трудно, Владислав, у меня появились некоторые вопросы к вам по поводу реализации уже имеющихся у вас модулей с Google API, подскажите пожалуйста (в почту) как с вами можно связаться для обсуждения парочки вопросов? Заранее спасибо :)
[…] апреля 2013 года старый добрый Documents List API, о котором я рассказывал много и часто на страницах этого блога, практически […]