Практика
Создание дерева файлов в виде меню с ассоциированными иконками

:: Меню ::
:: На главную ::
:: FAQ ::
:: Заметки ::
:: Практика ::
:: Win API ::
:: Проекты ::
:: Скачать ::
:: Секреты ::
:: Ссылки ::

:: Сервис ::
:: Написать ::

:: MVP ::

:: RSS ::

Яндекс.Метрика


Навигацию по файловой системе можно организовать разными способами. Наиболее распространенные - с использованием ListView и/или TreeView, иногда для навигации по дискам используется ComboBox (самый распространенный пример: Explorer). Сейчас мы рассмотрим еще один вариант, с использованием MainMenu.

Прежде, чем мы приступим, скажу пару слов про этот метод. Работа с таким меню очень напоминает работу с TreeView (расположение элементов меню по своей структуре ничем не отличается от расположения узлов TreeView). Для более комфортной навигации, каждому элементу меню можно присвоить иконку, ассоциированную с файлом или каталогом, но который он указывает. В отличие от TreeView, в меню не нужно каждый раз щелкать мышкой, чтобы просмотреть список дочерних элементов. Коротко о неприятном. Если мы раскрываем узел с большим количеством дочерних элементов, то нам предстоит долгое ожидание, пока все элементы будут прорисованы, прежде чем они отобразятся на экране. В это время пользователь может подумать, что программа зависла (такая вот неприятная мелочь).

Тем не менее, раз взялись, будем делать. Процедура, строящая дерево каталогов, будет работать рекурсивно.

(* Рекурсивная процедура, строящая дерево каталогов *)
procedure UpdateMenu( Path: string; Parent: TMenuItem );
var
  sr: TSearchRec;
  ShInfo: TSHFileInfo;
  MenuItem, MenuItemZero: TMenuItem;
begin
   if FindFirst( Path + '*.*', faAnyFile, sr ) = 0 then
   repeat
      // Запрашиваем индекс иконки в системном списке
      if Form1.GetIcon.Checked then
         SHGetFileInfo( PChar( Path + sr.Name ), 0, ShInfo, SizeOf( ShInfo ),
                        SHGFI_TYPENAME or SHGFI_SYSICONINDEX );
      if ( sr.Name <> '.' ) and ( sr.Name <> '..' ) then
      begin
         if ( sr.Attr and faDirectory ) = faDirectory then
         begin
            MenuItem := TMenuItem.Create( Form1 );
            MenuItem.Caption := sr.Name;
            Parent.Add( MenuItem );
            // Если нужно, рисуем иконку
            if Form1.GetIcon.Checked then
               MenuItem.ImageIndex := ShInfo.iIcon;
            UpdateMenu( Path + sr.Name + '\', MenuItem );
            // Проверяем, пустой каталог или нет
            if MenuItem.Count = 0 then
            begin
               MenuItemZero := TMenuItem.Create( Form1 );
               MenuItemZero.Caption := 'Пусто';
               MenuItem.Add( MenuItemZero );
            end;
         end
         else
         begin
            MenuItem := TMenuItem.Create( Form1 );
            MenuItem.Caption := sr.Name;
            MenuItem.Hint := Path + sr.Name;
            MenuItem.OnClick := Form1.MnuClick;
            Parent.Add( MenuItem );
            // Если нужно, рисуем иконку
            if Form1.GetIcon.Checked then
               MenuItem.ImageIndex := ShInfo.iIcon;
         end;
      end;
      Application.ProcessMessages;
      if Form1.Tag = 0 then
      begin
         Form1.StatusBar1.Panels.Items[0].Text := 'Поиск прерван';
         Break;
      end;
   until FindNext( sr ) <> 0;
end;

(* Прерываем поиск *)
procedure TForm1.EndSearchClick(Sender: TObject);
begin
   Tag := 0;
   BeginSearch.Enabled := true;
end;

Подробно рассматривать эту процедуру не стану (надеюсь, вы знакомы с рекурсией), но несколько моментов объясню. Функция SHGetFileInfo возвращает информацию о файле (директории, диске), о ней я расскажу подробнее чуть ниже. Наше меню позволяет запускать найденные файлы программой, связанной с ними по расширению. Для того чтобы связать пункт меню и файл, на который он указывает, путь к файлу можно занести в свойство Hint пункта меню. Процедуру, открывающую файл, присвоим событию OnClick пункта меню (эта процедура описана ниже). В конце цикла идет проверка поля Tag на равенство нулю. В нашем случае Tag показывает, идет в данный момент поиск (Tag <> 0, например он равен 1), или нет (Tag = 0). Перед началом поиска устанавливаем значение поля Tag в 1, и теперь, чтобы его прервать, это поле достаточно обнулить.

(* Процедура запуска файлов из меню *)
procedure TForm1.MnuClick(Sender: TObject);
begin
   ShellExecute( Handle, 'open', PChar( ( Sender as TMenuItem ).Hint ),
                 nil, nil, SW_SHOWNORMAL );
end;

Теперь, когда все готово к поиску, посмотрим, как происходит его вызов.

(* Запуск поиска файлов *)
procedure TForm1.BeginSearchClick(Sender: TObject);
var
  SysImageList: UINT;
  SFI: TSHFileInfo;
begin
   if DirectoryExists( PathEdit.Text ) then
   begin
      // Очищаем меню
      MainMenu1.Images.Free;
      // Если пользователь хочет видеть иконки ...
      if GetIcon.Checked then
      begin
         // Создаем список иконок
         MainMenu1.Images := TImageList.Create( Self );
         case RadioGroup1.ItemIndex of
            0: // Запрашиваем маленькие иконки
               SysImageList := SHGetFileInfo( '', 0, SFI, SizeOf( TSHFileInfo ),
                                              SHGFI_SYSICONINDEX or SHGFI_SMALLICON );
            1: // Запрашиваем большие иконки
               SysImageList := SHGetFileInfo( '', 0, SFI, SizeOf( TSHFileInfo ),
                                              SHGFI_SYSICONINDEX or SHGFI_LARGEICON );
         end;
         // Назначаем системный список иконок нашему меню
         if SysImageList <> 0 then
         begin
            MainMenu1.Images.Handle := SysImageList;
            MainMenu1.Images.ShareImages := true;
         end;
      end;

      Tag := 1;
      BeginSearch.Enabled := false;
      StatusBar1.Panels.Items[0].Text := 'Очистка главного меню ...';
      MnuRoot.Clear;
      MnuRoot.Caption := IncludeTrailingPathDelimiter( PathEdit.Text );
      StatusBar1.Panels.Items[0].Text := 'Ждите, работаю ...';
      UpdateMenu( IncludeTrailingPathDelimiter( PathEdit.Text ), MnuRoot );
      StatusBar1.Panels.Items[0].Text := 'Работа закончена';
      BeginSearch.Enabled := true;
      Tag := 0;
   end;
end;

Здесь нас, прежде всего, интересует первая половина кода (со второй все и так понятно). Если пользователь хочет видеть иконки файлов в меню, мы должны запросить их у системы. Для этого воспользуемся функцией SHGetFileInfo. Рассмотрим аргументы этой функции. Первый - путь к файлу (если файл указан, мы получим информацию по этому файлу, если этот параметр пустой, мы получим "глобальные" данные). Второй - атрибуты. Третий - указатель на структуру TSHFILEINFO (именно в нее будет занесена информация). Четвертый - размер структуры TSHFILEINFO. Пятый - флаги, говорящие системе о том, какую информацию мы у нее запрашиваем.

В процедуре BeginSearchClick, в зависимости от того, что хочет пользователь, мы запрашиваем системный список маленьких (SHGFI_SMALLICON) или больших (SHGFI_LARGEICON) иконок. В процедуре UpdateMenu мы запрашиваем номер иконки (SHGFI_TYPENAME) для файла или каталога, указанного в первом параметре.

Имейте в виду, получение иконок заметно снижает скорость поиска.

На сегодня все. Успехов в программировании.

.: Пример к данной статье :.


При использовании материала - ссылка на сайт обязательна