文件预览对话框的实现

来源:互联网 发布:增加字段的sql语句 编辑:程序博客网 时间:2024/05/21 00:19

文件预览可对所有在资源管理器中能够进行图像预览的文件进行预览,而不仅仅只是图片文件。如网页文件,甚至是CAD文件,像作者在进行UG二次开发时就使用了本文所使用的方法预览UG的part文件。
关于怎么定制通用对话框已经有很多文章讲了,这里就不讲了,我这里主要讲的是如何从文件中提取出它的预览图像。

对于文件预览图的提取,系统提供了一个Shell接口,IExtractImage,通过它的Extract方法就可以取出预览图的位图句柄。而IExtractImage接口可以通过IShellFolder接口的GetUIObect方法获得。这个IShellFolder是文件所在文件夹的IShellFolder。要取得任意文件夹的IShellFolder,必须从桌面的IShellFolder开始一级一级的往下找,因为取得IShellFolder需要从其父IShellFolder的BindToObject得到,在其中使用相对的PIDL。整个操作都是以PIDL为基础的,这个PIDL就是外壳命名空间的对象的ID,关于外壳命名空间及项目ID的解释见我的另一篇翻译《外壳命名空间》。下面给出几个有用的函数:

// 从文件的全路径得到它的绝对PIDL
LPITEMIDLIST ParsePidlFromPath(LPCSTR lpszPath)
{
 //存放以Unicode内码表示的路径字符串的缓冲区
 OLECHAR szOleChar[MAX_PATH];

 //“桌面“的IshellFolder接口指针
 LPSHELLFOLDER lpsfDeskTop;

 //返回的PIDL
 LPITEMIDLIST lpifq;

 ULONG ulEaten, ulAttribs;
 HRESULT hres;

 //得到“桌面”的IshellFolderr 接口指针
 SHGetDesktopFolder(&lpsfDeskTop);

 //将Ansi字符集的路径字符串转换成Unicode字符串,存入szOleChar
 MultiByteToWideChar(CP_ACP,MB_PRECOMPOSED, lpszPath, -1, szOleChar, sizeof(szOleChar));

 //将szOleChar,中的路径径字符串翻译成相应的PIDL,存入lpifq
 hres = lpsfDeskTop ->ParseDisplayName(NULL, NULL, szOleChar, &ulEaten, &lpifq, &ulAttribs);

 hres = lpsfDeskTop->Release();

 //如果翻译失败,则返回NULL

 if(FAILED(hres))
  return NULL;

 return lpifq;
}

// 取出一个PIDL的下一个PIDL,请参看《外壳命名空间》的空间对象的ID的图示
LPITEMIDLIST GetNextItem(LPITEMIDLIST pidl)
{
 unsigned short nLen = pidl->mkid.cb;
 if (nLen == 0)
  return NULL;
 return (LPITEMIDLIST)((LPBYTE)pidl + nLen);
 
}

// 取得IDL的大小
int GetPIDLSize(LPITEMIDLIST lpidl)
{
 unsigned short cb = 0;
 while (lpidl)
 {
  cb = (unsigned short)(cb + lpidl->mkid.cb);
  lpidl = GetNextItem(lpidl);
 }
 return (unsigned short)(cb + 2);
 
}

// 将两个PIDL相减,得到相对的ID,lpi1必须比包含lpi2
LPITEMIDLIST SubtractIDList(LPITEMIDLIST lpi1, LPITEMIDLIST lpi2)
{
 if(lpi1 == NULL || lpi2 == NULL || lpi1 == lpi2)
 {
  return NULL;
 }

 LPITEMIDLIST lpiNew = NULL;
 USHORT cb1 = GetPIDLSize(lpi1);
 USHORT cb2 = GetPIDLSize(lpi2);
 if(cb1 != 0)
 {
  cb1 -= 2;
 }
 if(cb2 != 0)
 {
  cb2 -= 2;
 }
 USHORT cb = cb1 - cb2 + 2;
 LPMALLOC lpMalloc = 0;
 SHGetMalloc(&lpMalloc);
 if(!lpMalloc)
 {
  return NULL;
 }
 lpiNew = (LPITEMIDLIST)lpMalloc ->Alloc(cb);
 if(lpiNew)
 {
  ZeroMemory(lpiNew, cb);
  CopyMemory(lpiNew, lpi1, cb - 2);
 }
 
 return lpiNew;
}

// 根据空间对象的全路径PIDL取得对象的父文件夹的IShellFolder及对象本身的项目ID
// lpsfDesktop 桌面的IShellFolder,可以通过SHGetDesktopFolder得到
// lpi 文件对象的全路径PIDL,可通过上面的ParsePidlFromPath由路径名获得
// lpiRet 返回的文件对象自身的项目ID
// 返回值 文件对象所在文件夹的IShellFolder
LPSHELLFOLDER ExtractParentShellFolder(LPSHELLFOLDER lpsfDesktop, LPITEMIDLIST lpi, LPITEMIDLIST &lpiRet)
{
 LPSHELLFOLDER lpsfParent = NULL, lpsfTemp = lpsfDesktop;
 LPITEMIDLIST lpi1 = lpi, lpi2 = lpi;
 LPMALLOC pMalloc = NULL;
 SHGetMalloc(&pMalloc); 
 while(lpi2 && lpsfTemp)
 {
  lpi2 = GetNextItem(lpi1);
  if(GetNextItem(lpi2) == NULL)
  {
   // 只取出文件夹的数据
   lpiRet = SubtractIDList(lpi1, lpi2);
   break;
  }
  if(lpiRet)
  {
   pMalloc->Free(lpiRet);
  }
  lpiRet = SubtractIDList(lpi1, lpi2);
  lpsfTemp->BindToObject(lpiRet, NULL, IID_IShellFolder, (void**)&lpsfParent);
  if(lpsfTemp != lpsfDesktop)
  {
   lpsfTemp->Release();
  }
  lpsfTemp = lpsfParent;
  lpi1 = lpi2;
 }
 if(pMalloc)
 {
  pMalloc->Release();
 }

 return lpsfParent;
}


// 文件预览对话框在文件选中被改变时的通知处理函数
// m_stThumbNail为预览图像的Static控件
void CPreviewFileDialog::OnFileNameChange()
{
 LPITEMIDLIST lpi = ParsePidlFromPath(GetPathName());
 LPITEMIDLIST lpi2 = NULL;
 LPSHELLFOLDER lpsfParent = ExtractParentShellFolder(m_lpsfDeskTop, lpi, lpi2);
 if(lpsfParent && lpi2)
 {
  lpsfParent->GetUIObjectOf(GetSafeHwnd(), 1, (LPCITEMIDLIST*)&lpi2,
   IID_IExtractImage, NULL, (void**)&m_pExtImg);
  if(m_pExtImg)
  {
   HRESULT hRes;
   HBITMAP hBmThumbnail;
   hRes = m_pExtImg->Extract(&hBmThumbnail);
   if(FAILED(hRes))
   {
    TRACE0("IExtractIamge::Extract Failed/n");
   }
   m_stThumbNail.SetBitmap(hBmThumbnail);
   m_stThumbNail.Invalidate();
   m_pExtImg->Release();
  }
  LPMALLOC pMalloc = NULL;
  SHGetMalloc(&pMalloc);
  if(pMalloc)
  {
   pMalloc->Free(lpi2);
   pMalloc->Release();
  }
  lpsfParent->Release();
 }
}