Тюнинг функции SHBrowseForFolder
Функция API SHBrowseForFolder предназначена для выбора какой-нибудь папки из дерева каталогов всех дисков системы. Со своей задачей она, в принципе, справляется неплохо, но имеет ряд недостатков: окно выбора открывается в произвольных местах экрана, нельзя задать начальный каталог, нельзя менять заголовок окна и т.п. Если посмотреть внимательно на структуру BROWSEINFO, которую функция использует в работе, то в ней обнаруживается интересный параметр - указатель на callback-функцию BrowseCallbackProc. Эта функция получает управление при возникновении различных событий в окне выбора папок. Попробуем с ее помощью немного расширить базовый функционал SHBrowseForFolder. Сперва в сегменте данных определим необходимые значения для создания самого диалога выбора.Code (Assembler) : Убрать нумерацию
- ; Описание структуры BROWSEINFO
- struct BROWSEINFO
- hwndOwner dd ?
- pidlRoot dd ?
- pszDisplayName dd ?
- lpszTitle dd ?
- ulFlags dd ?
- lpfn dd ?
- lParam dd ?
- iImage dd ?
- ends
- ; Структура для работы с папками
- bi BROWSEINFO
- ; Буфер, который получит выбранных путь
- szDisplayName rb MAX_PATH
- ; Текст подсказки в окне выбора папки
- szTitle db 'Please select folder',0
Code (Assembler) : Убрать нумерацию
- ...
- ; Заполнить структуру
- mov [bi.hwndOwner],NULL
- mov [bi.ulFlags],BIF_RETURNONLYFSDIRS+BIF_DONTGOBELOWDOMAIN
- mov [bi.pszDisplayName],szDisplayName
- mov [bi.lpszTitle],szTitle
- ; Указатель на callback-функцию
- mov [bi.lpfn],BrowseCallbackProc
- ; Открыть диалог выбора папки
- invoke SHBrowseForFolder,bi
- ...
Теперь перейдем к самому обработчику функции BrowseCallbackProc. В FASM не определены константы, которые в ней используются, сделаем это самостоятельно. Также нам потребуются некоторые дополнительные данные.
Code (Assembler) : Убрать нумерацию
- ; Неообходимые константы для callback-функции
- BFFM_INITIALIZED = 1
- BFFM_SELCHANGED = 2
- BFFM_ENABLEOK = (WM_USER + 101)
- BFFM_SETSELECTION = (WM_USER + 102)
- BFFM_SETOKTEXT = (WM_USER + 105)
- ; Временный буфер для задания начального пути
- tmp_buff rb MAX_PATH
- ; Текст заголовка окна
- szMainTitle db 'FASM - Simple and powerful',0
- ; Текст кнопки OK (должен быть в юникоде)
- szOkButton du 'Yes, my Lord',0
Code (Assembler) : Убрать нумерацию
- ;---------------------------------------------
- ; Обработчик callback-функции
- ;---------------------------------------------
- proc BrowseCallbackProc hwnd:DWORD,uMsg:DWORD,lParam:DWORD,lpData
- cmp [uMsg],BFFM_INITIALIZED
- je .loc_init
- cmp [uMsg],BFFM_SELCHANGED
- je .loc_change
- .loc_ret:
- xor eax,eax
- ret
- .loc_init:
- ; Установить иконку окна
- invoke GetModuleHandle,0
- invoke LoadIcon,eax,1
- invoke SendMessage,[hwnd],WM_SETICON,ICON_BIG,eax
- ; Получить текущий каталог
- invoke GetCurrentDirectory,MAX_PATH,tmp_buff
- ; Установить начальный каталог в окне
- invoke SendMessage,[hwnd],BFFM_SETSELECTION,TRUE,tmp_buff
- ; Изменить стиль окна
- invoke GetWindowLong,[hwnd],GWL_EXSTYLE
- add eax,WS_EX_LAYERED
- invoke SetWindowLong,[hwnd],GWL_EXSTYLE,eax
- ; Установить атрибут прозрачности
- invoke SetLayeredWindowAttributes,[hwnd],0,200,LWA_ALPHA
- ; Изменить текст в заголовке окна
- invoke SetWindowText,[hwnd],szMainTitle
- ; Изменить текст на кнопке OK
- invoke SendMessage,[hwnd],BFFM_SETOKTEXT,NULL,szOkButton
- ; Заблокировать кнопку OK
- invoke SendMessage,[hwnd],BFFM_ENABLEOK,NULL,FALSE
- ; Субклассировать обработчик окна
- invoke SetWindowLong,[hwnd],GWL_WNDPROC,NewWindowProc
- ; Сохранить адрес предыдущего обработчика
- invoke SetWindowLong,[hwnd],GWL_USERDATA,eax
- ; Установить окно по центру экрана
- stdcall WindowToCenter,[hwnd],0
- jmp .loc_ret
- .loc_change:
- ; Разблокировать кнопку OK
- invoke SendMessage,[hwnd],BFFM_ENABLEOK,NULL,TRUE
- jmp .loc_ret
- endp
Code (Assembler) : Убрать нумерацию
- proc NewWindowProc hwnd:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD
- ; Нажатие левой клавиши мыши?
- cmp [uMsg],WM_LBUTTONDOWN
- je .loc_drag
- ; Передать управление предыдущему обработчику
- invoke GetWindowLong,[hwnd],GWL_USERDATA
- invoke CallWindowProc,eax,[hwnd],[uMsg],[wParam],[lParam]
- ret
- .loc_drag:
- ; Перетаскивание окна
- invoke ReleaseCapture
- invoke SendMessage,[hwnd],WM_SYSCOMMAND,61458,0
- .exit_proc:
- ; Подавить сообщение и вернуть из обработчика FALSE
- xor eax,eax
- ret
- endp
В приложении пример программы, реализующей все вышеперечисленные новые функции. Для сравнения в нее добавлена функция открытия обычного диалога выбора папки.
Просмотров: 9303 | Комментариев: 5
Внимание! Статья опубликована больше года назад, информация могла устареть!
Комментарии
Отзывы посетителей сайта о статье
semiono
(02.10.2010 в 22:10):
"окно выбора открывается в произвольных местах экрана,"
Самое противное что есть, это беспорядочные окна! Всегда борюсь за порядок на рабочем столе...
Самое противное что есть, это беспорядочные окна! Всегда борюсь за порядок на рабочем столе...
Puk
(02.05.2010 в 17:15):
Так и писали бы что рефералы нужны! Я их отключаю в браузере, разрешаю только в экстренных случаях, когда без них сайт неадекватен.
Zummenix
(10.02.2010 в 19:53):
Отличный пример :)
в обработчик .loc_change добавил проверку
.loc_change:
; Заблокировать кнопку OK
invoke SendMessage,[hwnd],BFFM_ENABLEOK,NULL,FALSE
; Проверить выбранный путь
invoke SHGetPathFromIDList,[lParam],szDisplayName
cmp byte [szDisplayName],0
je .loc_ret
; Если все в порядке, то разблокировать кнопку OK
invoke SendMessage,[hwnd],BFFM_ENABLEOK,NULL,TRUE
jmp .loc_ret
в обработчик .loc_change добавил проверку
.loc_change:
; Заблокировать кнопку OK
invoke SendMessage,[hwnd],BFFM_ENABLEOK,NULL,FALSE
; Проверить выбранный путь
invoke SHGetPathFromIDList,[lParam],szDisplayName
cmp byte [szDisplayName],0
je .loc_ret
; Если все в порядке, то разблокировать кнопку OK
invoke SendMessage,[hwnd],BFFM_ENABLEOK,NULL,TRUE
jmp .loc_ret
SAY
(09.02.2010 в 08:10):
Здорово!
Добавить комментарий
Заполните форму для добавления комментария
Добавлю, szDisplayName должна быть статично определена в сегменте данных.
Иначе функция SHBrowseForFolder(..) начинает вести себя непредсказуемо и очень неприятно. Что и стоило пары часов нехороших ощущений.
Ну, например, если определить строку в стеке локально, то первый же вызов SHBrowseForFolder(..) заканчивался записью по невалидному адресу в shell32.dll (а это должна быть ссылка на сегмент данных shell32.dll в нормальном случае).
При этом если перед вызовом этой функции в диалоге генерируется хотя бы один вызов MessageBox'а или одна операция с ListBox'ом, тогда дальше SHBrowseForFolder(..) уже работает нормально даже при локальном определении этой строки. Почему-то.
В общем, такие вот дела..