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

новогодняя елка В одном из последних постов я рассказывал о том как в 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 (не все же постоянно читают блог). Вот, что я бы посоветовал просмотреть по теме:

  1. Дерево каталогов и документов Google Docs.
  2. Загрузка файлов в Google Docs средствами Delphi.
  3. Библиотека Synapse. Работа с модулем HTTPSend.pas. (для тех, кто не в курсе, что такое Synapse)
  4. Список публикаций с меткой: ClientLogin (для тех, кто не в курсе, как происходит авторизация в сервисах Google для Desktop-приложений)
Скачать исходник: Исходники —> API онлайн-сервисов —> Google API

Книжная полка

Описание: Рассмотрены практические вопросы по разработке клиент-серверных приложений в среде Delphi 7 и Delphi 2005 с использованием СУБД MS SQL Server 2000, InterBase и Firebird. Приведена информация о теории построения реляционных баз данных и языке SQL. Освещены вопросы эксплуатации и администрирования СУБД.
купить книгу delphi на ЛитРес
Описание: Рассмотрены малоосвещенные вопросы программирования в Delphi. Описаны методы интеграции VCL и API. Показаны внутренние механизмы VCL и приведены примеры вмешательства в эти механизмы. Рассмотрено использование сокетов в Delphi: различные режимы их работы, особенности для протоколов TCP и UDP и др.
купить книгу delphi на ЛитРес
0 0 голоса
Рейтинг статьи
уважаемые посетители блога, если Вам понравилась, то, пожалуйста, помогите автору с лечением. Подробности тут.
Подписаться
Уведомить о
6 Комментарий
Межтекстовые Отзывы
Посмотреть все комментарии
Николай
Николай
29/08/2011 20:11

Вот интересно, Владислав, у вас разный стиль программирования. В данных примерах кода у вас нет отступов между оператором присваивания в одном примере а другом есть, между оператором конкатенации, пропуски между строками — та же история :) Интересно — это получается случайно или просто код из разных источников? 

И не менее важный вопрос — планируете ли вы сделать связь с Google Task? API — документация уже в сети есть с мая, интересно было бы посмотреть на вашу реализацию, если таковая будет. (А таковая нужна, а именно увязать свой аккаунт гугл таск с отправкой Смс) 

Николай
Николай
30/08/2011 13:04

1. Ознакомившись вкратце с имеющейся докой по GT (Google Task), увидел что операций достаточно для работы с задачами (создание, редактирование, удаление) или я ошибаюсь? Интересно, Владислав, что у вас получился за модуль и какие операции вы в нем реализовали. Если конечно можете, не могли бы вы мне его на почту скинуть, для ознакомления (в каком бы виде он не был). 2. По поводу лимита на количество запросов: на сколько я понял, лимит устанавливается на определенный IP и то по количеству запросов в день, и на разные сервисы он разный. На GT походу там до 100 тык запросов в день расширили… Подробнее »

Николай
Николай
30/08/2011 18:18

Буду благодарен если найдете. Меня очень заинтересовали возможности связки Google API и Delphi. Я хотел бы участвовать с вами в разработках по мере возможности, и был бы благодарен если вы возьмете меня к себе в команду разработчиков. Если не трудно, Владислав, у меня появились некоторые вопросы к вам по поводу реализации уже имеющихся у вас модулей с Google API, подскажите пожалуйста (в почту) как с вами можно связаться для обсуждения парочки вопросов? Заранее спасибо :)

trackback

[…] апреля 2013 года старый добрый Documents List API, о котором я рассказывал много и часто на страницах этого блога, практически […]