这两天想做一下windows系统下图标的修改,让程序有更新的时候能够更新图标的外观,达到提醒的作用,360,QQ经常采用这种方式进行更新的提示,也有采用弹框的方式来提示,用新版QVOD的同事可能见到过类似的。废话不说,我的思路是:
(1)检测程序更新
(2)检测windows桌面图标中是够含有需要的,如果没有则需要创建,如果有则准备更新图标
(3)根据跟新的内容,程序自己创建新的图像,修改快捷方式的图标
这种方法需要开机之后,存在一个系统进程来检测这种更新,360就是这样干的~~还有一个弊端是程序需要检测桌面快捷方式的信息来判断是不是自己想去修改的,OK ,这个问题其实可以通过LNK文件的属性,应用信息,名字等问题来解决。 下面是实现过程
1 ICON文件的操作
ICON文件的格式和BMP文件的格式的类似,不过增加了层数的东西,一个ICON文件中可能有几份数据,不同的分辨率,ICON文件的格式主要定义如下:
//ICON文件存储格式 typedef struct { byte bwidth; // width, in pixels, of the image byte bheight; // height, in pixels, of the image byte bcolorcount; // number of colors in image (0 if >=8bpp) byte breserved; // reserved ( must be 0) WORD wplanes; // color planes WORD wbitcount; // bits per pixel DWORD dwbytesinres; // how many bytes in this resource? DWORD dwimageoffset; // where in the file is this image? } ICONDIRENTRY, *LPICONDIRENTRY; //ICON文件目录 typedef struct { WORD idReserved; // Reserved WORD idType; // resource type (1 for icons) WORD idCount; // how many images? ICONDIRENTRY idEntries[1]; // the entries for each image } ICONDIR, *LPICONDIR; //ICON文件数据存储格式 typedef struct { BITMAPINFOHEADER icheader; // dib header RGBQUAD iccolors[1]; // color table byte icxor[1]; // dib bits for xor mask byte icand[1]; // dib bits for and mask } iconimage, *lpiconimage;
(1)格式具体的讲解可以参考一下两篇文章:ICON格式解析,ICON文件格式分析VB实现,中间涉及到BMP文件BITMAPINFOHEADER的格式解析,
可以到MSDN上或者百度上查找一下 很容易理解,ICON填充这个格式的时候和BMP文件不同,biHeight参数是ICONDIRENTRY结构中bheight的2倍,
原因是图像数据中包含了icxor和icand部分。ICon文件中比较纠结的还有ICONDIRENTRY结构中dwbytesinres和dwimageoffset这两个变量的
的计算,下文会给出具体的计算方式代码,需要自己理解一下。
(2)ICON文件的操作主要涉及读和写两部分,图标的修改则直接操作内存数据即可,注意的是里面如果内部包含多份数据,需要全部进行修改,
本文主要处理ICON文件中只有一份数据的情况。ICON文件读写可以使用C语言的FILE类型操作,C++的fstream或者ifstream以及ofstream类
以及 文件句柄CreateFile进行操作,这里使用C++的方式进行操作
(3)结构中变长变量的理解,ICONDIR结构中采用了变长数组结构,如ICONDIRENTRY idEntries[1],刚开始还有点不熟悉,这种结构默认
结构中有一个ICONDIRENTRY 变量,但是如果有多个的话可以通过重新分配内存大小来实现,具体方式参考代码中读数据部分。很囧的是,
我现在才知道C99已经支持变长数组了。
(4)数据操作代码(支持多分数据)
程序里面对ICON读入的数据重新进行了整理,结构如下
//图像数据存储 struct stImageData { BITMAPINFOHEADER icheader; // ptr to header byte* icxor ; byte* icand ; stImageData() { icand = NULL ; icxor = NULL ; } }; //ICON图像整体组成 struct stIconData { int num ; stImageData data[1]; stIconData() { num = 0 ; } }; struct stIcon { ICONDIR *dir ; stIconData *icon ; stIcon() { dir = NULL ; icon = NULL ; } };
读数据:
bool CIconOperate::ReadIcon(const string _str) { fstream fin ; fin.open(_str.c_str(),ios_base::binary | ios_base::in); if (!fin.is_open()) { return false ; } ICONDIR dirTemp ; //reserved fin.read((char*)(&dirTemp.idReserved),sizeof(WORD)); //type fin.read((char*)(&dirTemp.idType),sizeof(WORD)); //num fin.read((char*)(&dirTemp.idCount),sizeof(WORD)); //image data header byte *pBuffer = new byte[3 * sizeof(WORD) + dirTemp.idCount * sizeof(ICONDIRENTRY)] ; // 变长数据操作 m_IconDir = (ICONDIR *)(pBuffer); m_IconDir->idCount = dirTemp.idCount ; m_IconDir->idReserved = dirTemp.idReserved ; m_IconDir->idType = dirTemp.idType ; for (int i = 0 ; i < dirTemp.idCount ; ++i) { fin.read((char*)(&m_IconDir->idEntries[i]),sizeof(ICONDIRENTRY)); } //img data byte *pBufferData = new byte[sizeof(int) + dirTemp.idCount * sizeof(stImageData)]; m_IconData = (stIconData*)pBufferData ; m_IconData->num = m_IconDir->idCount ; for (int i = 0 ; i < m_IconDir->idCount ; ++ i) { //SEEK fin.seekg(m_IconDir->idEntries[i].dwimageoffset,ios_base::beg) ; //READ BITMAPINFOHEADER fin.read((char*)(&m_IconData->data[i].icheader),sizeof(BITMAPINFOHEADER)); //READ XOR DATA int xornum = WIDTHBYTES(m_IconDir->idEntries[i].bwidth,m_IconDir->idEntries[i].wbitcount) * m_IconDir->idEntries[i].bheight; int andnum = WIDTHBYTES(m_IconDir->idEntries[i].bwidth,1) * m_IconDir->idEntries[i].bheight; m_IconData->data[i].icxor = new byte[xornum]; fin.read((char*)(m_IconData->data[i].icxor),xornum); //READ AND DATA m_IconData->data[i].icand = new byte[andnum]; fin.read((char*)(m_IconData->data[i].icand),andnum); } fin.close(); return true ; }
写数据
bool CIconOperate::SaveIcon(const string _str) { if (m_IconDir == NULL || m_IconData == NULL) return false ; //int width = 64 ; //int heigth = 64 ; //int ppx = 32 ; fstream fout; fout.open(_str.c_str(),ios_base::out | ios_base::binary); //reserved WORD wData = 0 ; fout.write((char*)(&wData),sizeof(WORD)); //type wData = 1 ; fout.write((char*)(&wData),sizeof(WORD)); //num wData = m_IconDir->idCount ; fout.write((char*)(&wData),sizeof(WORD)); //write ICONDIRENTRY数据 for (int i = 0 ; i < m_IconDir->idCount ; ++i) { //ICONDIRENTRY结构 ICONDIRENTRY iconData ; iconData.bwidth = m_IconDir->idEntries[i].bwidth ; iconData.bheight = m_IconDir->idEntries[i].bheight ; iconData.bcolorcount = 0; iconData.breserved = 0 ; iconData.wplanes = 1 ; iconData.wbitcount = m_IconDir->idEntries[i].wbitcount ; iconData.dwbytesinres = sizeof(BITMAPINFOHEADER) + iconData.bheight * WIDTHBYTES(iconData.bwidth,iconData.wbitcount) + iconData.bheight * WIDTHBYTES(iconData.bwidth,1); iconData.dwimageoffset = CalculateImageOffset(i) ; fout.write((char*)(&iconData),sizeof(ICONDIRENTRY)); } for (int i = 0 ; i < m_IconDir->idCount ; ++i) { //BITMAPINFOHEADER结构 BITMAPINFOHEADER tBitHeader; tBitHeader.biSize = sizeof(BITMAPINFOHEADER); tBitHeader.biWidth = m_IconDir->idEntries[i].bwidth ; tBitHeader.biHeight = m_IconDir->idEntries[i].bheight*2 ; tBitHeader.biPlanes = 1 ; tBitHeader.biBitCount = m_IconDir->idEntries[i].wbitcount ; tBitHeader.biCompression = 0 ; tBitHeader.biSizeImage = 0; tBitHeader.biXPelsPerMeter = 0 ; tBitHeader.biYPelsPerMeter = 0 ; tBitHeader.biClrUsed = 0 ; tBitHeader.biClrImportant = 0 ; fout.write((char*)(&tBitHeader),sizeof(BITMAPINFOHEADER)); fout.write((char*)m_IconData->data[i].icxor,tBitHeader.biHeight/2 * WIDTHBYTES(tBitHeader.biWidth,tBitHeader.biBitCount)); fout.write((char*)m_IconData->data[i].icand,tBitHeader.biHeight/2 * WIDTHBYTES(tBitHeader.biWidth,1)); } fout.close(); return true ; }
2快捷方式搜索
以前没注意到这个问题,原来快捷方式也是有具体格式的,格式后缀为.LNK,格式的具体解析可以参考文档:lnk文件解析,windows快捷方式
在程序中windows系统提供了IshellLink类进行具体的操作,OK,文件搜索的windows系统通过IShellForder来继续操作,具体搜索过程可以
参考文章:Windows Shell提取媒体信息,vC++实现遍历桌面和快速启动里的所有快捷方式, ok,这些理解之后,你就可以遍历桌面的所有快捷
方式了。还需要理解一下window的注册表操作,桌面的位置是通过注册表获得的。
vector<string>* CFileSearch::SearchLinkByName(const CString& _str) { m_FindList.clear(); m_strFind = _str ; GetPath(m_strDeskTopPath,m_strQuickLanchPath); if(GetDesktopIShellFolder()) { GetIEunmIDList(m_pIShellFolder,FALSE,m_mode); } fprintf(fp," "); for (int i = 0 ; i < m_FindList.size() ; i++) { fprintf(fp,"%s ",m_FindList[i].c_str()); } return &m_FindList ; } //获取桌面文件夹和快速启动文件夹的路径 int CFileSearch::GetPath(char *DeskTop, char* AppData) { CRegKey m_reg; if(ERROR_SUCCESS==m_reg.Open(HKEY_CURRENT_USER,"Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders",KEY_READ)) { ULONG m_ulsize1=1000; ULONG m_ulsize2=1000; m_reg.QueryStringValue("Desktop",DeskTop,&m_ulsize1); m_reg.QueryStringValue("AppData",AppData,&m_ulsize2); lstrcat(AppData,"\Microsoft\Internet Explorer\Quick Launch"); } return 0; } //获取桌面文件夹的IShellFolder接口指针 BOOL CFileSearch::GetDesktopIShellFolder() { m_pIShellFolder=GetIShellFolderByPath(m_strDeskTopPath); //m_pAppData=GetIShellFolderByPath(m_strQuickLanchPath); return TRUE; } //获取桌面文件夹的IEnumIDList接口 //isQuickLanch---是否搜索第二层文件夹 //isRecur---是否是文件夹 BOOL CFileSearch::GetIEunmIDList(IShellFolder * pShellFolder,BOOL isRecur,BOOL isQuickLanch) { IEnumIDList *pIEnumFile = NULL ; IEnumIDList *pIEnumFolder = NULL ; if(!pShellFolder) return FALSE; HRESULT hr=pShellFolder->EnumObjects(0,SHCONTF_NONFOLDERS,&pIEnumFile); //不是文件夹 if(FAILED(hr)) { return FALSE; } if(!isRecur) { m_pFirstLayerFile = pIEnumFile; } EnumAllItems(pIEnumFile,FALSE,isQuickLanch); if(!isQuickLanch) { HRESULT hr=pShellFolder->EnumObjects(0,SHCONTF_FOLDERS,&pIEnumFolder); //文件夹 if(FAILED(hr)) { return FALSE; } if(!isRecur) { m_pFirstLayerFolder = pIEnumFolder; } EnumAllItems(pIEnumFolder,TRUE,isQuickLanch); } return TRUE; } BOOL CFileSearch::EnumAllItems(IEnumIDList *m_pEnum,BOOL isFolder,BOOL isQuickLanch) { LPITEMIDLIST m_pItem=NULL; ULONG m_ulwork= 0; while(m_pEnum->Next(1,&m_pItem,&m_ulwork)==S_OK) { //如果是第一层,重置路径 if(!isQuickLanch) { if((m_pFirstLayerFolder==m_pEnum) && (isFolder)) { lstrcpy(m_strParentPath,m_strDeskTopPath); } if((m_pFirstLayerFile==m_pEnum) && (!isFolder)) { lstrcpy(m_strParentPath,m_strDeskTopPath); } } else { // if((m_pFirstLayerFile==m_pEnum) && (!isFolder)) // { // lstrcpy(m_strParentPath,m_strQuickLanchPath); // } if((m_pFirstLayerFolder==m_pEnum) && (isFolder)) { lstrcpy(m_strParentPath,m_strDeskTopPath); } if((m_pFirstLayerFile==m_pEnum) && (!isFolder)) { lstrcpy(m_strParentPath,m_strDeskTopPath); } } WIN32_FIND_DATA ffd; SHGetDataFromIDList(m_pIShellFolder,m_pItem,SHGDFIL_FINDDATA,&ffd,sizeof(WIN32_FIND_DATA)); if(!isFolder) { CString m_strTempPath=m_strParentPath; m_strTempPath+="\"; m_strTempPath += ffd.cFileName; CString m_strCmp=".lnk"; fprintf(fp,"%s ",m_strTempPath.GetBuffer()); m_strTempPath.MakeUpper(); m_strCmp.MakeUpper(); if(m_strTempPath.Right(4)==m_strCmp) //判断是否是链接文件 { if (ReadShortCut(m_strTempPath.GetBuffer())) { string str = m_strTempPath.GetBuffer(); m_FindList.push_back(str); } m_strTempPath.ReleaseBuffer(); } } else { lstrcat(m_strParentPath,"\"); lstrcat(m_strParentPath,ffd.cFileName); IShellFolder *m_pITemp=GetIShellFolderByPath(m_strParentPath); GetIEunmIDList(m_pITemp,TRUE,isQuickLanch); } } return TRUE; } //获取指定目录的IShellFolder接口 IShellFolder *CFileSearch::GetIShellFolderByPath(LPTSTR path) { IShellFolder *m_ShellFolderTopMost=NULL; HRESULT hr=SHGetDesktopFolder(&m_ShellFolderTopMost); if(FAILED(hr)) { return NULL; } IShellFolder *m_pFolder; LPITEMIDLIST pidlWorkDir=NULL; OLECHAR strOleFilePath[MAX_PATH]; MultiByteToWideChar( CP_ACP, MB_PRECOMPOSED, path, -1, strOleFilePath, MAX_PATH ); hr = m_ShellFolderTopMost->ParseDisplayName(NULL,NULL,strOleFilePath,NULL,&pidlWorkDir,NULL); if(FAILED(hr)) { return NULL; } hr=m_ShellFolderTopMost->BindToObject(pidlWorkDir,NULL,IID_IShellFolder,(LPVOID*)&m_pFolder); if(m_ShellFolderTopMost)m_ShellFolderTopMost->Release(); return m_pFolder; } //读取快捷方式的信息 BOOL CFileSearch::ReadShortCut(LPTSTR linkName) { ::CoInitialize(NULL); IShellLink *m_pIShellLink=NULL; IPersistFile *m_pIPersistFile=NULL; HRESULT hr=::CoCreateInstance(CLSID_ShellLink,NULL,CLSCTX_INPROC_SERVER,IID_IShellLink,(LPVOID*)&m_pIShellLink); if(hr==S_OK) { hr=m_pIShellLink->QueryInterface(IID_IPersistFile,(void **)&m_pIPersistFile); if(hr==S_OK) { USES_CONVERSION; m_pIPersistFile->Load(T2COLE(linkName),STGM_READWRITE); char m_strPath[MAX_PATH]={0}; m_pIShellLink->GetPath(m_strPath,MAX_PATH,NULL,SLGP_UNCPRIORITY); CString temp = m_strPath; temp.MakeUpper(); m_strFind.MakeUpper(); if (strstr(temp.GetBuffer(),m_strFind.GetBuffer())) //判断应用程序名 { //AfxMessageBox(temp); if(m_pIShellLink) m_pIShellLink->Release(); if(m_pIPersistFile) m_pIPersistFile->Release(); ::CoUninitialize(); return TRUE; } } } if(m_pIShellLink) m_pIShellLink->Release(); if(m_pIPersistFile) m_pIPersistFile->Release(); ::CoUninitialize(); return FALSE;
3快捷方式图标的更新
搜索到快捷方式之后,IshellLink类提供了更换图标、设置描述、设置链接的应用程序等信息,如果要参考文档可以参考一下:
快捷方式操作指南,在应用程序中创建快捷方式
bool CModifyLinkICon::ChangeLinkIcon(const string& strLnkName,const string& strIconPath) { if (strLnkName.empty() || strIconPath.empty()) { return false; } HRESULT hres; IShellLink *psl = NULL; IPersistFile *pPf = NULL; int id; LPITEMIDLIST pidl; bool bRet = false; do { hres = CoInitialize(NULL); if (FAILED(hres)) { break; } hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (LPVOID*)&psl); if (FAILED(hres)) { break; } hres = psl->QueryInterface(IID_IPersistFile, (LPVOID*)&pPf); if (FAILED(hres)) { break; } wchar_t wsz[256]; MultiByteToWideChar(CP_ACP, 0, strLnkName.c_str(), -1, wsz, MAX_PATH); hres = pPf->Load(wsz, STGM_READWRITE); if (FAILED(hres)) { break; } hres = psl->SetIconLocation(strIconPath.c_str(), 0); if (FAILED(hres)) { break; } pPf->Save(wsz, TRUE); if (FAILED(hres)) { break; } bRet = true; } while (0); if (pPf != NULL) { pPf->Release(); } if (psl != NULL) { psl->Release(); } CoUninitialize(); AfxMessageBox("替换完成"); return bRet; }
4结论
其实这些操作估计很早之前就有,网上的信息页很多,我就算做一下汇总吧,最后给出我测试用的界面,很简答,可以实现快捷的搜索,快捷图标的更换,图像的读取,写入以及简单的修改。