Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
ОСиСП - методички / OS&SP_Lab_2.6(shell).doc
Скачиваний:
95
Добавлен:
18.05.2015
Размер:
1.12 Mб
Скачать

Отображение данных в столбцах

Последний метод интерфейса 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;

}

Соседние файлы в папке ОСиСП - методички