网站推广链接,wordpress社群模板,下载app的软件并安装,网站问题有哪些内容第六章 快捷方式的最短路径 Windows Shell允许存储任何对象的引用到系统范围内的任何地点。例如#xff0c;当你从一个文件夹拖拽可执行程序到另一个文件夹时#xff0c;鼠标自动改变形状给出除拷贝和移动文件之外的第三种选择。 除非你确定#xff0c;否则可执行文件是不能… 第六章 快捷方式的最短路径 Windows Shell允许存储任何对象的引用到系统范围内的任何地点。例如当你从一个文件夹拖拽可执行程序到另一个文件夹时鼠标自动改变形状给出除拷贝和移动文件之外的第三种选择。 除非你确定否则可执行文件是不能拷贝或移动的相反每次你做这样的操作时实际拷贝或移动的是对它的物理位置的一个引用实际所建立的不是文件的拷贝而是它的初始位置的连接。 所有这些都是快捷方式的示例这种东西在老版本的Windows中就已经存在—例如程序管理器的图标就是早期的快捷方式。然而不要弄混了它们不是相同的主要差别在于快捷方式具有可以指向文件对象这个更普遍的机理不仅是可执行文件也不仅仅是文件。在Windows9x和WindowsNT的Shell中快捷方式是无处不在的。可以在任何文件夹中找到它们而最多的是在系统的特殊文件夹中。如果你希望应用程序具有印象深刻的功能比如添加项到‘Favorites’或‘发送到’文件夹中甚至是到‘开始’菜单中则建立快捷方式是一个可行的方法。快捷方式是Shell的重要组成部分也是我们在这一章里要彻底讨论的内容。在这一章中我们打算讨论 快捷方式确切地是什么 系统怎样存储和装入快捷方式 怎样建立和删除快捷方式 可以编码处理快捷方式的函数举例 我们将要给出的例子假设你对Shell编程外围知识有一定了解但是例子将进一步清晰地说明快捷方式的灵活性。例如在这一章中我们将使用热键控件和拖拽功能作为示例应用的内建功能。 什么是快捷方式 快捷方式表示一个特定文件对象的连接并是一个具有.lnk扩展名的微小二进制文件。这里的‘微小’意思是快捷方式文件的尺寸很少达到1KB。并不是所有的快捷方式都确切地有相同的尺寸但是它们却拥有固定的属性集目标文件对象描述热键图标等。我们将简短地检测这些内容。 快捷方式遍及整个Windows Shell可以作为Shell提供的服务。从软件的观点分析快捷方式是通过暴露IShellLink接口的COM服务器实现的其接口标识是CLSID_ShellLink。通过这个接口你可以设置快捷方式的各种属性和调用接口方法在磁盘上保存或装入它们。 快捷方式文件类型 正如前面所说的快捷方式是一个文件但是它是一种Shell以特殊方法处理的文件。Shell当然知道一个类行为‘快捷方式’的文件是一个对某件东西的引用所以当你双击它的时候(或单击它—依赖于活动桌面的设置)返回一个被指向的对象而不是你点击的文件。 建立快捷方式 尽管快捷方式通常都与可执行文件相关联但这并不是规定—你可以建立目录或非可执行文件的快捷方式。就编程而言绝对没有不同。同样也能建立非文件系统对象的快捷方式(如打印机)。而此时就有一个小的差别了你应该使用不同的方法做这个工作。 建立一个新的.lnk文件有两个选择头一个依赖于Shell DDE接口它是直接从旧的程序管理器继承过来的。我们不考虑这种情况详情请参看Shell DDE相关资料(Internet 客户端SDK)和MSDN库。如果你使用DDE编程而不是下面将要看到的技术这些资料可以使你知道Windows3.x以来都发生了哪些改变以及DDE接口中相对较新的特征。 使用IShellLink接口 第二个建立快捷方式的方法(也是推荐的方法)是使用IShellLink COM接口这是一个十分容易的过程相关的步骤是 建立适当的COM服务器 获得IShellLink接口指针 通过IShellLink的方法设置属性 获得IPersistFile接口指针 使用IPersistFile的方法保存一个文件的快捷方式 建立服务器就是调用CoCreateInstance()一定要保证在处理之前已经适当地初始化了COM库(使用CoInitialize()) [cpp] view plaincopyprint? IShellLink* pShellLink NULL; HRESULT hr CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, reinterpret_castLPVOID*(pShellLink)); if(FAILED(hr)) return hr; CLSID在shlobj.h头文件中定义上面的调用返回指向IShellLink接口的指针这是处理快捷方式的关键。下面表中列出接口的所有方法和主要的描述我们将在随后的代码例程中指出某些可能存在的缺陷。 方法 描述 GetArguments() SetArguments() 返回/设置命令行变量 GetDescription() SetDescription() 返回/设置描述串 GetHotkey() SetHotkey() 返回/设置快捷方式的热键 GetIconLocation() SetIconLocation() 返回/设置路径和图标索引 GetIDList() SetIDList() 返回/设置链接对象的PIDL。如果操作非文件系统对象应该使用这两个方法代替GetPath()和SetPath() GetPath() SetPath() 返回/设置链接对象的路径和文件名 GetShowCmd() SetShowCmd() 返回/设置链接对象的SW_XXX标志 GetWorkingDirectory() SetWorkingDirectory() 返回/设置工作目录 SetRelativePath() 设置连接对象的相对路径 Resolve() 恢复有快捷方式指向的文件对象 一旦获得了IShellLink接口的指针你就可以开始通过设置目标对象(文件目录或非文件对象的PIDL)和选项属性列表构造快捷方式了。你也可以设置描述文字快速访问热键特殊的图标工作目录命令行参数以及表示窗口(如果有)建立行为的值。下面是典型的代码段 [cpp] view plaincopyprint? pShellLink-SetPath(pszTarget); pShellLink-SetDescription(pszDesc); pShellLink-SetHotkey(wHotKey); pShellLink-SetIconLocation(pszIconPath, wIconIndex); 此时对象仅仅存在于内存之中为了使它永久存在需要把它存进文件。就是为了这个原因我们使用的COM服务器(标志为CLSID_ShellLink)才实现了IPersistFile接口。这是一个包含读写磁盘方法的接口因此可以为调用者提供通常编程接口意义上的文件装入与保存服务。 [cpp] view plaincopyprint? IPersistFile* pPF; pShellLink-QueryInterface(IID_IPersistFile, reinterpret_castLPVOID*(pPF)); MultiByteToWideChar(CP_ACP, 0, szLnkFile, -1, wszLnkFile, MAX_PATH); pPF-Save(wszLnkFile, TRUE); IPersistFile接口的两个最重要的方法Load()和Save()二者都要求Unicode串因而需要转换包含文件名的缓冲为宽字符串格式。 快捷方式的全程函数 我们已经给出了形成新Shell辅助函数的信息用于建立快捷方式—显然Windows Shell API并不提供简单而直接的函数来建立(或处理)快捷方式。另一个想法是我们打算给函数取名为SHCreateShortcutEx()。 事实上尽管Win32API没有但是WindowsCE SDK中却包含了这样一个函数SHCreateShortcut()具有下面的原型 [cpp] view plaincopyprint? BOOL SHCreateShortcut(LPTSTR szShortcut, LPTSTR szTarget); 我们的函数接受.lnk文件作为目标名和一个包含这个快捷方式所有属性的结构 [cpp] view plaincopyprint? struct SHORTCUTSTRUCT { LPTSTR pszTarget; LPTSTR pszDesc; WORD wHotKey; LPTSTR pszIconPath; WORD wIconIndex; }; typedef SHORTCUTSTRUCT* LPSHORTCUTSTRUCT; 下面是这个函数的源代码这个函数我们将在后面的示例程序中使用 [cpp] view plaincopyprint? HRESULT SHCreateShortcutEx(LPCTSTR szLnkFile, LPSHORTCUTSTRUCT lpss) { WCHAR wszLnkFile[MAX_PATH] {0}; IShellLink* pShellLink NULL; IPersistFile* pPF NULL; // 验证SHORTCUTSTRUCT指针 if(lpss NULL) return E_FAIL; // 建立COM服务器假设CoInitialize()已经被调用 HRESULT hr CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, reinterpret_castLPVOID*(pShellLink)); if(FAILED(hr)) return hr; //设置属性 pShellLink-SetPath(lpss-pszTarget); pShellLink-SetDescription(lpss-pszDesc); pShellLink-SetHotkey(lpss-wHotKey); pShellLink-SetIconLocation(lpss-pszIconPath, lpss-wIconIndex); //取得IPersistFile接口 hr pShellLink-QueryInterface( IID_IPersistFile, reinterpret_castLPVOID*(pPF)); if(FAILED(hr)) { pShellLink-Release(); return hr; } // 保存LNK(Unicode名) MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, szLnkFile, -1, wszLnkFile, MAX_PATH); hr pPF-Save(wszLnkFile, TRUE); // 清理 pPF-Release(); pShellLink-Release(); return hr; } Shell脚本对象 Shell脚本对象提出了一种操作快捷方式更好的方法。这在IE4.0中引进并且已经成为Windows98 的标准部件。在坚固的外壳下他作为自动服务器给出了建立和解决快捷方式的编程接口(它们还做许多其它有趣的事情…) 最有趣的是这些部件都可以用于桌面应用HTML页面和整个Windows脚本环境。 在第十二章中我们将详细讨论它们。 快捷方式正确的命名 在Shell的4.71版本以后一个称之为SHGetNewLinkInfo()的新函数对程序员是可用的。然而与你所希望的不同这个函数不能建立快捷方式。相反它的用途在于为快捷方式安排一个正确的名字 [cpp] view plaincopyprint? BOOL SHGetNewLinkInfo(LPCTSTR pszLinkTo, LPCTSTR pszDir, LPTSTR pszName, BOOL* pfMustCopy, UINT uFlags); 这个函数接受路径名的指针或者目标对象的PIDL这个参数存储在pszLinkTo之中。uFlags值指明它是PIDL还是路径名。目标文件夹是pszDir。 这个例程将给出正在建立的快捷方式文件的名字。这个名字由pszName参量返回并假设其缓冲长度为MAX_PATH字符数。当你对已经存在的快捷方式建立快捷方式时Shell并不建立新的连接而是简单地拷贝和修改这个目标。pfMustCopy就用于这个目的它返回一个布尔值来表示Shell是建立了一个快捷方式文件还是处理了一个拷贝TRUE表示pszLinkTo是一个已存在的快捷方式此时Shell只拷贝和适当地修改它FALSE则是建立一个全新的快捷方式。最后的可用标志是 标志 描述 SHGNLI_PIDL 如果设置pszLinkTo变量将作为PIDL而不是串来考虑 SHGNLI_NOUNIQUE 如果设置Shell将首先确定快捷方式的名字而后检查可能的冲突如果名字与同文件夹中的另一个发生冲突就重复操作直到找出唯一的名字为止。 SHGNLI_PREFIXNAME 如果设置名字将总是有一个‘快捷方式到’的前缀 事实上SHGetNewLinkInfo()函数努力为快捷方式提供与给定目标一致的名字。例如对于指向DOS可执行文件将给出.pif扩展名否则将给出.lnk扩展名。这个函数所执行的另一个检查是关于目标驱动器是否支持长文件名。如果不支持则函数返回8.3格式的名字。 删除快捷方式 删除快捷方式与删除文件一样容易。更重要的是不必考虑它所指向文件的命运因为你仅仅删除了它的引用。被指向的目标完全不受影响。 解析快捷方式 建立快捷方式仅仅完成了工作的一半很快你就会被读出快捷方式文件的内容弄糊涂。解析快捷方式并不是不同于读文件但是这个操作一般称之为‘解析’而不是‘读’。有理由说明它们在概念上的差异快捷方式指向一个文件对象但是这只是一个连接—不是嵌入的。在建立快捷方式的时候假定对象是存在的但是在读它的时候并没有这个假设。当需要访问被引用对象时没有东西来保证它不被删除移动或重命名。 读一个快捷方式简单地说明你试图访问这个.lnk文件指定的对象。解析快捷方式则说明系统将试图了解被引用对象已经移动到了什么地方或它是怎样被重命名的。 探测器怎样解析快捷方式 我们说解析快捷方式是从读开始的。然而如果探测器在.lnk文件指定的位置找不到有效的文件对象则它将在所有驱动器和磁盘目录上执行递归搜索直到找到具有相同尺寸建立日期以及与快捷方式指向的文件一样属性的文件为止。如果搜索失败探测器将显示如下对话框 这个对话框可以通过适当设置IShellLink::Resolve()的标志加以抑制。当然如果你已经删除了引用对象探测器也就不可能找到它们即使它们仍然在‘回收站’中也不能。 解析快捷方式的函数 Shell API 也缺少解析快捷方式的函数所以还是需要我们自己写。相关的步骤是 建立必要的COM服务器 取得IPersistFile接口指针 使用IPersistFile的方法从.lnk文件装入快捷方式 取得IShellLink接口指针 解析这个快捷方式 整个操作的核心是Resolve()。语法如下 [cpp] view plaincopyprint? HRESULT IShellLink::Resolve(HWND hwnd, DWORD fFlags); 头一个参数是一个父窗口的Handle函数利用它来显示任何需要显示的对话框。另一个是dwFlags变量它可以是下列值得组合 标志 描述 SLR_NO_UI 函数不显示任何对话框即使查找指向文件失败。此时函数在默认的3秒钟之后返回。超时时间可以通过这个变量的高位字客户化地指定所期望的毫秒数。 SLR_ANY_MATCH 试着解析这个连接在失败时显示对话框 SLR_UPDATE 如果设置这个标志并且引用的对象已经被移动或重命名则快捷方式被更新到指向新的位置这个行为不是默认的。 注意更新快捷方式使其指向新位置上的文件对象(如果有)的行为不是自动的必须通过传递SLR_UPDATE标志到IShellLink::Resolve()函数显式地请求。下面是SHResolveShortcut()函数的源代码与它的姊妹例程SHCreateShortcutEx()一样它在我们说明快捷方式编程的示例程序中被广泛地使用。 [cpp] view plaincopyprint? HRESULT SHResolveShortcut(LPCTSTR szLnkFile, LPSHORTCUTSTRUCT lpss) { WCHAR wszLnkFile[MAX_PATH] {0}; IShellLink* pShellLink NULL; IPersistFile* pPF NULL; // 建立合适的COM服务器 HRESULT hr CoCreateInstance(CLSID_ShellLink, NULL,CLSCTX_INPROC_SERVER, IID_IShellLink,reinterpret_castLPVOID*(pShellLink)); if(FAILED(hr)) return hr; // 取得装入.LNK 文件的IPersistFile接口 hr pShellLink-QueryInterface(IID_IPersistFile, reinterpret_castLPVOID*(pPF)); if(FAILED(hr)) { pShellLink-Release(); return hr; } // 装入快捷方式(Unicode 名) MultiByteToWideChar(CP_ACP, 0, szLnkFile, -1, wszLnkFile, MAX_PATH); hr pPF-Load(wszLnkFile, STGM_READ); if(FAILED(hr)) { pPF-Release(); pShellLink-Release(); return hr; } // 解析连接 hr pShellLink-Resolve(NULL, SLR_ANY_MATCH); if(FAILED(hr)) { pPF-Release(); pShellLink-Release(); return hr; } // 抽取信息充填到lpss if(lpss ! NULL) { TCHAR szPath[MAX_PATH] {0}; TCHAR szDesc[MAX_PATH] {0}; TCHAR szIcon[MAX_PATH] {0}; WORD w 0; WORD wIcon 0; WIN32_FIND_DATA wfd; pShellLink-GetPath(szPath, MAX_PATH, wfd, SLGP_SHORTPATH); pShellLink-GetDescription(szDesc, MAX_PATH); pShellLink-GetHotkey(w); pShellLink-GetIconLocation(szIcon, MAX_PATH, reinterpret_castint*(wIcon)); lpss-pszTarget szPath; lpss-pszDesc szDesc; lpss-pszIconPath szIcon; lpss-wHotKey w; lpss-wIconIndex wIcon; } pPF-Release(); pShellLink-Release(); return hr; } 在装入文件时我们使用了IPersistFile接口的Load()方法它有两个变量Unicode格式的.Lnk文件名和表示要打开文件的访问模式的参数。 快捷方式与特殊文件夹 在绝大多数情况下如果需要编程建立快捷方式你都需要在一个特殊文件夹中建立它。然而这并不复杂—仅仅是要指定这个文件夹的正确路径而已。在下一节将要讨论的程序中就允许你在很多这种通常的特殊文件夹‘我的文档’‘桌面’‘开始菜单’‘程序’‘发送到’和‘Favorites’中建立快捷方式。考虑第五章中的SHGetSpecialFolderPath()函数它正好能发现非虚拟文件夹的路径。 示例程序快捷方式管理 下图所看到的应用是一个建立和解析快捷方式的控制板管理器。它的对话框窗口分成两个部分上面部分是解析快捷方式下面的则是建立新的快捷方式。 这个用户界面使你能够选择打开.Lnk文件并且可以拉动目标—即支持拖拽快捷方式和解析快捷方式。在这个程序中解析的每一个快捷方式都将在观察中列出报告。这里开发的例子将仅仅显示目标描述和热键信息要进一步增强它的功能对于你不应该是太大的问题。 选择一个快捷方式 头一个必须考虑的问题是安排一个‘打开’对话框来选择要解析的快捷方式。麻烦是在默认情况下‘打开’对话框不能处理快捷方式因此没有任何.lnk文件的名字被返回。为了在这种环境下工作你必须在GetOpenFileName()函数中指定OFN_NODEREFERENCELINKS标志。就象下面显示的处理器函数那样它在应用对话框上安排两个浏览按钮 [cpp] view plaincopyprint? void OnBrowse(HWND hDlg, WPARAM wID) { TCHAR szFile[MAX_PATH] {0}; OPENFILENAME ofn; ZeroMemory(ofn, sizeof(OPENFILENAME)); ofn.lStructSize sizeof(OPENFILENAME); if(wID IDC_SHORTCUT) { ofn.lpstrFilter __TEXT(Shortcuts/0*.lnk/0); ofn.Flags OFN_NODEREFERENCELINKS; } else ofn.lpstrFilter __TEXT(All files/0*.*/0); ofn.nMaxFile MAX_PATH; ofn.lpstrInitialDir __TEXT(c://); ofn.lpstrFile szFile; if(!GetOpenFileName(ofn)) return; else SetDlgItemText(hDlg, wID, ofn.lpstrFile); return; } 使用这个技术如果你双击一个.lnk文件探测器将停在那儿并返回一个指向文件的名而不是进入引用的文件。 Shell拖拽 我承认即使我们是对Shell进行编程拖拽也不是一个有重要关系的科目但是在我们看到它操作的过程之后这就显得有价值了。VC资源编辑器就有拉动目标的特征(通过打开WS_EX_ACCEPTFILES位)。当然它也能使你来规划怎样或什么时候可以处理拉动事件。我们想要限制列表观察的拖拽操作但是如果你指派了这个特征则我们所要面对的是必须子类化这个窗口以便感知相关的拉动事件。反过来我们希望使用较简单的方法整个对话框都将可以拉动但是当它捕捉到消息WM_DROPFILES后应该校验列表观察中所发生的事件否则忽略这个事件。Shell处理拖拽的函数都定义在shellapi.h中有DragQueryPoint(),DragQueryFile()和DragFinish()后面我们还将继续讨论这个课题。 显示结果 这个程序在用户界面上有一个报告风格的列表观察为了较容易的使用它我们建立了两个辅助函数来帮助在观察中增加列和串。记住以后我们还会使用它们。 第一个函数是MakeReportView()它转换列表观察窗口到具有指定列的报告风格的列表观察。函数原型要求传递一个列表观察的Handle一个带有字符串名和列宽度值的数组以及一个列数值。为了使函数尽量简洁我们假定数组中偶位置为名字串奇位置为数字。 数组实际是一个串指针数组—即一个32位值的数组理解了这个假设之后你就可以如下使用数组了 [cpp] view plaincopyprint? LPTSTR psz[] {Target, reinterpret_castTCHAR*(170), Description, reinterpret_castTCHAR*(170), Hotkey, reinterpret_castTCHAR*(100)}; MakeReportView(hwndList, psz, 3); MakeReportView()总是以名字/宽度对方式处理数组元素所以列数总是等于数组尺寸的一半。 [cpp] view plaincopyprint? void MakeReportView(HWND hwndList, LPTSTR* psz, int iNumOfCols) { RECT rc; DWORD dwStyle GetWindowStyle(hwndList); SetWindowLong(hwndList, GWL_STYLE, dwStyle | LVS_REPORT); GetClientRect(hwndList, rc); // 处理元素对数组尺寸假设为 2 * iNumOfCols for(int i 0 ; i 2 * iNumOfCols ; i i 2) { LV_COLUMN lvc; ZeroMemory(lvc, sizeof(LV_COLUMN)); lvc.mask LVCF_TEXT | LVCF_WIDTH; lvc.pszText psz[i]; if(reinterpret_castint(psz[i 1]) 0) lvc.cx rc.right / iNumOfCols; else lvc.cx reinterpret_castint(psz[i 1]); ListView_InsertColumn(hwndList, i, lvc); } return; } MakeReportView()的伴随例程是AddStringToReportView()它添加新行到指定的列表观察。由于底层编程接口的原因充填报告风格列表观察所有列要求分几个步骤。你应该为这个新项的第一列(主列)添加指定的文字然后依次在其他列上设置文字。所有这些步骤都由AddStringToReportView()执行你只需要传递一个包含所有子串的NULL分隔串和在iNumOfCols指出有多少列就可以了。 [cpp] view plaincopyprint? void AddStringToReportView(HWND hwndList, LPTSTR psz, int iNumOfCols) { LV_ITEM lvi; ZeroMemory(lvi, sizeof(LV_ITEM)); lvi.mask LVIF_TEXT; lvi.pszText psz; lvi.cchTextMax lstrlen(psz); lvi.iItem 0; ListView_InsertItem(hwndList, lvi); // 其它列 for(int i 1 ; i iNumOfCols ; i) { psz lstrlen(psz) 1; ListView_SetItemText(hwndList, 0, i, psz); } return; } 在本例中列表观察有三列‘目标’‘描述’和‘热键’。前两列是直接的而第三列使用了以前没用过的通用控件的一个用法。这是需要进一步说明的。 热键通用控制 Windows95引进了一个新的控件可以使你能够图像方式选择一个键的组合(如图) 使用这个控件的方法是敲击按键组合它解释键盘码并转换成相应文字显然在建立快捷方式环境下这个控件有更友善的用户界面。 反过来在解析快捷方式时你所有的全部仅是一个由IShellLink::GetHotkey()返回的数(精确地DWORD)这需要把它转换成一个友善格式的串。 这个字分解成两个字节表示一个热键高字节是修改符(Alt,Ctrl,Shift,或三者的组合)低字节是你所敲击的键码。注意如果你按了A这个码是65 (大写字符)不是97(小写字符)。要使用HotkeyToString()例程你需要相对于某些已知常量检查高字节的位。下面这个函数对此进行了处理 [cpp] view plaincopyprint? void HotkeyToString(WORD wHotKey, LPTSTR pszBuf) { BYTE bKey LOBYTE(wHotKey); BYTE bMod HIBYTE(wHotKey); if(bMod HOTKEYF_CONTROL) lstrcpy(pszBuf, __TEXT(Ctrl)); if(bMod HOTKEYF_SHIFT) if(lstrlen(pszBuf)) lstrcat(pszBuf, __TEXT( Shift)); else lstrcpy(pszBuf, __TEXT(Shift)); if(bMod HOTKEYF_ALT) if(lstrlen(pszBuf)) lstrcat(pszBuf, __TEXT( Alt)); else lstrcpy(pszBuf, __TEXT(Alt)); TCHAR s[2] {0}; wsprintf(s, __TEXT(%c), bKey); if(lstrlen(pszBuf)) { lstrcat(pszBuf, __TEXT( )); lstrcat(pszBuf, s); } else lstrcpy(pszBuf, s); } HotkeyToString()函数接受热键值和一个要充填返回结构的串缓冲。它检查修改器和建立串的第一部分—如CtrlAlt然后通过关联的按键字符完成这个操作—如CtrlAltX。下图中显示了在解析快捷方式时应用的结果 收集建立变量 如果不是有点微妙的话建立快捷方式对话框的这一部分没有什么可值得注意的。打开一个已存在的快捷方式(使用在桌面上的一个是比较好的)试图给它分配一个新的热键此时你将发现热键控件校正你的按键。A将变成CtrlAltA。 正是这个特征而且是个重要特征因为如果你试图编程地分配一个非CtrlAlt…形式的热键这个热键将永远不被识别。稍微考虑一下你就会明白这种行为不是串—CtrlAlt … 与可能的加速器冲突。这也使我明白了在前几个例子中为什么AltA是错误的了。 给出热键规则 指令热键控件自动置换某些错误或无效的按键组合你需要使用按键规则。不管它的名字如何这其实就是简单地发送消息到热键窗口。为了强制使它接受仅仅CtrlAlt前缀的按键你必须 [cpp] view plaincopyprint? SendMessage(hwndHotkey, HKM_SETRULES, HKCOMB_NONE | HKCOMB_S | HKCOMB_A | HKCOMB_C, HOTKEYF_CONTROL | HOTKEYF_ALT); 这个‘规则’可以重新解释为 无效的按键组合总是那些有一个修改符在wParam中列出的按键。 用在lParam中指定的组合键置换每一个无效的按键。 如果不是从空(HKCOMB_NONE)Shift(HKCOMB_S), Alt (HKCOMB_A) 或 Ctrl(HKCOMB_C)开始则忽略它们并用CtrlAlt代替之。下面图像显示了建立快捷方式时的这个过程 源代码 现在看一下这个示例程序的剩余源代码。要正确地编译它必须确保包含shlobj.h, resource.h 和commdlg.h以及链接comdlg32.lib和ole32.lib。另外由于我们使用了COM所以还需要用CoInitialize(NULL)和CoUninitialize()把WinMain()中的DialogBox()调用括起来。 DoCreateShortcut()函数 这个函数在点击‘建立’按钮时被调用。它从控件中收集参数和安排调用SHCreateShortcutEx()函数在combo框中有一些特殊文件夹的名字。 [cpp] view plaincopyprint? void DoCreateShortcut(HWND hDlg) { SHORTCUTSTRUCT ss; ZeroMemory(ss, sizeof(SHORTCUTSTRUCT)); TCHAR szTarget[MAX_PATH] {0}; TCHAR szDesc[MAX_PATH] {0}; // 取得热键 ss.wHotKey static_castWORD(SendDlgItemMessage( hDlg, IDC_HOTKEY, HKM_GETHOTKEY, 0, 0)); // 取得目标和描述 GetDlgItemText(hDlg, IDC_TARGET, szTarget, MAX_PATH); GetDlgItemText(hDlg, IDC_DESCRIPTION, szDesc, MAX_PATH); ss.pszTarget szTarget; ss.pszDesc szDesc; // 确定快捷方式文件名 // 取得目标文件夹和最后的反斜杠 HWND hwndCbo GetDlgItem(hDlg, IDC_SPECIAL); int i ComboBox_GetCurSel(hwndCbo); DWORD nFolder ComboBox_GetItemData(hwndCbo, i); TCHAR szPath[MAX_PATH] {0}; SHGetSpecialFolderPath(hDlg, szPath, nFolder, FALSE); if(szPath[lstrlen(szPath) - 1] ! //) lstrcat(szPath, __TEXT(//)); TCHAR szLnkFile[MAX_PATH] {0}; GetDlgItemText(hDlg, IDC_LNKFILE, szLnkFile, MAX_PATH); lstrcat(szPath, szLnkFile); lstrcat(szPath, __TEXT(.lnk)); // 建立 SHCreateShortcutEx(szPath, ss); // 更新UI SetDlgItemText(hDlg, IDC_SHORTCUT, szPath); return; } DoResolveShortcut()函数 这个函数在响应‘解析’按钮的点击时调用。尽管它也接受一个附加的参数pszFile用于表示要解析的文件。如果这个参数为NULL则函数使用‘快捷方式’编辑框中的内容。这个变量存在的原因是使这个函数更容易解析拖拽到程序窗口上的任何文件。DoResolveShortcut()首先调用我们的函数SHResolveShortcut()解析这个快捷方式然后更新用户界面附加一个新行到报告列表观察中。 [cpp] view plaincopyprint? void DoResolveShortcut(HWND hDlg, LPTSTR pszFile) { TCHAR szLnkFile[MAX_PATH] {0}; if(pszFile NULL) GetDlgItemText(hDlg, IDC_SHORTCUT, szLnkFile, MAX_PATH); else lstrcpy(szLnkFile, pszFile); // 解析快捷方式 SHORTCUTSTRUCT ss; HRESULT hr SHResolveShortcut(szLnkFile, ss); if(FAILED(hr)) return; // // 更新UI // 建立列表观察串 TCHAR pszBuf[1024] {0}; LPTSTR psz pszBuf; lstrcpy(psz, ss.pszTarget); lstrcat(psz, __TEXT(/0)); psz lstrlen(psz) 1; lstrcpy(psz, ss.pszDesc); lstrcat(psz, __TEXT(/0)); psz lstrlen(psz) 1; // Try to get the text version of the hotkey TCHAR szKey[30] {0}; HotkeyToString(ss.wHotKey, szKey); lstrcpy(psz, szKey); lstrcat(psz, __TEXT(/0)); // 加一个新项到报告列表观察(3 列) HWND hwndList GetDlgItem(hDlg, IDC_VIEW); AddStringToReportView(hwndList, pszBuf, 3); return; } HandleFileDrop()函数 在响应WM_DROPFILES消息时调用此函数这个函数定义了当用户拖拽文件到窗口的客户区域时所要求的操作。接受的数据是CF_HDROP类型的这是一种从探测器窗口或从桌面拖拽文件操作时用于Shell移动文件环境下的交互格式。任何具有WS_EX_ACCEPTFILES风格设置的窗口都只对拖拽操作敏感并以这种格式封装数据。换句话说当源是Windows Shell或其它以CF_HDROP格式传递参数的程序时我们的程序也接受拖拽操作。 CF_HDROP是一种剪裁板格式用于交换基本为文件名的数据项的格式—更多关于剪裁板格式信息和CF_HDROP数据的内部结构可以参看VC的帮助文件。对于我们而言重要的是虽然它的内存Handle称为CF_HDROP还是有一定数量的函数能够读出这种格式的数据。 当你从Shell拉动文件的时候目标窗口接收到消息WM_DROPFILES其中一个变量是HDROP型的Handle。我们的HandleFileDrop()函数首先检查拉动发生的窗口如果这个窗口是列表观察则进一步抽取和解析各种文件名。你可以拉动任何文件到这个列表观察上但是仅仅快捷方式被正确地处理。 [cpp] view plaincopyprint? void HandleFileDrop(HWND hDlg, HDROP hDrop) { // 检查拉动到的窗口 POINT pt; DragQueryPoint(hDrop, pt); ClientToScreen(hDlg, pt); HWND hwndDrop WindowFromPoint(pt); if(hwndDrop ! GetDlgItem(hDlg, IDC_VIEW)) { Msg(__TEXT(抱歉你必须拉动到列表观察控件上!)); return; } // 检查文件 int iNumOfFiles DragQueryFile(hDrop, -1, NULL, 0); for(int i 0 ; i iNumOfFiles; i) { TCHAR szFileName[MAX_PATH] {0}; DragQueryFile(hDrop, i, szFileName, MAX_PATH); DoResolveShortcut(hDlg, szFileName); } DragFinish(hDrop); } DragQueryPoint()告知拉动发生时点的客户区域坐标而DragQueryFile()则依次抽取所有包装在HDROP Handle中的文件。你也可以使用这个函数获得拉动的文件数。最后必须调用DragFinish()函数来结束拉动操作。 APP_DlgProc()函数 这是应用主窗口过程由于涉及到我们前面给出的几个例子所以看一下这个处理器是有价值的 [cpp] view plaincopyprint? BOOL CALLBACK APP_DlgProc(HWND hDlg, UINT uiMsg, WPARAM wParam, LPARAM lParam) { switch(uiMsg) { case WM_INITDIALOG: OnInitDialog(hDlg); break; case WM_DROPFILES: HandleFileDrop(hDlg, reinterpret_castHDROP(wParam)); break; case WM_COMMAND: switch(wParam) { case IDC_RESOLVE: DoResolveShortcut(hDlg, NULL); return FALSE; case IDC_CREATE: DoCreateShortcut(hDlg); return FALSE; case IDC_BROWSE: OnBrowse(hDlg, IDC_SHORTCUT); return FALSE; case IDC_BROWSETARGET: OnBrowse(hDlg, IDC_TARGET); return FALSE; case IDCANCEL: EndDialog(hDlg, FALSE); return FALSE; } break; } return FALSE; } OnInitDialog()函数 在这个工程(project)中有几个东西要初始化。在处理combo框时应该有一个熟知的过程我们还需要设置列表观察控件以及编程热键控件使其使用CtrlAlt…的格式形式。 [cpp] view plaincopyprint? void OnInitDialog(HWND hDlg) { // 设置图标(T/F 大/小图标) SendMessage(hDlg, WM_SETICON, FALSE, reinterpret_castLPARAM(g_hIconSmall)); SendMessage(hDlg, WM_SETICON, TRUE, reinterpret_castLPARAM(g_hIconLarge)); // 初始化报告观察 HWND hwndList GetDlgItem(hDlg, IDC_VIEW); LPTSTR psz[] {Target, reinterpret_castTCHAR*(170), Description, reinterpret_castTCHAR*(170), Hotkey, reinterpret_castTCHAR*(100)}; MakeReportView(hwndList, psz, 3); // 可用的特殊文件夹 HWND hwndCbo GetDlgItem(hDlg, IDC_SPECIAL); int i ComboBox_AddString(hwndCbo, Desktop); ComboBox_SetItemData(hwndCbo, i, CSIDL_DESKTOP); i ComboBox_AddString(hwndCbo, Favorites); ComboBox_SetItemData(hwndCbo, i, CSIDL_FAVORITES); i ComboBox_AddString(hwndCbo, Programs); ComboBox_SetItemData(hwndCbo, i, CSIDL_PROGRAMS); i ComboBox_AddString(hwndCbo, My Documents); ComboBox_SetItemData(hwndCbo, i, CSIDL_PERSONAL); i ComboBox_AddString(hwndCbo, SendTo); ComboBox_SetItemData(hwndCbo, i, CSIDL_SENDTO); i ComboBox_AddString(hwndCbo, Start Menu); ComboBox_SetItemData(hwndCbo, i, CSIDL_STARTMENU); ComboBox_SetCurSel(hwndCbo, 0); // 初始化热键控件每一件东西都有CtrlAlt前缀 SendDlgItemMessage(hDlg, IDC_HOTKEY, HKM_SETRULES, HKCOMB_NONE | HKCOMB_S | HKCOMB_A | HKCOMB_C, HOTKEYF_CONTROL | HOTKEYF_ALT); SetDlgItemText(hDlg, IDC_TARGET, __TEXT(C://)); } 在系统文件夹中建立快捷方式 现在可以编译和运行的这个示例程序可以使你很容易在系统文件夹中建立快捷方式—所需要做的全部操作就是从combo框中选择一个文件夹名。如果你希望在你自己的程序中静默地做这项工作只要知道所涉及的文件夹名剩下的就是用这个全路径名格式化一个串。 下面是做这项工作的一个简单的函数。作为变量它接受要建立的.lnk文件名特殊文件夹的ID(有CSIDL_XXX格式的常量)以及指向的文件名。代码是SHCreateShortcutEx()的一个封装 [cpp] view plaincopyprint? HRESULT SHCreateSystemShortcut(LPCTSTR szLnkFile, int nFolder, LPCTSTR szFile) { WCHAR wszLnkFile[MAX_PATH] {0}; TCHAR szPath[MAX_PATH] {0}; IShellLink* pShellLink NULL; IPersistFile* pPF NULL; // 建立适当的COM服务器 HRESULT hr CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, reinterpret_castLPVOID*(pShellLink)); if(FAILED(hr)) return hr; // 设置属性 pShellLink-SetPath(szFile); // 取得IPersistFile接口用于保存 hr pShellLink-QueryInterface( IID_IPersistFile, reinterpret_castLPVOID*(pPF)); if(FAILED(hr)) { pShellLink-Release(); return hr; } // 准备快捷方式名 SHGetSpecialFolderPath(NULL, szPath, nFolder, FALSE); if(szPath[lstrlen(szPath) - 1] ! //) lstrcat(szPath, __TEXT(//)); lstrcat(szPath, szLnkFile); // 存储LNK(Unicode 名) MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, szPath, -1, wszLnkFile, MAX_PATH); hr pPF-Save(wszLnkFile, TRUE); // 清理 pPF-Release(); pShellLink-Release(); return hr; } 使用上面的函数在‘桌面’上在‘开始’菜单中在‘程序文件’中或在‘Favorites’中建立快捷方式就非常容易了。要证明这一点要求在‘开始’菜单中添加一个新项指向‘记事本’则只需要 [cpp] view plaincopyprint? SHCreateSystemShortcut(__TEXT(Notepad.lnk), CSIDL_STARTMENU, __TEXT(c://windows//notepad.exe)); 就可以了显然c:/windows/路径应该置换成你机器上的实际Windows的目录。还要注意在Windows NT下‘notepad.exe’存储在‘System’目录下。 你也可以建立指向目录和非可执行文件的快捷方式。事实上引用任何系统文件对象仅仅需要传递路径到IShellLink::SetPath()或传递PIDL调用IShellLink::SetIDList()。 ‘发送到’文件夹 ‘发送到’文件夹是包含两个非快捷方式对象的文件夹对于快捷方式是不值得注意的。如果在Windows上安装了IE4.0以上版或在Windows98以上版系统上‘发送到’文件夹可能包含对Email容器或桌面的引用。使用这个机理你也可以从Shell把给定的文件作为新消息的附件直接发送到你自己的发件箱或作为快捷方式发送到桌面。 这个截图显示有两个没有典型的快捷标记的项它们是‘桌面快捷方式’是一个空的.DeskLink文件其长度为0字节。如果你搜索这个扩展名的注册表信息就会发现在其后有一个COM对象。 了解到它由一个COM对象支持文件是一大进步但是是哪一种COM对象在支持它COM对象实现了什么接口都还需要进一步探讨。事实上这是一个Shell扩展更确切地讲是一个拖动处理器。我们将在第十五章中讨论Shell扩展。现在仅说明‘发送到’文件夹不仅包含快捷方式还有别的就可以了。使用DeskLink串纯粹是一种表示你也可以使用其它串来表示。 ‘最近文档’文件夹 ‘最近文档’文件夹收集最近打开的文档。这个目录下的内容可以通过单击‘开始’菜单的‘文档’项来查看其物理位置在Windows目录下。然而奇怪的是在它包含的快捷方式和通过菜单显示的项之间并不是11对应的。 Shell API给出了一个称之为SHAddToRecentDocs()的函数使程序员能够把文档的链接存储到这个文件夹下。 [cpp] view plaincopyprint? void SHAddToRecentDocs(UINT uFlags, LPCVOID pv); 第一个变量指出了第二个变量的类型PIDL或路径名可以取SHARD_PATH或SHARD_PIDL值。使用这个函数能够成功地把你的文档引用加进菜单中。但是如果简单地在这个文件夹上建立一个快捷方式就不对了—也就是说建立快捷方式是必要的但不充分。SHAddToRecentDocs()显然做了更多的事情。 最终SHAddToRecentDocs()添加项目到由‘开始菜单’使用的MRU(最近使用的)列表中并且简单地加这个文件到‘最近文档’文件夹中。这个函数还复制这个文件夹中的快捷方式以及处理菜单的顺序。因此你应该坚定地使用函数而不是其他方法以兼容未来在某些方面实现方式的变化。 小结 这一章中探讨了快捷方式这个在Windows的任何书和文章中都讨论过的课题。快捷方式是相对简单的但是没有建立和解析它的单一函数这一章中讨论并写出了这样的函数还查看了 快捷方式的作用 怎样建立和解析它们 某些与快捷方式一道工作的有用的函数 拖拽和热键控件 快捷方式和系统文件夹之间的关系 转载于:https://www.cnblogs.com/songtzu/p/3239827.html