- •Методические указания для выполнения лабораторной работы №2.6 по курсу «Операционные системы и системное программирование»
- •Цель работы
- •Теоретические сведения
- •Пример 1 «Добавление пунктов в контекстное меню для отдельных расширений файлов»
- •Использование AppWizard.
- •Интерфейс инициализации
- •Интерфейс взаимодействия с контекстным меню
- •Модификация контекстного меню
- •Отображение подсказки в строке состояния
- •Выполнение выбора пользователя
- •Регистрация расширения оболочки
- •Отладка расширения оболочки
- •Результат
- •Пример 2 «Добавление пунктов в контекстное меню для всех файлов не зависимо от расширения»
- •Дополнительные функции расширения, добавляющего пункты к контекстному меню оболочки.
- •Использование AppWizard
- •Интерфейс инициализации
- •Добавление пунктов к меню
- •Подсказка в строке состояния и "действие"
- •Выполнение выбора пользователя
- •Регистрация расширения
- •Результат
- •Другие способы регистрации расширения
- •Пример 3 «Использование разделяемой с оболочкой памяти»
- •Расширение QueryInfo
- •Использование AppWizard
- •Интерфейс инициализации
- •Создание текста для тултипа
- •Регистрация расширения оболочки
- •Пример 4 «Обработчик перетаскивания контекстного меню»
- •Обработчик перетаскивания
- •Интерфейс инициализации
- •Модификация контекстного меню
- •Создание связи
- •Обеспечение подсказки в строке состояния
- •Создание связи
- •Регистрация расширения
- •Пример 5 «Добавления новых страниц в набор свойств файлов»
- •Обработчик набора свойств
- •Использование AppWizard
- •Интерфейс инициализации
- •Добавление страниц свойств
- •Неприятная ситуация с периодом жизни объектов
- •Функции обратного вызова страницы свойств
- •Обработчики сообщений страницы свойств
- •Регистрация расширения
- •Пример 6 «Обработчик сбрасывания в меню Send To»
- •Обработчик сбрасывания
- •Использование AppWizard
- •Интерфейс инициализации
- •Участвуем в операции drag and drop
- •DragEnter()
- •DragLeave()
- •Регистрация расширения
- •Пример 7 «Owner-drawn меню в расширениях контекстных меню и по созданию расширения контекстного меню, которое отзывается на правый щелчок на фоне окна каталога» Расширение 1 - Пункты меню owner-drawn.
- •Использование AppWizard
- •Интерфейс инициализации.
- •Взаимодействие с контекстным меню
- •Модифицирование контекстного меню.
- •Отображение всплывающей подсказки в строке состояния.
- •Выполнение выбора пользователя.
- •Рисование пункта меню.
- •Обработка wm_measureitem
- •Обработка wm_drawitem
- •Регистрация расширения оболочки
- •Расширение 2 - Обработка щелчка правой кнопкой мыши на фоне окна каталога.
- •Отличия в iShellExtInit::Initialize()
- •Отличия в регистрации.
- •Пример 8 «Добавление колонки в окно детального просмотра Проводника» Детальный просмотр в Windows 2000
- •Использование AppWizard
- •Интерфейс расширения
- •Инициализация
- •Перечисление новых столбцов
- •Отображение данных в столбцах
- •Небольшое отступление - обработка тэгов id3
- •Как это все выглядит?
- •Регистрация расширения оболочки
- •Еще одна полезная штучка - InfoTips
- •Пример 9 «Настройка иконок, отображаемых для файлов заданного типа» Файловые иконки в Проводнике
- •Использование AppWizard
- •Интерфейс расширения
- •Интерфейс инициализации
- •Интерфейс iExtractIcon
- •Извлечение методом 1
- •Извлечение методом 2
- •Регистрация расширения
- •Пример 10 «Расширение оболочки для изменения иконок у dll в зависимости от их типа»
- •Как установить
- •Подробности реализации
- •Задания для лабораторных работ
- •Содержание отчета
Отображение данных в столбцах
Последний метод интерфейса IColumnProvider, GetItemData(), который Проводник вызывает, чтобы показать в столбцах данные о файле, имеет следующий прототип:
|
HRESULT IColumnProvider::GetItemData ( LPCSHCOLUMNID pscid, LPCSHCOLUMNDATA pscd, VARIANT* pvarData ); |
Структура SHCOLUMNID показывает, данные какого столбца нужны Проводнику. Она содержит ту же самую информацию, которую мы дали Проводнику в GetColumnInfo(). Структура SHCOLUMNDATA содержит подробности о файле или каталоге, включая его путь. Мы можем использовать эту информацию, чтобы решить, хотим ли мы предоставить какие-либо данные для этого файла или каталога. pvarData - это указатель на VARIANT, в котором мы сохраним данные для показа их Проводником. VARIANT - это воплощение в C "свободного" типа переменных, существующего в VB и других скриптовых языках. Переменная VARIANT состоит фактически из двух частей - типа и данных. В ATL есть удобный класс CComVariant, делающий все черную работу по инициализации и установке переменных типа VARIANT. Я продемонстрирую его использование ниже.
Небольшое отступление - обработка тэгов id3
Сейчас самое время показать, как наше расширение будет читать и сохранять информацию из тэгов ID3. Тэг ID3v1 - это структура фиксированной длины, добавленная в конец файла MP3, которая выглядит примерно так:
|
struct CID3v1Tag { char szTag[3]; // Всегда 'T','A','G' char szTitle[30]; char szArtist[30]; char szAlbum[30]; char szYear[4]; char szComment[30]; char byGenre; }; |
Все поля - простые символьные, строки не обязательно заканчиваются нулем и требуют немного специальной обработки. Первое поле, szTag, содержит символы "TAG", идентифицирующие тэг ID3. byGenre - это номер, который идентифицирует жанр песни. (Существует предопределенный список жанров и их числовых идентификаторов, доступный на ID3.org.)
Нам также будет нужна дополнительная структура, содержащая тэг ID3 и имя файла, из которого был взят этот тэг. Эта структура будет использоваться в кэше, о котором я вскоре расскажу.
|
#include <string> #include <list> typedef std::basic_string<TCHAR> tstring; // a TCHAR string
struct CID3CacheEntry { tstring sFilename; CID3v1Tag rTag; };
typedef std::list<CID3CacheEntry> list_ID3Cache; |
Объект CID3CacheEntry содержит имя файла и тэг ID3, сохраненный в этом файле. list_ID3Cache - это связанный список структур CID3CacheEntry.
OK, вернемся к нашему расширению. Вот начало нашей функции GetItemData(). Сначала мы проверяем структуру SHCOLUMNID, чтобы удостовериться, что нас вызывают для одного из наших собственных столбцов.
|
#include <atlconv.h>
STDMETHODIMP CMP3ColExt::GetItemData ( LPCSHCOLUMNID pscid, LPCSHCOLUMNDATA pscd, VARIANT* pvarData ) { USES_CONVERSION; LPCTSTR szFilename = OLE2CT(pscd->wszFile); char szField[31]; TCHAR szDisplayStr[31]; bool bUsingBuiltinCol = false; CID3v1Tag rTag; bool bCacheHit = false;
// Проверить, что format id и номер столбца - те, что мы ожидаем. if ( pscid->fmtid == *_Module.pguidVer ) { if ( pscid->pid > 2 ) return S_FALSE; } |
Если format ID - это наш собственый GUID, property ID должен быть 0, 1, или 2, как и те ID, что мы мы использовали в GetColumnInfo(). Если по каким либо причинам ID выходит за эти рамки мы возвращаем S_FALSE, чтобы сообщить оболочке, что мы не имеем никаких данных, и столбец должен быть пустым.
Далее, мы сравниваем format ID с FMTID_SummaryInformation и проверяем идентификатор свойства property ID, чтобы посмотреть, является ли оно свойством, которое мы предоставляем.
|
else if ( pscid->fmtid == FMTID_SummaryInformation ) { bUsingBuiltinCol = true;
if ( pscid->pid != 2 && pscid->pid != 4 && pscid->pid != 6 ) return S_FALSE; } else { return S_FALSE; } |
Далее, мы проверяем атрубуты файла, имя которого нам было передано. Если это каталог или файл "в оффлайне" (т.е. был перемещен на другой носитель, например ленту), мы спокойно вываливаемся. Также мы проверяем расширение файла и возвращаем S_FALSE, если оно не MP3.
|
// Если нас вызывают с каталогом (вместо файла) мы можем // выйти немедленно. // Также выходим, если файл "в оффлайне" (например сохранен на ленте, или // другом носителе). if ( pscd->dwFileAttributes & (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_OFFLINE) ) return S_FALSE;
// Проверяем расширение файла. Если не MP3, мы можем выйти. if ( 0 != lstrcmpiW ( pscd->pwszExt, L".mp3" )) return S_FALSE; |
В этом месте мы решили, что хотим работать с файлом. Начинаем использовать наш кэш тэгов ID3. Документация MSDN говорит, что оболочка группирует запросы к GetItemData() по имени файла, это означает, что вызывы GetItemData() с одним и тем же именем файла будут идти подряд. Мы можем воспользоваться этим поведением и кэшировать тэг ID3 для отдельного файла, так что нам не придется читать тэг из этого файла снова при следующих запросах.
Сначала мы просматриваем наш кэш (сохраняемый как переменная-член m_ID3Cache), сравнивая имя файла в кэше с именем, переданным в функцию. Если мы находим имя в нашем кэше, мы используем связанный с ним тэг ID3.
|
// Искать имя файла в нашем кэше. list_ID3Cache::const_iterator it, itEnd;
for ( it = m_ID3Cache.begin(), itEnd = m_ID3Cache.end(); !bCacheHit && it != itEnd; it++ ) { if ( 0 == lstrcmpi ( szFilename, it->sFilename.c_str() )) { CopyMemory ( &rTag, &it->rTag, sizeof(CID3v1Tag) ); bCacheHit = true; } } |
Если bCacheHit = false после этого цикла, мы должны читать файл и смотреть, имеет ли он тэг ID3. Вспомогательная функция ReadTagFromFile() делает грязную работу по чтению последних 128 байтов файла и возвращает TRUE в случае успеха или FALSE если произошла ошибка чтения файла. Заметьте, что ReadTagFromFile() возвращает последние 128 байт, независимо от того, являются ли они действительно тэгом ID3.
|
// Если тэга файла нет в нашем кэше, читаем тэг из файла. if ( !bCacheHit ) { if ( !ReadTagFromFile ( szFilename, &rTag )) return S_FALSE; |
Итак, теперь у нас есть тэг ID3. Мы проверяем размер нашего кэша, и если он содержит 5 вхождений, удаляем самое старое, чтобы освободить место для нового. (5 - это произвольный маленький номер.) Мы создаем новый объект CID3CacheEntry и добавляем его в список.
|
// Мы храним тэги только для последних 5 кэшируемых файлов - удаляем самое старое // вхождение, если кэш имеет больший чем 4 вхождения. while ( m_ID3Cache.size() > 4 ) { m_ID3Cache.pop_back(); }
// Добавим новый тэг ID3 к нашему кэшу. CID3CacheEntry entry;
entry.sFilename = szFilename; CopyMemory ( &entry.rTag, &rTag, sizeof(CID3v1Tag) );
m_ID3Cache.push_front ( entry ); } // end if(!bCacheHit) |
Наш следующий шаг - проверить первые три байта сигнатуры, чтобы определить, ID3-тэг это или нет. Если нет, мы можем вернуть S_FALSE немедленно.
|
// Проверяем, имеем ли мы действительно тэг ID3, ища сигнатуру. if ( 0 != StrCmpNA ( rTag.szTag, "TAG", 3 )) return S_FALSE; |
Затем мы читаем из тэга ID3 поле, которое соответствует свойству, затребованному оболочкой. Для этого нужно только протестировать идентификаторы свойств. Например, для поля Title:
|
// Форматировать строку подробностей. if ( bUsingBuiltinCol ) { switch ( pscid->pid ) { case 2: // заголовок песни CopyMemory ( szField, rTag.szTitle, countof(rTag.szTitle) ); szField[30] = '\0'; break; ... } |
Обратите внимание, что наш буфер szField - длиной 31 символ, что на 1 символ длинее, чем соответствующее поле ID3v1. Таким образом мы гарантируем, что строка всегда будет должным образом закончена нулевым символом в конце. Флажок bUsingBuiltinCol был установлен ранее, когда мы проверили пару FMTID/PID. Этот флажок нам нужен, т.к. одного PID не достаточно, чтобы идентифицировать столбец - столбцы Title и MP3 Genre оба имеют PID=2.
В этом месте szField содержит строку, которую мы считали из тэга ID3. Редактор ID3 тэгов программы WinAmp дополняет строки пробелами вместо нулевых символов, поэтому мы правим строки, удаляя конечные пробелы:
|
// WinAmp дополняет строки с пробелами вместо нулей, так что удаляем любые // завершающие строку пробелы. StrTrimA ( szField, " " ); |
И наконец мы создаем объект CComVariant и сохраняем в нем строку szDisplayStr. Мы вызываем CComVariant::Detach(), чтобы скопировать данные из CComVariant в VARIANT, предоставленный Проводником.
|
// Создать VARIANT со строкой подробностей, и возвратить его назад оболочке. CComVariant vData ( szField );
vData.Detach ( pvarData );
return S_OK; } |
