Users can store documents, images, and other files in many locations—on different types of hardware installed locally or on other computers on the network. In the past, files were often physically stored in a location according to their type —for example, images in the My Pictures folder, documents in the My Documents folder, and so on. However, a far more powerful and modern way to access files is via their type rather than through their location. This is the purpose of Windows 7 Libraries. Files from many different locations can be accessed through a single logical location according to their type even though they are stored in many different locations. Libraries are user defined collections of content that are indexed to enable faster search and sorting. Hilo uses the Windows 7 Libraries feature to access the user’s images.
Using the Shell Namespace
The Windows Shell namespace provides access to a wide range of objects through a hierarchical structure. Windows Vista replaced the older constant special item ID list (CSIDL) naming of special folders with known folder IDs. In both cases the special folders contain specific types of data: video, pictures, or music, but the folders are accessed using an ID that at runtime can give access to the disk storage path. Known folders in Windows Vista offer more features than CSIDL including the ability to change the storage location without changing the applications that rely on the known folder (CSIDL only allows My Documents location to be changed by the user).
Windows 7 extends the idea of known folders with libraries. Windows 7 libraries are user-defined collections of folders, and actions that are performed on the library will be applied to all folders in the library. This means that when you search a Windows 7 library you will search all the folders that are part of the library, and when you stack the items in a library the stacks will contain items from all the folders in the library regardless of the actual location of those folders. Windows 7 indexing is applied to Windows 7 libraries which mean that searches on a library will be performed on all the folders in the library.
Windows Explorer displays libraries in the navigation pane, as shown in Figure 1. The properties of a library shows the actual folders that will be included. You can use this property dialog to add or remove folders and to determine which folder will be treated as the default save location. You can add any folders on the local disks that your account has access too, and any folders on external drives like USB drives or shares on a server. You cannot add folders on removable drives, nor on remote shares that are not available offline that (MSDN lists the folders that cannot be put in libraries).
When you select a library, Windows Explorer displays an aggregate view of the files and folders that are part of the library, as shown in Figure 2.
Libraries are logical representations of user content. This means that you store files in the folders that are part of the library and not in the library itself. So for example, the Documents library is the default location for documents and contains the user’s documents in their My Documents folder and any documents in the Public Documents folder. Although Windows Explorer displays the Documents library as if it is a folder, no physical folder exists, so if a user saves a file to the Documents library then the file will actually be saved to the default save location (in Figure 1 this save location is set to My Documents).
The Windows 7 Application Programming Interface (API) provides COM-based objects used to access the contents of the libraries. You can traverse through the logical hierarchy through these shell item objects without knowing the absolute storage location paths (although it is possible to obtain the system file path). All items in the shell are represented by an object with the IShellItem interface, but specific shell items will implement other interfaces (for example, folder objects also implement the IShellFolder interface). It is very important that Windows 7 applications use the Shell API to access shell items rather than using absolute file system file paths. Equally important is that applications use the Windows 7 common file dialogs because these dialogs show the system’s libraries and provide appropriate IShellItem objects selected through the dialog.
Using Shell Items
Central to the shell namespace are objects called shell items that implement the IShellItem interface. The new common file dialogs (CLSID_FileOpenDialog orCLSID_FileSaveDialog) refer to items through shell item objects and return an IShellItem or an array of such items through the IShellItemArray interface. The caller can then use an individual IShellItem object to get a file system path or to open a stream object on the item to read or write information, or query for one of the several shell interfaces implemented for specific shell types. The use of IShellItem objects is important because these file dialogs can access items in both file system folders and other virtual folders that you find in the shell, including libraries. In addition, Windows 7 provides a new object, CLSID_ShellLibrary, specifically to access libraries.
To use the shell API in C++ you include the shobjidl.h header file. This file declares the shell interfaces and the symbols for the CLSIDs for the shell objects that you can create, and it also contains helper methods. Listing 1 shows simple code to create and use a shell item. The SHCreateItemFromParsingName method takes a system file path and returns a shell item object. In this example the shell item object is used to obtain a user readable string for the item.
LPWSTR szFilePath = GetFileNameFromSomewhere(); // Get a file name from somewhere. IShellItem* pItem = nullptr; HRESULT hr = ::SHCreateItemFromParsingName( szFilePath, nullptr, IID_PPV_ARGS(&pItem)); if (SUCCEEDED(hr)) { LPWSTR szName = nullptr; hr = pItem->GetDisplayName(SIGDN_NORMALDISPLAY, &szName); if (SUCCEEDED(hr)) { wprintf(L"Shell item name: %s\n", szName); ::CoTaskMemFree(szName); } pItem->Release(); }
IShellItem* pItem = nullptr; HRESULT hr = ::SHCreateItemInKnownFolder( FOLDERID_Libraries, 0, nullptr, IID_PPV_ARGS(&pItem)); if (SUCCEEDED(hr)) { DWORD dwAttr = 0; hr = pItem->GetAttributes(SFGAO_FILESYSANCESTOR, &dwAttr); if (SUCCEEDED(hr)) { if (SFGAO_FILESYSANCESTOR == dwAttr) { wprintf(L"Item is a file system folder\n"); } } pItem->Release(); }
Accessing Shell Item Properties
You can get additional information about a shell item by requesting the value of an item property. To do this you should use the methods on the IShellItem2 rather than IShellItem. Each property is identified by the values in a PROPERTKEY structure and propkey.h defines the initialized values for the properties that you can request. Properties can be strings, numeric values, or dates, and there are methods on IShellItem2 to return appropriate values. For example, Listing 3 shows how to access the date that the item was created by accessing the PKEY_DateCreated property and it assumes the pItem variable has already been assigned to an IShellItem object.IShellItem2* pItem2 = nullptr; hr = pItem->QueryInterface(&pItem2); if (SUCCEEDED(hr)) { FILETIME ft = {0}; pItem2->GetFileTime(PKEY_DateCreated, &ft); SYSTEMTIME st = {0}; ::FileTimeToSystemTime(&ft, &st); wprintf( L"Date Created: %04d-%02d-%02d %02d:%02d:%02d\n", st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond); pItem2->Release(); }
Binding to Handler Objects
The methods of the IShellItem and IShellItem2 interfaces give limited access to the shell object, however, you can obtain a handler object to get additional access by calling theIShellItem::BindToHandler method. There are many types of handler objects and different shell items will use different handler objects. If the item is a file then you can access aIStream handler object, if the item is a folder then you can access an IShellFolder handler. The BindToHandler method is passed a GUID (defined in shlguid.h) to the type of the handler object you want to obtain. and the method returns a pointer to the handler object. So assuming that the pItem variable is the shell item for the Libraries folder initialized in Listing 2, the code in Listing 4 obtains an enumerator object to enumerate the child items and print their names.IEnumShellItems* pEnum = nullptr; hr = pItem->BindToHandler(nullptr, BHID_EnumItems, IID_PPV_ARGS(&pEnum)); if (SUCCEEDED(hr)) { IShellItem* pChildItem = nullptr; ULONG ulFetched = 0; do { hr = pEnum->Next(1, & pChildItem, &ulFetched); if (FAILED(hr)) break; if (ulFetched != 0) { LPWSTR szChildName = nullptr; child->GetDisplayName(SIGDN_NORMALDISPLAY, &szChildName); wprintf(L"Obtained %s\n", szChildName); CoTaskMemFree(szChildName); pChildItem ->Release(); } } while (hr != S_FALSE); pEnum->Release(); }
Using Common File Dialogs
In addition, other APIs will act upon shell item objects, for example you can use the Common File Dialog to obtain a shell item with the path to where you want a file saved. The Common File Dialog object does not open or save a file, instead it gives information about the shell item that the user identifies. Listing 5 shows the basic code to save a file, here the pInitialItem variable is an initialized shell item object that indicates the file that is initially shown in the Save As dialog by calling the IFileSaveAsDialog::SetSaveAsItemmethod. The IFileSaveDialog::Show method displays the dialog and if the user clicks the OK button this method will return S_OK, otherwise if the user clicks the Cancel button then the dialog will return ERROR_CANCELLED. If the user has specified a file in the dialog then a shell item with information about this file is obtained through a call toIFileSaveDialog::GetResult. In this case, the shell item object will not necessarily reference an actual file, the shell item object simply contains the path and file name provided by the user in the dialog. The code then has to provide its own code to copy the file referenced by the pInitialItem shell item to the location specified by the pSaveAsItem shell item.IFileSaveDialog* pShellDialog; hr = CoCreateInstance( CLSID_FileSaveDialog, nullptr, CLSCTX_INPROC, IID_PPV_ARGS(&pShellDialog)); if (SUCCEEDED(hr)) { pShellDialog->SetSaveAsItem(pInitialItem); pShellDialog->Show(nullptr); if (SUCCEEDED(hr)) { IShellItem* pSaveAsItem = nullptr; hr = pShellDialog->GetResult(&pSaveAsItem); if (SUCCEEDED(hr)) { CopyFileTo(pInitialItem, pSaveAsItem); // Call the code to Copy copy the actual file… pSaveAsItem->Release(); } } else { // If the user clicks cancel the return value is 0x800704c7, that is // HRESULT_CODE(hr) == ERROR_CANCELLED } pShellDialog->Release(); }
Using the Shell Library Object
The Windows 7 API provides a COM object to allow you to administer libraries. The shell library object implements the IShellLibrary interface and the Windows API provides a helper method SHCreateLibrary that creates an uninitialized object. You can call IShellLibrary::LoadLibraryFromKnownFolder to initialize the object to refer to a known folder. The shell library object can be used to add or remove folders from the library, enumerate the folders in the library, and set the default save folder. The library object is used to write to the description file (library-ms file) for the library, and once you have finished changing the library settings you must call the IShellLibrary:Commit method if the library already exists, or the IShellLibrary::Save if this is a new library.Using Windows 7 Libraries in Hilo
The carousel and media panes in the Hilo Browser show thumbnail representations of folders and photos, so the message handlers for these classes need to be initialized with information about these items. In Hilo this is done through a struct called ThumbnailInfo that has a member which is a reference to an IShellItem object. When a user selects a folder in the carousel, the window message handler enumerates the items in the folder and displays the subfolders in the inner orbital and the photos in the media pane. To do this, Hilo has to enumerate and filter shell items.Initializing the Browser Carousel
The BrowserApplication class has a variable called m_currentBrowseLocationItem that is a shell item to the initial folder shown by the Browser carousel. The variable is initialized when the Browser is started with the code shown in Listing 6. The first part of this code calls the SHCreateItemInKnownFolder method to get a shell item object for the Pictures library and if this call is not successful the code then calls SHGetKnownFolderItem to obtain the shell item for the Computer folder which gives access to all the hard drives (including external drives) accessible by the computer.// No location has been defined yet, so we just load the pictures library if (nullptr == m_currentBrowseLocationItem) { // Default to Libraries library hr = ::SHCreateItemInKnownFolder( FOLDERID_PicturesLibrary, 0, nullptr, IID_PPV_ARGS(&m_currentBrowseLocationItem)); } // If the Pictures Library was not not found if (FAILED(hr)) { // Try obtaining the "Computer" known folder hr = ::SHGetKnownFolderItem( FOLDERID_ComputerFolder, static_cast<KNOWN_FOLDER_FLAG>(0), nullptr, IID_PPV_ARGS(&m_currentBrowseLocationItem)); } if (SUCCEEDED(hr)) { ComPtr<IPane> carouselPane; hr = carouselPaneHandler.QueryInterface(&carouselPane); if (SUCCEEDED(hr)) { hr = carouselPane->SetCurrentLocation(m_currentBrowseLocationItem, false); } }
Whenever the user selects a folder in the carousel or in the history list the shell item for the newly selected item is passed to SetCurrentLocation of the carousel handler and hence the carousel and media pane are updated with the items in the selected folder.
Enumerating Folders
Hilo provides a utility class (in the Common project) called ShellItemsLoader. This class has one public static method called EnumerateFolderItems that is passed the shell item object of the folder to enumerate, a value indicating if the method should return the folders or image file items in the folder, and a parameter indicating whether the search is recursive or not. This method returns a vector of the shell item objects for the requested items.
Listing 7 shows the first part of this method, 1 indicates the type of objects to search for and is used to add named values to the itemKinds vector. When the method enumerates items in the folder it obtains the type of each item, and if the type of the item is one of those in the itemKinds vector the item is added to the shellItems vector returned to the caller.
HRESULT ShellItemsLoader::EnumerateFolderItemsNonRecursive( IShellItem* currentBrowseLocation, ShellFileType fileType, std::vector<ComPtr<IShellItem> >& shellItems) { std::vector<std::wstring> itemKinds; if ((fileType & FileTypeImage) == FileTypeImage) { itemKinds.push_back(L"picture"); } if ((fileType & FileTypeImage) == FileTypeVideo) { itemKinds.push_back(L"video"); } if ((fileType & FileTypeImage) == FileTypeAudio) { itemKinds.push_back(L"music"); } // Followed by code to do the enumeration
// Enumerate all objects in the current search folder ComPtr<IShellFolder> searchFolder; HRESULT hr = currentBrowseLocation->BindToHandler( nullptr, BHID_SFObject, IID_PPV_ARGS(&searchFolder)); if (SUCCEEDED(hr)) { // Enumeration code, see Listing 9 }
bool const isEnumFolders = (fileType & FileTypeFolder) == FileTypeFolder; SHCONTF const flags = isEnumFolders ? SHCONTF_FOLDERS : SHCONTF_NONFOLDERS; ComPtr<IEnumIDList> fileList; if (S_OK == searchFolder->EnumObjects(nullptr, flags, &fileList)) { ITEMID_CHILD* idList = nullptr; unsigned long fetched; while (S_OK == fileList->Next(1, &idList, &fetched)) { ComPtr<IShellItem2> shellItem; hr = SHCreateItemWithParent(nullptr, searchFolder, idList, IID_PPV_ARGS(&shellItem)); if (SUCCEEDED(hr)) { // Check to see if the item should be added to the returned item vector // See Listing 10 } ILFree(idList); } } } return hr;
if (isEnumFolders) { shellItems.push_back(static_cast<IShellItem*>(shellItem)); } else { // Check if we the item is correct wchar_t *itemType = nullptr; hr = shellItem->GetString(PKEY_Kind, &itemType); if (SUCCEEDED(hr)) { auto found = std::find(itemKinds.begin(), itemKinds.end(), itemType); if (found != itemKinds.end()) { shellItems.push_back(static_cast<IShellItem*>(shellItem)); } ::CoTaskMemFree(itemType); } }
No comments:
Post a Comment
Thank you for Commenting Will reply soon ......