Blog. Just Blog

Пользовательская фильтрация в функции SHBrowseForFolder

Версия для печати Добавить в Избранное Отправить на E-Mail | Категория: Образ мышления: Assembler | Автор: ManHunter
Функцию SHBrowseForFolder я оттюнинговал уже давно, казалось бы уже все, дальше некуда. Ан нет, есть куда. Сегодня разберем еще одну замечательную возможность этой функции, а именно пользовательскую фильтрацию. С ее помощью вы можете выводить в древовидный список только те папки и файлы, которые считаете нужным. Это будет очень полезно, когда надо дополнительно подстраховать пользователя от возможной ошибки.

Для реализации фильтрации используется технология COM, а это значит, что нам потребуется некоторое количество GUID'ов, структур и констант, которые не знает FASM.
  1. ; GUID {00000000-0000-0000-C000-000000000046}
  2. IID_IUnknown \
  3.     dd 000000000h
  4.     dw 00000h
  5.     dw 00000h
  6.     db 0C0h, 000h, 000h, 000h, 000h, 000h, 000h, 046h
  7.  
  8. ; GUID {C0A651F5-B48B-11D2-B5ED-006097C686F6}
  9. IID_IFolderFilterSite \
  10.     dd 0C0A651F5h
  11.     dw 0B48Bh
  12.     dw 011D2h
  13.     db 0B5h, 0EDh, 000h, 060h, 097h, 0C6h, 086h, 0F6h
  14.  
  15. ; IID_IFolderFilterSite Interface
  16. struct IFolderFilterSite
  17.     ; IUnknown
  18.     QueryInterface dd ?   ; 000h
  19.     AddRef         dd ?   ; 004h
  20.     Release        dd ?   ; 008h
  21.     ; IFolderFilterSite
  22.     SetFilter      dd ?   ; 00Ch
  23. ends
  24.  
  25. ; GUID {9CC22886-DC8E-11D2-B1D0-00C04F8EEB3E}
  26. IID_IFolderFilter \
  27.     dd 09CC22886h
  28.     dw 0DC8Eh
  29.     dw 011D2h
  30.     db 0B1h, 0D0h, 000h, 0C0h, 04Fh, 08Eh, 0EBh, 03Eh
  31.  
  32. ; IID_IFolderFilter Interface
  33. struct IFolderFilter
  34.     ; IUnknown
  35.     QueryInterface dd ?   ; 000h
  36.     AddRef         dd ?   ; 004h
  37.     Release        dd ?   ; 008h
  38.     ; IFolderFilter
  39.     ShouldShow     dd ?   ; 00Ch
  40.     GetEnumFlags   dd ?   ; 010h
  41.     refcount       dd ?
  42. ends
  43.  
  44. ; IID_IShellFolder Interface
  45. struct IShellFolder
  46.     ; IUnknown
  47.     QueryInterface   dd ?   ; 000h
  48.     AddRef           dd ?   ; 004h
  49.     Release          dd ?   ; 008h
  50.     ; IShellFolder
  51.     ParseDisplayName dd ?   ; 00Ch
  52.     EnumObjects      dd ?   ; 010h
  53.     BindToObject     dd ?   ; 014h
  54.     BindToStorage    dd ?   ; 018h
  55.     CompareIDs       dd ?   ; 01Ch
  56.     CreateViewObject dd ?   ; 020h
  57.     GetAttributesOf  dd ?   ; 024h
  58.     GetUIObjectOf    dd ?   ; 028h
  59.     GetDisplayNameOf dd ?   ; 02Ch
  60.     SetNameOf        dd ?   ; 030h
  61. ends
  62.  
  63. struct STRRET
  64.     uType dd ?
  65.     cStr  rb 260
  66. ends
  67.  
  68. SHCONTF_FOLDERS    = 0x00000020
  69. SHCONTF_NONFOLDERS = 0x00000040
  70.  
  71. SFGAO_FOLDER = 0x20000000
  72.  
  73. SHGDN_FORPARSING = 0x8000
  74.  
  75. BFFM_IUNKNOWN = 5
  76.  
  77. ; Флаги открытия диалога
  78. BIF_RETURNONLYFSDIRS   = 1
  79. BIF_DONTGOBELOWDOMAIN  = 2
  80. BIF_BROWSEINCLUDEFILES = 0x00004000
  81. BIF_NEWDIALOGSTYLE     = 0x00000040
Теперь немного теории. В callback-функцию BrowseCallbackProc диалога выбора помимо прочих сообщений приходит сообщение BFFM_IUNKNOWN, в параметре lParam которого системой передается указатель на интерфейс IUnknown. Через метод QueryInterface этого интерфейса можно получить указатель на интерфейс IFolderFilterSite. Затем с помощью метода SetFilter этого интерфейса нужно установить пользовательский фильтр. Фильтр представляет собой объект IFolderFilter, реализация методов которого ложится целиком и полностью на разработчика приложения. Благо их совсем немного.

Важное замечание. Для того, чтобы callback-функция получила сообщение BFFM_IUNKNOWN, окно выбора обязательно должно быть создано с флагом BIF_NEWDIALOGSTYLE. А объект IFolderFilter к этому моменту уже должен быть заполнен.
  1.         ; Настроить структуру IFolderFilter
  2.         mov     [FolderFilter.QueryInterface],IFolderFilter_QueryInterface
  3.         mov     [FolderFilter.AddRef],IFolderFilter_AddRef
  4.         mov     [FolderFilter.Release],IFolderFilter_Release
  5.         mov     [FolderFilter.ShouldShow],IFolderFilter_ShouldShow
  6.         mov     [FolderFilter.GetEnumFlags],IFolderFilter_GetEnumFlags
  7.  
  8.         ; Заполнить указатель на структуру
  9.         mov     [pFolderFilter],FolderFilter
  10.  
  11.         ; Заполнить структуру
  12.         mov     eax,[hwnddlg]
  13.         mov     [bi.hwndOwner],eax
  14.         mov     [bi.ulFlags],BIF_NEWDIALOGSTYLE+BIF_BROWSEINCLUDEFILES+\
  15.                 BIF_DONTGOBELOWDOMAIN+BIF_RETURNONLYFSDIRS
  16.         mov     [bi.pszDisplayName],szDisplayName
  17.         mov     [bi.lpszTitle],szTitle2
  18.         ; Указатель на callback-функцию
  19.         mov     [bi.lpfn],BrowseCallbackProc
  20.  
  21.         ; Открыть диалог выбора папки
  22.         invoke  SHBrowseForFolder,bi
  23.  
  24.         ...
  25.         ...
  26.         ...
  27.  
  28. ;---------------------------------------------
  29. ; Обработчик callback-функции
  30. ;---------------------------------------------
  31. proc    BrowseCallbackProc hwnd:DWORD,uMsg:DWORD,lParam:DWORD,lpData
  32.         cmp     [uMsg],BFFM_IUNKNOWN
  33.         jne     .loc_ret
  34.  
  35. .loc_interface:
  36.         cmp     [lParam],0
  37.         je      .loc_ret
  38.  
  39.         ; Получить адрес интерфейса IFolderFilterSite
  40.         mov     eax,[lParam]
  41.         mov     eax,[eax]
  42.         stdcall dword [eax+IFolderFilterSite.QueryInterface],[lParam],\
  43.                 IID_IFolderFilterSite,pFilter
  44.  
  45.         ; Установить пользовательский фильтр
  46.         mov     eax,[pFilter]
  47.         mov     eax,[eax]
  48.         stdcall dword [eax+IFolderFilterSite.SetFilter],[pFilter],\
  49.                 pFolderFilter
  50.  
  51.         ; Объект IFolderFilterSite больше не нужен
  52.         mov     eax,[pFilter]
  53.         mov     eax,[eax]
  54.         stdcall dword [eax+IFolderFilterSite.Release],[pFilter]
  55. .loc_ret:
  56.         xor     eax,eax
  57.         ret
  58. endp
Переходим к описанию методов интерфейса фильтрации. Обработчики методов интерфейса IUnknown достаточно типовые, мы с ними уже сталкивались ранее. Здесь разница только в том, что в методе QueryInterface сравнивается значение с GUID'ом IID_IFolderFilter.
  1. ;----------------------------------------------------------------------------
  2. ; Обработчик метода QueryInterface
  3. ;----------------------------------------------------------------------------
  4. proc IFolderFilter_QueryInterface pthis:DWORD, iid:DWORD, ppvObject:DWORD
  5.         pusha
  6.  
  7.         mov     eax,[ppvObject]
  8.         cmp     eax,0
  9.         jne     @f
  10.         ; E_FAIL
  11.         mov     eax,0x80004005
  12.         jmp     .loc_ret
  13. @@:
  14.         ; Это интерфейс IFolderFilter?
  15.         push    4
  16.         pop     ecx
  17.         mov     esi,[iid]
  18.         mov     edi,IID_IFolderFilter
  19.         xor     eax,eax
  20.         repe    cmpsd
  21.         jz      .loc_call
  22.  
  23.         ; Это интерфейс IUnknown?
  24.         push    4
  25.         pop     ecx
  26.         mov     esi,[iid]
  27.         mov     edi,IID_IUnknown
  28.         xor     eax,eax
  29.         repe    cmpsd
  30.         jz      .loc_call
  31.  
  32.         ; E_NOINTERFACE
  33.         mov     eax,0x80004002
  34.         jmp     .loc_ret
  35. .loc_call:
  36.         mov     eax,[pthis]
  37.         ; Установить интерфейс
  38.         mov     ecx,[ppvObject]
  39.         mov     [ecx],eax
  40.         mov     ecx,[eax]
  41.         stdcall dword [ecx+IFolderFilter.AddRef],eax
  42. .loc_ok:
  43.         ; S_OK
  44.         xor     eax,eax
  45. .loc_ret:
  46.         mov     [esp+28],eax
  47.         popa
  48.         ret
  49. endp
  50.  
  51. ;----------------------------------------------------------------------------
  52. ; Обработчик метода AddRef
  53. ;----------------------------------------------------------------------------
  54. proc IFolderFilter_AddRef pthis:DWORD
  55.         mov     eax,[pthis]
  56.         inc     [eax+IFolderFilter.refcount]
  57.         mov     eax,[eax+IFolderFilter.refcount]
  58.         ret
  59. endp
  60.  
  61. ;----------------------------------------------------------------------------
  62. ; Обработчик метода Release
  63. ;----------------------------------------------------------------------------
  64. proc IFolderFilter_Release pthis:DWORD
  65.         push    ecx
  66.         mov     eax,[pthis]
  67.         mov     ecx,[eax+IFolderFilter.refcount]
  68.         or      ecx,ecx
  69.         jz      @f
  70.         dec     [eax+IFolderFilter.refcount]
  71.         dec     ecx
  72. @@:
  73.         mov     eax,ecx
  74.         pop     ecx
  75.         ret
  76. endp
А вот два оставшихся метода требуют особого внимания, так как именно здесь творится все колдунство. Начну с конца, то есть с обработчика метода GetEnumFlags:
  1. ;----------------------------------------------------------------------------
  2. ; Обработчик метода GetEnumFlags
  3. ;----------------------------------------------------------------------------
  4. proc IFolderFilter_GetEnumFlags pthis:DWORD, pIShellFolder:DWORD,\
  5.         pidlFolder:DWORD, phwnd:DWORD, pgrfFlags:DWORD
  6.  
  7.         ; Установить значение флагов
  8.         mov     eax,[pgrfFlags]
  9.         mov     dword [eax],SHCONTF_FOLDERS or SHCONTF_NONFOLDERS
  10.  
  11.         ; S_OK
  12.         xor     eax,eax
  13.         ret
  14. endp
Тут при запросе системы возвращаются флаги SHCONTF_FOLDERS и SHCONTF_NONFOLDERS, то есть показывать в списке элементы, которые являются каталогами и не являются каталогами. Полный список всех доступных флагов с их описаниями смотрите в MSDN. Вы можете поэкспериментировать с фильтрацией уже на этом этапе, выбирая разные комбинации флагов. Следите только за взаимоисключением флагов, например, было бы странно создать диалог выбора с флагом BIF_BROWSEINCLUDEFILES, то есть включать в выбор кроме каталогов еще и файлы, а затем отменить это правило, вернув в методе GetEnumFlags только один флаг SHCONTF_FOLDERS.

Кроме глобальных правил, вы можете устанавливать флаги для каждого отдельного элемента списка, по правилам которых будут обрабатываться его вложенные элементы. В параметре pIShellFolder обработчика приходит указатель на интерфейс IShellFolder текущего элемента. Через методы этого интерфейса можно узнать имя элемента, его свойства, а затем принять какое-то решение. Вряд ли вам когда-либо потребуется настолько детальная обработка, но знать о такой возможности будет полезно.

И вот теперь плавно подходим к самому интересному - реализации метода ShouldShow. Он вызывается системой на каждый элемент списка без исключения и должен вернуть результат S_OK - элемент можно показывать или S_FALSE - элемент показывать нельзя. Все правила проверки вы придумываете сами.
  1. ;----------------------------------------------------------------------------
  2. ; Обработчик метода ShouldShow
  3. ;----------------------------------------------------------------------------
  4. proc IFolderFilter_ShouldShow pthis:DWORD, pIShellFolder:DWORD,\
  5.         pidlFolder:DWORD, pidlItem:DWORD
  6.  
  7.         locals
  8.                 pidl   dd ?
  9.                 dAttr  dd ?
  10.                 StrRet STRRET
  11.         endl
  12.  
  13.         ; Получить атрибуты проверяемого элемента
  14.         mov     [dAttr],SFGAO_FOLDER
  15.         lea     eax,[dAttr]
  16.         push    eax
  17.  
  18.         mov     eax,[pidlItem]
  19.         mov     [pidl],eax
  20.         lea     eax,[pidl]
  21.         push    eax
  22.  
  23.         mov     eax,[pIShellFolder]
  24.         mov     eax,[eax]
  25.         stdcall dword [eax+IShellFolder.GetAttributesOf],[pIShellFolder],\
  26.                 1
  27.         ; Атрибуты получить не удалось, пропустить
  28.         or      eax,eax
  29.         jnz     .loc_no
  30.  
  31.         ; Это каталог?
  32.         mov     eax,[dAttr]
  33.         and     eax,SFGAO_FOLDER
  34.         cmp     eax,SFGAO_FOLDER
  35.         je      .loc_yes
  36.  
  37.         ; Очистить структуру STRRET
  38.         lea     edi,[StrRet]
  39.         xor     eax,eax
  40.         mov     ecx,sizeof.STRRET
  41.         rep     stosb
  42.  
  43.         ; Получить имя элемента
  44.         lea     eax,[StrRet]
  45.         push    eax
  46.  
  47.         push    SHGDN_FORPARSING
  48.  
  49.         mov     eax,[pidlItem]
  50.         push    eax
  51.  
  52.         mov     eax,[pIShellFolder]
  53.         mov     eax,[eax]
  54.         stdcall dword [eax+IShellFolder.GetDisplayNameOf],[pIShellFolder]
  55.         or      eax,eax
  56.         jnz     .loc_no
  57.  
  58.         ; Указатель на имя элемента
  59.         lea     eax,[StrRet]
  60.         mov     eax,[eax+4]
  61.  
  62.         ; Файл соответствует разрешенным типам?
  63.         invoke  PathMatchSpec,eax,szMatch
  64.         cmp     eax,TRUE
  65.         ; Не отображать в списке
  66.         jnz     .loc_no
  67.  
  68. .loc_yes:
  69.         ; S_OK
  70.         xor     eax,eax
  71.         jmp     .loc_ret
  72. .loc_no:
  73.         ; S_FALSE
  74.         xor     eax,eax
  75.         inc     eax
  76. .loc_ret:
  77.         ret
  78. endp
Как и в предыдущем методе, в параметре pIShellFolder обработчика приходит указатель на интерфейс IShellFolder текущего элемента, с помощью которого можно узнать все параметры этого элемента. В этом примере сперва проверяется атрибут элемента, все каталоги пропускаются без разговоров. Что интересно, под эту категорию попадают и zip-архивы, которые воспринимаются системой тоже как своеобразные каталоги. Если такое поведение фильтра не соответствует решаемой задаче, то придется добавить дополнительное правило фильтрации. Затем с помощью метода GetDisplayNameOf обработчик получает полное имя или путь элемента. Почему "или"? Потому что для особых системных папок типа "Мой компьютер", "Домашняя группа", "Библиотеки" и т.п. имя будет возвращено виде GUID. Дальше полученное имя файла сравнивается с маской при помощи функции PathMatchSpec, здесь используется маска "*.jpg;*.png". Если имя файла соответствует этой маске, то он отображается в списке. Во всех других случаях файлы в список не попадают.

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

Пример программы с исходным текстом (FASM)Пример программы с исходным текстом (FASM)

SHBrowseForFolder.Filtered.Demo.zip (4,863 bytes)


Поделиться ссылкой ВКонтакте
Просмотров: 461 | Комментариев: 2

Внимание! Статья опубликована больше года назад, информация могла устареть!

Комментарии

Отзывы посетителей сайта о статье
zenon (19.07.2023 в 22:52):
Красота. Спасибо!
morgot (19.07.2023 в 17:58):
Спасибо, пригодится.

Добавить комментарий

Заполните форму для добавления комментария
Имя*:
Текст комментария (не более 2000 символов)*:

*Все поля обязательны для заполнения.
Комментарии, содержащие рекламу, ненормативную лексику, оскорбления и т.п., а также флуд и сообщения не по теме, будут удаляться. Нарушителям может быть заблокирован доступ к сайту.
Наверх
Powered by PCL's Speckled Band Engine 0.2 RC3
© ManHunter / PCL, 2008-2024
При использовании материалов ссылка на сайт обязательна
Время генерации: 0.07 сек. / MySQL: 2 (0.006 сек.) / Память: 4.5 Mb
Наверх