<?xml version="1.0" encoding="utf-8"?><?xml-stylesheet type='text/xsl' href='http://sluttery.spaces.live.com/mmm2008-07-24_12.50/rsspretty.aspx?rssquery=en-US;http%3a%2f%2fsluttery.spaces.live.com%2fcategory%2f%e6%8a%80%e6%9c%af%2ffeed.rss' version='1.0'?><rss version="2.0" xmlns:slash="http://purl.org/rss/1.0/modules/slash/" xmlns:msn="http://schemas.microsoft.com/msn/spaces/2005/rss" xmlns:live="http://schemas.microsoft.com/live/spaces/2006/rss" xmlns:dcterms="http://purl.org/dc/terms/" xmlns:cf="http://www.microsoft.com/schemas/rss/core/2005" xmlns:wfw="http://wellformedweb.org/CommentAPI/"><channel><title>珠穆朗玛: 技术</title><description /><link>http://sluttery.spaces.live.com/?_c11_BlogPart_BlogPart=blogview&amp;_c=BlogPart&amp;partqs=cat%25E6%258A%2580%25E6%259C%25AF</link><language>en-US</language><pubDate>Wed, 27 Aug 2008 13:07:19 GMT</pubDate><lastBuildDate>Wed, 27 Aug 2008 13:07:19 GMT</lastBuildDate><generator>Microsoft Spaces v1.1</generator><docs>http://www.rssboard.org/rss-specification</docs><ttl>60</ttl><cf:parentRSS>http://sluttery.spaces.live.com/blog/feed.rss</cf:parentRSS><live:type>blogcategory</live:type><live:identity><live:id>3848887354281525204</live:id><live:alias>sluttery</live:alias></live:identity><cf:listinfo><cf:group ns="http://schemas.microsoft.com/live/spaces/2006/rss" element="typelabel" label="Type" /><cf:group ns="http://schemas.microsoft.com/live/spaces/2006/rss" element="tag" label="Tag" /><cf:group element="category" label="Category" /><cf:sort element="pubDate" label="Date" data-type="date" default="true" /><cf:sort element="title" label="Title" data-type="string" /><cf:sort ns="http://purl.org/rss/1.0/modules/slash/" element="comments" label="Comments" data-type="number" /></cf:listinfo><item><title>用于 Symbian OS 的 CString 类</title><link>http://sluttery.spaces.live.com/Blog/cns!3569FEA80C717FD4!4255.entry</link><description>在 Google 上搜 Symbian 和 CString，会发现有很多人在寻找这个东西，可惜的很，就是没有一个人愿意写一个玩玩。不知道是不是大部分的人到后来都屈服于描述符的淫威之下了。&lt;br&gt;&lt;br&gt;老汉不才，扛起了这面大旗，把原来用在 TWL 里的 str.h 又加工了一番，增加了对 Symbian 操作系统的初步支持。有感兴趣的朋友可以到 &lt;a target="_blank" href="http://cid-3569fea80c717fd4.skydrive.live.com/browse.aspx/Public"&gt;http://cid-3569fea80c717fd4.skydrive.live.com/browse.aspx/Public&lt;/a&gt; 下载。&lt;br&gt;&lt;br&gt;出于发布方便的目的，该头文件中的 lstrtoxl 函数没有提取到单独的文件中，使用者自己稍作一些剪切/粘贴的工作即可。&lt;br&gt;&lt;br&gt;如果发现 BUG，请反馈给我。&lt;br&gt;&lt;br&gt;&lt;img src="http://c.services.spaces.live.com/CollectionWebService/c.gif?cid=3848887354281525204&amp;page=RSS%3a+%e7%94%a8%e4%ba%8e+Symbian+OS+%e7%9a%84+CString+%e7%b1%bb&amp;referrer=" width="1px" height="1px" border="0" alt=""&gt;&lt;img style="position:absolute" alt="" width="0px" height="0px" src="http://c.live.com/c.gif?NC=31263&amp;amp;NA=1149&amp;amp;PI=73329&amp;amp;RF=&amp;amp;DI=3919&amp;amp;PS=85545&amp;amp;TP=sluttery.spaces.live.com&amp;amp;GT1=sluttery"&gt;</description><comments>http://sluttery.spaces.live.com/Blog/cns!3569FEA80C717FD4!4255.entry#comment</comments><guid isPermaLink="true">http://sluttery.spaces.live.com/Blog/cns!3569FEA80C717FD4!4255.entry</guid><pubDate>Thu, 20 Mar 2008 14:46:01 GMT</pubDate><slash:comments>0</slash:comments><msn:type>blogentry</msn:type><live:type>blogentry</live:type><live:typelabel>Blog entry</live:typelabel><wfw:commentRss>http://sluttery.spaces.live.com/blog/cns!3569FEA80C717FD4!4255/comments/feed.rss</wfw:commentRss><wfw:comment>http://sluttery.spaces.live.com/Blog/cns!3569FEA80C717FD4!4255.entry#comment</wfw:comment><dcterms:modified>2008-03-20T14:47:10Z</dcterms:modified></item><item><title>调出系统的证书管理界面</title><link>http://sluttery.spaces.live.com/Blog/cns!3569FEA80C717FD4!4104.entry</link><description>&lt;p&gt;好久没有贴代码了，今天贴一个。目的和作用如题。 &lt;div style="border-right:#ff6600 1px solid;border-top:#ff6600 1px solid;background:gray;overflow:scroll;border-left:#ff6600 1px solid;width:100%;border-bottom:#ff6600 1px solid"&gt;&lt;pre&gt;&lt;font face=FIXEDSYS&gt;
// copied from cryptuiapi.h
typedef struct _CRYPTUI_CERT_MGR_STRUCT
{
    DWORD dwSize;
    HWND hwndParent;
    DWORD dwFlags;
    LPCWSTR pwszTitle;
    LPCSTR pszInitUsageOID;
} CRYPTUI_CERT_MGR_STRUCT, *PCRYPTUI_CERT_MGR_STRUCT;
typedef const CRYPTUI_CERT_MGR_STRUCT *PCCRYPTUI_CERT_MGR_STRUCT;

int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE, LPTSTR pszCmdLine, int nCmdShow)
{
 	HINSTANCE hCryptui = LoadLibrary(TEXT(&amp;quot;cryptui.dll&amp;quot;));
	if(!hCryptui)
		return 0;

	typedef BOOL (WINAPI* fnCryptUIDlgCertMgr)(PCCRYPTUI_CERT_MGR_STRUCT pCryptUICertMgr);

	fnCryptUIDlgCertMgr CryptUIDlgCertMgr = NULL;
	((FARPROC&amp;amp;)CryptUIDlgCertMgr) = GetProcAddress(hCryptui, TEXT(&amp;quot;CryptUIDlgCertMgr&amp;quot;));
	if(CryptUIDlgCertMgr)
	{
		CRYPTUI_CERT_MGR_STRUCT ccm = { sizeof(CRYPTUI_CERT_MGR_STRUCT) };
		CryptUIDlgCertMgr(&amp;amp;ccm);
	}

	FreeLibrary(hCryptui);

	return 0;
}

&lt;/font&gt;&lt;/pre&gt;&lt;/div&gt;&lt;img src="http://c.services.spaces.live.com/CollectionWebService/c.gif?cid=3848887354281525204&amp;page=RSS%3a+%e8%b0%83%e5%87%ba%e7%b3%bb%e7%bb%9f%e7%9a%84%e8%af%81%e4%b9%a6%e7%ae%a1%e7%90%86%e7%95%8c%e9%9d%a2&amp;referrer=" width="1px" height="1px" border="0" alt=""&gt;&lt;img style="position:absolute" alt="" width="0px" height="0px" src="http://c.live.com/c.gif?NC=31263&amp;amp;NA=1149&amp;amp;PI=73329&amp;amp;RF=&amp;amp;DI=3919&amp;amp;PS=85545&amp;amp;TP=sluttery.spaces.live.com&amp;amp;GT1=sluttery"&gt;</description><comments>http://sluttery.spaces.live.com/Blog/cns!3569FEA80C717FD4!4104.entry#comment</comments><guid isPermaLink="true">http://sluttery.spaces.live.com/Blog/cns!3569FEA80C717FD4!4104.entry</guid><pubDate>Fri, 07 Dec 2007 11:25:50 GMT</pubDate><slash:comments>0</slash:comments><msn:type>blogentry</msn:type><live:type>blogentry</live:type><live:typelabel>Blog entry</live:typelabel><wfw:commentRss>http://sluttery.spaces.live.com/blog/cns!3569FEA80C717FD4!4104/comments/feed.rss</wfw:commentRss><wfw:comment>http://sluttery.spaces.live.com/Blog/cns!3569FEA80C717FD4!4104.entry#comment</wfw:comment><dcterms:modified>2007-12-07T11:25:50Z</dcterms:modified></item><item><title>译：Windows Vista for Developers - 第二部分 深入任务对话框</title><link>http://sluttery.spaces.live.com/Blog/cns!3569FEA80C717FD4!3724.entry</link><description>&lt;div&gt;
&lt;div&gt;
&lt;p&gt;正如 &lt;a href="http://weblogs.asp.net/kennykerr/archive/2006/07/12/Windows-Vista-for-Developers-_1320_-Part-1-_1320_-Aero-Wizards.aspx"&gt;Aero 向导&lt;/a&gt;相比传统向导带来了更佳的用户体验那样，任务对话框则是带来了比高龄的消息框更好的用户体验。不过，任务对话框要比低级的消息框提供了多得多的一长串的特性以及可定制性。而随这些而来的则是相当深的复杂度。在此 &lt;a href="http://weblogs.asp.net/kennykerr/archive/2006/07/12/Windows-Vista-for-Developers-_1320_-A-New-Series.aspx"&gt;Windows Vista for Developers&lt;/a&gt; 系列的这第二个部分里，我将会为你展示如何通过 C++ 有效地使用任务对话框 API 来既简单又容易地构建出形形色色的对话框。如果你现在就迫不及待，你可以跳到文章的末尾，在那儿你可以找到一份供下载的源代码，里面有对任务对话框 API 的完整 C++ 封装。&lt;br&gt;&lt;br&gt;在 &lt;span style="font-weight:bold"&gt;comctl32.dll&lt;/span&gt; 库的内部，隐藏着一个名为 &lt;span style="font-weight:bold"&gt;CTaskDialog &lt;/span&gt;的 C++ 类，它负责实现了任务对话框提供的所有功能。由 &lt;span style="font-weight:bold"&gt;comctl32.dll &lt;/span&gt;导出的 &lt;span style="font-weight:bold"&gt;TaskDialog &lt;/span&gt;和 &lt;span style="font-weight:bold"&gt;TaskDialogIndirect &lt;/span&gt;函数会为你调用到它。&lt;span style="font-weight:bold"&gt;TaskDialog &lt;/span&gt;函数仅仅是 &lt;span style="font-weight:bold"&gt;TaskDialogIndirect &lt;/span&gt;的一个简化版本，它提供了较少的功能，不过也更容易使用一些。由于无论哪一个都并不是即刻就很有用，所以本文将专注于 &lt;span style="font-weight:bold"&gt;TaskDialogIndirect&lt;/span&gt;，并演示如何使用一个 C++ 的小小辅助就可以使得它相当的便于使用。&lt;br&gt;&lt;br&gt;&lt;font face="Courier New"&gt;以下代码创建一个最简单的任务对话框：&lt;br&gt;&lt;br&gt;TASKDIALOGCONFIG config = { sizeof (TASKDIALOGCONFIG) };&lt;br&gt; &lt;br&gt;int selectedButtonId = 0;&lt;br&gt;int selectedRadioButtonId = 0;&lt;br&gt;BOOL verificationChecked = FALSE;&lt;br&gt; &lt;br&gt;HRESULT result = ::TaskDialogIndirect(&amp;amp;config,&lt;br&gt;                                      &amp;amp;selectedButtonId,&lt;br&gt;                                      &amp;amp;selectedRadioButtonId,&lt;br&gt;                                      &amp;amp;verificationChecked);&lt;br&gt;&lt;/font&gt;&lt;br&gt;&lt;span style="font-weight:bold"&gt;TASKDIALOGCONFIG &lt;/span&gt;结构提供了一大堆你可以填充的成员域以及标志，其中还有一个你可以用来提供对任务对话框发出的事件作出响应的回调函数：&lt;br&gt;&lt;br&gt;&lt;font face="Courier New"&gt;struct TASKDIALOGCONFIG&lt;br&gt;{&lt;br&gt;    UINT cbSize;&lt;br&gt;    HWND hwndParent;&lt;br&gt;    HINSTANCE hInstance;&lt;br&gt;    TASKDIALOG_FLAGS dwFlags;&lt;br&gt;    TASKDIALOG_COMMON_BUTTON_FLAGS dwCommonButtons;&lt;br&gt;    PCWSTR pszWindowTitle;&lt;br&gt;    union&lt;br&gt;    {&lt;br&gt;        HICON hMainIcon;&lt;br&gt;        PCWSTR pszMainIcon;&lt;br&gt;    };&lt;br&gt;    PCWSTR pszMainInstruction;&lt;br&gt;    PCWSTR pszContent;&lt;br&gt;    UINT cButtons;&lt;br&gt;    const TASKDIALOG_BUTTON* pButtons;&lt;br&gt;    int nDefaultButton;&lt;br&gt;    UINT cRadioButtons;&lt;br&gt;    const TASKDIALOG_BUTTON* pRadioButtons;&lt;br&gt;    int nDefaultRadioButton;&lt;br&gt;    PCWSTR pszVerificationText;&lt;br&gt;    PCWSTR pszExpandedInformation;&lt;br&gt;    PCWSTR pszExpandedControlText;&lt;br&gt;    PCWSTR pszCollapsedControlText;&lt;br&gt;    union&lt;br&gt;    {&lt;br&gt;        HICON hFooterIcon;&lt;br&gt;        PCWSTR pszFooterIcon;&lt;br&gt;    };&lt;br&gt;    PCWSTR pszFooter;&lt;br&gt;    PFTASKDIALOGCALLBACK pfCallback;&lt;br&gt;    LONG_PTR lpCallbackData;&lt;br&gt;    UINT cxWidth;&lt;br&gt;};&lt;br&gt;&lt;/font&gt;&lt;br&gt;正如你能想象到的，恰如其分地填充这个结构简直是个挑战，而出错的机会却相当的大。尽管其中的很多域可以置零，但为了能达到预期的效果，下列的域通常是要进行设置的：&lt;br&gt;&lt;br&gt;&lt;span style="font-weight:bold"&gt;cbSize &lt;/span&gt;域在编译时指定了此结构的大小，这是一种常用技术，用以在 C 中表示数据结构的版本。它使得操作系统可以知道应用程序编译时采用了哪种版本结构，并据此来像应用程序所期望的那样来对其中的域以及功能性作一个确定的推断。&lt;br&gt;&lt;br&gt;&lt;span style="font-weight:bold"&gt;hwndParent &lt;/span&gt;域保存了父窗口的句柄。这就允许结果对话框以模态窗口的方式工作，而且还可以使你对它进行相对于父窗口的定位。&lt;br&gt;&lt;br&gt;&lt;span style="font-weight:bold"&gt;hInstance &lt;/span&gt;域对 C++ 开发人员有用，因为它能使你使用 ID 来指定字符串以及图标资源而不必使用代码来加载或者创建它们。&lt;br&gt;&lt;br&gt;&lt;span style="font-weight:bold"&gt;dwFlags &lt;/span&gt;域保存了若干个允许你控制对话框的行为以及外观的标志。本分的后续篇幅会分别探讨这些标志。&lt;br&gt;&lt;br&gt;&lt;span style="font-weight:bold"&gt;文本标题&lt;/span&gt;&lt;br&gt;&lt;br&gt;&lt;span style="font-weight:bold"&gt;TASKDIALOGCONFIG &lt;/span&gt;结构提供了以下在任务对话框上设置不同的文本标题的域：&lt;br&gt;&lt;br&gt;&lt;font face="Courier New"&gt;pszWindowTitle&lt;br&gt;pszMainInstruction&lt;br&gt;pszContent&lt;br&gt;pszVerificationText&lt;br&gt;pszExpandedInformation&lt;br&gt;pszExpandedControlText&lt;br&gt;pszCollapsedControlText&lt;br&gt;pszFooter&lt;br&gt;&lt;/font&gt;&lt;br&gt;所有的这些域都可以被初始化为字符串指针或者是用 &lt;span style="font-weight:bold"&gt;MAKEINTRESOURCE &lt;/span&gt;宏创建的资源标识符。除此之外，你还可以为自定义按钮设置标题，我们会在下一节里涉及这些。&lt;br&gt;&lt;br&gt;下面的窗口截图展示了不同的文本标题：&lt;br&gt;&lt;br&gt;&lt;img height=242 src="http://tkfiles.storage.msn.com/x1pQ9fDJCqr-278bj324ojFY5tgI3LJJkA56pJE5D3qZzI5wrUC4lCBWBCoyufb8fUM1ZgLxNyWuhiJfwAx8J7_GVS6pShvnHX8xtGkfNTzxdE-RzMDiMO1NQ" width=366&gt; &lt;br&gt;&lt;br&gt;“Window title” 可以在对话框创建之前通过 &lt;span style="font-weight:bold"&gt;pszWindowTitle &lt;/span&gt;域来指定。一旦创建，就可以使用常规的 &lt;span style="font-weight:bold"&gt;SetWindowText &lt;/span&gt;函数更新标题了。 &lt;br&gt;&lt;br&gt;“Main instruction” 可以在对话框创建之前通过 &lt;span style="font-weight:bold"&gt;pszMainInstruction &lt;/span&gt;域来指定。一旦创建，你就必须使用 &lt;span style="font-weight:bold"&gt;TDM_SET_ELEMENT_TEXT &lt;/span&gt;消息来更新文本。设置 &lt;span style="font-weight:bold"&gt;WPARAM &lt;/span&gt;为 &lt;span style="font-weight:bold"&gt;TDE_MAIN_INSTRUCTION&lt;/span&gt;，设置 &lt;span style="font-weight:bold"&gt;LPARAM &lt;/span&gt;为字符串指针或者用 &lt;span style="font-weight:bold"&gt;MAKEINTRESOURCE &lt;/span&gt;宏创建的资源标识符。同样的方法也可用在 “Content”、“Verification text”、“Expanded information” 以及 “Footer” 文本标题上，只要为 &lt;span style="font-weight:bold"&gt;WPARAM &lt;/span&gt;传递不同的需要更新文本的控件的标识符即可。&lt;br&gt;&lt;br&gt;“Expanded control text” 和 “Collapsed control text” 仅能在对话框创建之前通过 &lt;span style="font-weight:bold"&gt;pszExpandedControlText &lt;/span&gt;和 &lt;span style="font-weight:bold"&gt;pszCollapsedControlText &lt;/span&gt;域指定。Windows Vista Build 5456 中，在展开和收缩扩充信息的控件里有一个 bug。如果该控件失去了焦点，文本会恢复到为收缩状态所指定的值。 &lt;br&gt;&lt;br&gt;设置文本标题是否是一个挑战取决于你从哪儿取得文本以及你什么时候要设置它。在后文中，我们会看到如何使用 C++ 来梦幻般地简化这一工作。&lt;br&gt;&lt;br&gt;&lt;span style="font-weight:bold"&gt;按钮&lt;/span&gt;&lt;br&gt;&lt;br&gt;任务对话框支持公用按钮与自定义按钮的任意组合。当前定义的公用按钮有：&lt;br&gt;&lt;br&gt;&lt;font face="Courier New"&gt;TDCBF_OK_BUTTON (IDOK)&lt;br&gt;TDCBF_YES_BUTTON (IDYES)&lt;br&gt;TDCBF_NO_BUTTON (IDNO)&lt;br&gt;TDCBF_CANCEL_BUTTON (IDCANCEL)&lt;br&gt;TDCBF_RETRY_BUTTON (IDRETRY)&lt;br&gt;TDCBF_CLOSE_BUTTON (IDCLOSE)&lt;br&gt;&lt;/font&gt;&lt;br&gt;你可以在 &lt;span style="font-weight:bold"&gt;dwCommonButtons &lt;/span&gt;域中指定这些按钮标志的任意组合。括号中的常量标明了当特定按钮被点击之后用以标识该按钮的按钮标识符。 &lt;br&gt;    &lt;br&gt;可在文末找到的下载里的 &lt;span style="font-weight:bold"&gt;Common Buttons 示例&lt;/span&gt;演示了公用按钮的使用：&lt;br&gt;&lt;br&gt;&lt;img height=149 src="http://tkfiles.storage.msn.com/x1pQ9fDJCqr-278bj324ojFY1wf7yymRlWNDWDp1d5DeIib1BkfA6ZGgsx7LMx5Iqo-ATcKuzzQBr8OqjkvhHJjK79K4M6exlzyKUQydhOrEbAyQszfNXcD6Q" width=496&gt; &lt;br&gt;&lt;br&gt;你不能直接操纵公用按钮的事情之一是对它们重新排序或者改变它们的标题。要完全控制这些按钮，你需要提供一组 &lt;span style="font-weight:bold"&gt;TASKDIALOG_BUTTON&lt;/span&gt; 结构。下面是指定了两个自定义按钮的简单例子：&lt;br&gt;&lt;br&gt;&lt;font face="Courier New"&gt;TASKDIALOGCONFIG config = { sizeof (TASKDIALOGCONFIG) };&lt;br&gt; &lt;br&gt;TASKDIALOG_BUTTON buttons[] =&lt;br&gt;{&lt;br&gt;    { 101, L&amp;quot;First Button&amp;quot;  },&lt;br&gt;    { 102, L&amp;quot;Second Button&amp;quot; }&lt;br&gt;};&lt;br&gt;&lt;br&gt;config.pButtons = buttons;&lt;br&gt;config.cButtons = _countof(buttons);&lt;br&gt;&lt;/font&gt;&lt;br&gt;你也可以使用 &lt;span style="font-weight:bold"&gt;MAKEINTRESOURCE &lt;/span&gt;宏来为按钮所用到的字符串表中的字符串指定一个资源标识符。&lt;br&gt;&lt;br&gt;除按钮外，任务对话框还可以容纳一组单选按钮。它们也可以使用一组 &lt;span style="font-weight:bold"&gt;TASKDIALOG_BUTTON&lt;/span&gt; 结构来描述：&lt;br&gt;&lt;br&gt;&lt;font face="Courier New"&gt;TASKDIALOG_BUTTON radioButtons[] =&lt;br&gt;{&lt;br&gt;    { 201, L&amp;quot;First Radio Button&amp;quot;  },&lt;br&gt;    { 202, L&amp;quot;Second Radio Button&amp;quot; }&lt;br&gt;};&lt;br&gt; &lt;br&gt;config.pRadioButtons = radioButtons;&lt;br&gt;config.cRadioButtons = _countof(radioButtons);&lt;br&gt;&lt;br&gt;&lt;/font&gt;下面是以上代码的结果：&lt;br&gt;&lt;br&gt;&lt;img height=193 src="http://tkfiles.storage.msn.com/x1pQ9fDJCqr-278bj324ojFY7eVheqZa9dOS89COLAerCV4tdlT2o9s9cRXw_g2dc4v3exKRMEUU2BnSxcIAUCtG3HLSNbSPuymtx3HBwWtN8A" width=366&gt;&lt;br&gt; &lt;br&gt;你还可以指定 &lt;span style="font-weight:bold"&gt;TDF_USE_COMMAND_LINKS&lt;/span&gt; 标志把自定义按钮显示为命令链接。如果你不愿看到标题边的图标的话可以使用 &lt;span style="font-weight:bold"&gt;TDF_USE_COMMAND_LINKS_NO_ICON&lt;/span&gt; 标志。&lt;br&gt;&lt;br&gt;&lt;img height=285 src="http://tkfiles.storage.msn.com/x1pQ9fDJCqr-278bj324ojFY_MuSl10wPE5upwOAhkuf0KwNRKI3iaCwXXhwZmoBcvq_H0CDKu0p1igewu5VEDzFNSR4hEWu9DabOs9JMuZcYQ" width=366&gt; &lt;br&gt;&lt;br&gt;正如你看到的，这些标志仅仅影响自定义按钮。你指定的任何公用按钮仍然显示为常规按钮。&lt;br&gt;&lt;br&gt;你还可以在你的按钮旁边显示那个声名狼藉的用户帐号控制（User Account Control）的盾形图标，只要给窗口发送 &lt;span style="font-weight:bold"&gt;TDM_SET_BUTTON_ELEVATION_REQUIRED_STATE&lt;/span&gt; 消息即可。&lt;span style="font-weight:bold"&gt;WPARAM &lt;/span&gt;指定按钮标识符，&lt;span style="font-weight:bold"&gt;LPARAM &lt;/span&gt;指定一个指示要显示还是隐藏盾标的 &lt;span style="font-weight:bold"&gt;BOOL &lt;/span&gt;值。 &lt;br&gt;&lt;br&gt;&lt;img height=190 src="http://tkfiles.storage.msn.com/x1pQ9fDJCqr-278bj324ojFY5vBL8KglHYrA2eNcAiPp490iVrJTT4StMdgxLWCk1g1a4T03EMYj1Zsnj3GatVT2pE0vuLv5_0L_L1uaHEuLWY" width=366&gt; &lt;br&gt;&lt;br&gt;无论你的按钮是命令链接还是常规按钮，它都会生效。同样，它还可以工作于像 OK 和 Cancel 这样的公用按钮，尽管通常都不会为这样一个按钮准备一个漂亮的用户体验去获取权限的提升。&lt;br&gt;&lt;br&gt;&lt;span style="font-weight:bold"&gt;图标&lt;/span&gt;&lt;br&gt;&lt;br&gt;任务对话框可以选择性地显示一个“主”图标以及一个“脚注”图标。主图标出现在主指令（main instruction）文本的边上，而且，如果指定了 &lt;span style="font-weight:bold"&gt;TDF_CAN_BE_MINIMIZED &lt;/span&gt;标志，也可以显示在标题栏上。当存在脚注文本的时候，脚注图标显示在其附近。&lt;br&gt;&lt;br&gt;&lt;img height=185 src="http://tkfiles.storage.msn.com/x1pQ9fDJCqr-278bj324ojFY7RDWFmECteSvFi1ELiNnvnf60OQ-KEkJ2LEPC9TothT_Fv9Qdni6cgWllIGxcvC50VHQNdA2fIFJHa_pNNhjFQ" width=366&gt; &lt;br&gt;&lt;br&gt;指定图标需要点技巧。在对话框创建之前可以使用 &lt;span style="font-weight:bold"&gt;pszMainIcon &lt;/span&gt;域来指定一个使用了 &lt;span style="font-weight:bold"&gt;MAKEINTRESOURCE &lt;/span&gt;宏的图标资源标识符。如果你用这种方法，要确保 &lt;span style="font-weight:bold"&gt;TDF_USE_HICON_MAIN&lt;/span&gt; 标志没有设置。另外，你还可以在 &lt;span style="font-weight:bold"&gt;hMainIcon &lt;/span&gt;域里指定一个图标句柄，在这种情况下，你就要确保设置了 &lt;span style="font-weight:bold"&gt;the TDF_USE_HICON_MAIN&lt;/span&gt; 标志。 &lt;br&gt;&lt;br&gt;脚注图标是用同样的方式工作的。在对话框创建之前，可以使用 &lt;span style="font-weight:bold"&gt;pszFooterIcon &lt;/span&gt;域指定一个图标资源标识符。此外，还可以在 &lt;span style="font-weight:bold"&gt;hFooterIcon &lt;/span&gt;中指定一个图标句柄。对于脚注图标，表明你是要使用一个句柄的标志是 &lt;span style="font-weight:bold"&gt;TDF_USE_HICON_FOOTER&lt;/span&gt;。&lt;br&gt;&lt;br&gt;对话框创建后，你可以发送 &lt;span style="font-weight:bold"&gt;TDM_UPDATE_ICON &lt;/span&gt;消息来更新图标。把 &lt;span style="font-weight:bold"&gt;WPARAM &lt;/span&gt;设置为 &lt;span style="font-weight:bold"&gt;TDIE_ICON_MAIN &lt;/span&gt;来更新主图标，设置为 &lt;span style="font-weight:bold"&gt;TDIE_ICON_FOOTER&lt;/span&gt; 来更新脚注图标。&lt;span style="font-weight:bold"&gt;LPARAM &lt;/span&gt;是要设置成图标资源标识符还是图标句柄则取决于创建时指定的标志是 &lt;span style="font-weight:bold"&gt;TDF_USE_HICON_MAIN&lt;/span&gt; 还是 &lt;span style="font-weight:bold"&gt;TDF_USE_HICON_FOOTER&lt;/span&gt;。&lt;br&gt;&lt;br&gt;如同文本标题一样，把这些全部搞妥也是个挑战，本文稍后献上的 C++ 解决方案会在相当程度上简化这一问题。&lt;br&gt;&lt;br&gt;&lt;span style="font-weight:bold"&gt;进度条&lt;/span&gt;&lt;br&gt;&lt;br&gt;任务对话框的显著特性之一是它提供了一个进度条。只要简单地指定一下 &lt;span style="font-weight:bold"&gt;TDF_SHOW_PROGRESS_BAR&lt;/span&gt; 标志，你的任务对话框就会包含一个进度条。如果你希望把进度条显示为来回摆动的形式，可以使用 &lt;span style="font-weight:bold"&gt;TDF_SHOW_MARQUEE_PROGRESS_BAR&lt;/span&gt; 标志。对话框创建之后，你还可以使用 &lt;span style="font-weight:bold"&gt;TDM_SET_PROGRESS_BAR_MARQUEE&lt;/span&gt; 消息在常规进度条和往返进度条之间切换。把 &lt;span style="font-weight:bold"&gt;WPARAM &lt;/span&gt;设置为 &lt;span style="font-weight:bold"&gt;TRUE &lt;/span&gt;来显示为往返进度条，设置为 &lt;span style="font-weight:bold"&gt;FALSE &lt;/span&gt;来显示常规进度条。&lt;span style="font-weight:bold"&gt;LPARAM &lt;/span&gt;则以毫秒为单位用以控制往返动画的时延。&lt;br&gt;&lt;br&gt;可以用 &lt;span style="font-weight:bold"&gt;TDM_SET_PROGRESS_BAR_RANGE&lt;/span&gt; 消息来指定进度条的范围。&lt;span style="font-weight:bold"&gt;LPARAM &lt;/span&gt;中指定了范围的两个值，低位字中是范围的最小值而高位字中为范围的最大致。&lt;span style="font-weight:bold"&gt;TDM_SET_PROGRESS_BAR_POS&lt;/span&gt; 消息用来设置进度条的居于范围内的位置。&lt;span style="font-weight:bold"&gt;WPARAM &lt;/span&gt;指定了位置的值。&lt;br&gt;&lt;br&gt;还可以用 &lt;span style="font-weight:bold"&gt;TDM_SET_PROGRESS_BAR_STATE &lt;/span&gt;消息来改变进度条的状态。&lt;span style="font-weight:bold"&gt;WPARAM &lt;/span&gt;可以设置为 &lt;span style="font-weight:bold"&gt;PBST_NORMAL&lt;/span&gt;、&lt;span style="font-weight:bold"&gt;PBST_PAUSED&lt;/span&gt; 或者 &lt;span style="font-weight:bold"&gt;PBST_ERROR&lt;/span&gt;。&lt;br&gt;&lt;br&gt;本文下载里的 &lt;strong&gt;Progress 示例&lt;/strong&gt;以及 &lt;strong&gt;Progress Effects 示例&lt;/strong&gt;中演示了进度条的所有功能。&lt;br&gt;&lt;br&gt;&lt;img height=184 src="http://tkfiles.storage.msn.com/x1pQ9fDJCqr-278bj324ojFY-w_1dvdEt-05-DsOPn2JbdZPNKCfuGUQ4mk9fRzq26bOPUc-7jKVQVl0MKwkF574bEZukJabWE2SO-Tk5vgkU4" width=386&gt; &lt;br&gt;&lt;br&gt;&lt;strong&gt;通知&lt;/strong&gt;&lt;br&gt;&lt;br&gt;任务对话框提供了一组通知，允许你加入行为以及事件发生时的响应。这些通知会被传递到你在 &lt;strong&gt;pfCallback &lt;/strong&gt;域中指定的一个回调函数。此回调函数的原型如下：&lt;br&gt;&lt;br&gt;&lt;font face="Courier New"&gt;HRESULT __stdcall Callback(HWND handle, &lt;br&gt;                           UINT notification, &lt;br&gt;                           WPARAM wParam, &lt;br&gt;                           LPARAM lParam, &lt;br&gt;                           LONG_PTR data);&lt;br&gt;&lt;br&gt;不过这一原型有些误导&lt;/font&gt;，因为没有任何消息是返回一个 &lt;strong&gt;HRESULT&lt;/strong&gt;。仅有的一个返回了点东西的消息也仅仅是返回一个值为 &lt;strong&gt;TRUE &lt;/strong&gt;或者 &lt;strong&gt;FALSE&lt;/strong&gt; 的布尔值。我希望这个问题能在发布之前解决（译者注：文本成文于 Windows Vista 正式版发布之前）。其中的 handle 参数提供了任务对话框的窗口句柄，你可以保存下来以备其他时间使用，直到收到 &lt;strong&gt;TDN_DESTROYED&lt;/strong&gt; 通知为止。data 参数提供了你在 &lt;strong&gt;lpCallbackData &lt;/strong&gt;中指定的指针。这通常用于传递一个 C++ 窗口对象的指针到静态回调函数中。现在我们看一下这些通知。&lt;br&gt;&lt;br&gt;&lt;strong&gt;TDN_DIALOG_CONSTRUCTED&lt;/strong&gt; 是第一个到达的通知。它在对话框创建完毕而即将要显示的时候触发，因而随之提供了任务对话框的窗口句柄。在这个时候，你可以发送任何你想在显示之前修改对话框的外观的消息。随在本通知之后的是 &lt;strong&gt;TDN_CREATED&lt;/strong&gt; 通知，不过你通常不需要理会它，除非你要执行一些针对特定窗口的初始化操作。这两个通知消息都可以用来执行初始化操作，只不过在导航页面发生时不会发送 &lt;strong&gt;TDN_CREATED&lt;/strong&gt; 而只发送 &lt;strong&gt;TDN_DIALOG_CONSTRUCTED&lt;/strong&gt;。下一节里会讨论导航。&lt;br&gt;&lt;br&gt;毫无惊奇可言，&lt;strong&gt;TDN_BUTTON_CLICKED&lt;/strong&gt; 通知表示一个按钮被点击过了。这既包括公用按钮也包括自定义按钮。这一通知还会在通过点击右上角的 X 或者按 Esc 键以取消对话框的情况下发出，不过这个功能仅在创建之前指定了 &lt;strong&gt;TDF_ALLOW_DIALOG_CANCELLATION&lt;/strong&gt; 标志才有效。&lt;strong&gt;WPARAM &lt;/strong&gt;中为指示哪个按钮被点击了的按钮标识符。在前文里，我已经讨论过了按钮和按钮标识符。回调函数如果对此通知返回 &lt;strong&gt;FALSE&lt;/strong&gt; 则会使对话框关闭，返回 &lt;strong&gt;TRUE&lt;/strong&gt; 则会组织对话框被关闭。&lt;br&gt;&lt;br&gt;&lt;strong&gt;TDN_RADIO_BUTTON_CLICKED&lt;/strong&gt; 通知表示有一个单选按钮被点击了。&lt;strong&gt;WPARAM &lt;/strong&gt;中为指示哪个单选按钮被点击了的按钮标识符。。回调函数对此通知的返回值会被忽略掉。&lt;br&gt;&lt;br&gt;&lt;strong&gt;TDN_HELP&lt;/strong&gt; 通知表示用户在键盘上按下了 F1（帮助）键。试着提供些帮助吧。&lt;br&gt;&lt;br&gt;&lt;strong&gt;TDN_VERIFICATION_CLICKED&lt;/strong&gt; 通知表示确认复选框的状态发生了改变。&lt;strong&gt;WPARAM &lt;/strong&gt;为 &lt;strong&gt;FALSE &lt;/strong&gt;则为未选中状态，为 &lt;strong&gt;TRUE &lt;/strong&gt;则为选中状态。&lt;br&gt;&lt;br&gt;&lt;strong&gt;TDN_EXPANDO_BUTTON_CLICKED&lt;/strong&gt; 通知表示展开或者收缩“扩充信息”区域的控件被点击了。&lt;strong&gt;WPARAM &lt;/strong&gt;为 &lt;strong&gt;FALSE &lt;/strong&gt;则为收缩状态，为 &lt;strong&gt;TRUE &lt;/strong&gt;则为展开状态。&lt;br&gt;&lt;br&gt;&lt;strong&gt;TDN_HYPERLINK_CLICKED&lt;/strong&gt; 通知表示任务对话框中的某个文本域内的超链接被点击了。只有“内容”、“扩充信息”和“脚注”文本标题内才支持超链接，而且需要指定 &lt;strong&gt;TDF_ENABLE_HYPERLINKS&lt;/strong&gt; 标志。超链接可以使用 HTML 中的 &lt;strong&gt;A&lt;/strong&gt;（nchor）元素来定义，如下所示：&lt;br&gt;&lt;br&gt;&lt;font face="Courier New"&gt;&amp;lt;a href=&amp;quot;uri&amp;quot;&amp;gt;text&amp;lt;/a&amp;gt;&lt;br&gt;&lt;br&gt;&lt;/font&gt;仅支持双引号，所以在必要时你可能需要转义。链接可以出现在大一些的串中间。在 href 属性中指定的值会由 &lt;strong&gt;LPARAM &lt;/strong&gt;提供，你可以用它来做任何你喜欢做的事，比如打开一个网页。任务对话框没有聪明到可以给它提供任何缺省的行为。本文下载里的 MainWindow 类演示了超链接的使用。&lt;br&gt;&lt;br&gt;&lt;strong&gt;TDN_TIMER&lt;/strong&gt; 通知用以提供一个定时器，你的对话框可以用它做各种事情，从更新对话框控件到在一段时间后自动关闭对话框。如果指定了 &lt;strong&gt;TDF_CALLBACK_TIMER&lt;/strong&gt; 标志，则定时器通知会大约每 200 毫秒发送一次。本文下载里的 &lt;strong&gt;Timer 示例&lt;/strong&gt;演示了定时器功能的使用：&lt;br&gt;&lt;br&gt;&lt;img height=149 src="http://tkfiles.storage.msn.com/x1pQ9fDJCqr-278bj324ojFY1bdJY2YfUBGP0yfBSfqfIdHmut5Vm-K4LmW6ZmJ3eT_JQTiwgi6r1o-1_AGg8w2W92gfqHsOkMozAlnUq6CWsM" width=366&gt; &lt;br&gt;&lt;br&gt;&lt;strong&gt;消息&lt;/strong&gt;&lt;br&gt;&lt;br&gt;任务对话框会响应一些消息以便你用来按需模拟特定的动作。 &lt;br&gt;&lt;br&gt;&lt;strong&gt;TDM_CLICK_BUTTON&lt;/strong&gt; 和 &lt;strong&gt;TDM_CLICK_RADIO_BUTTON&lt;/strong&gt; 消息可以分别模拟按钮或者单选按钮的点击。&lt;strong&gt;WPARAM &lt;/strong&gt;中指定按钮标识符，&lt;strong&gt;LPARAM &lt;/strong&gt;参数会被忽略。&lt;br&gt;&lt;br&gt;&lt;strong&gt;TDM_CLICK_VERIFICATION&lt;/strong&gt; 消息模拟确认复选框的点击。&lt;strong&gt;WPARAM &lt;/strong&gt;里指示它应该是选中的（&lt;strong&gt;TRUE&lt;/strong&gt;）还是未选中的（&lt;strong&gt;FALSE&lt;/strong&gt;）。&lt;strong&gt;LPARAM &lt;/strong&gt;用以指示它是否应该接受焦点，&lt;strong&gt;TRUE&lt;/strong&gt; 为是 &lt;strong&gt;FALSE&lt;/strong&gt; 为否。&lt;br&gt;&lt;br&gt;&lt;strong&gt;TDM_ENABLE_BUTTON&lt;/strong&gt; 和 &lt;strong&gt;TDM_ENABLE_RADIO_BUTTON&lt;/strong&gt; 消息分别可以启用或者禁用一个按钮或者单选按钮。&lt;strong&gt;WPARAM &lt;/strong&gt;指定按钮标识符，&lt;strong&gt;LPARAM &lt;/strong&gt;指示是要启用（&lt;strong&gt;TRUE&lt;/strong&gt;）还是禁用（&lt;strong&gt;FALSE&lt;/strong&gt;）。&lt;br&gt;&lt;br&gt;我在前一节里避免提及的最后一个通知是 &lt;strong&gt;TDN_NAVIGATED&lt;/strong&gt;，这个通知在本文写作之时还没有任何文档。它直接关系到 &lt;strong&gt;TDM_NAVIGATE_PAGE&lt;/strong&gt; 消息，因此我认为应该在这儿讨论它。自从 &lt;strong&gt;TDM_NAVIGATE_PAGE&lt;/strong&gt; 消息出现以来，它也是毫无任何文档。在调试器中经过几分钟的单步追踪汇编（当然有操作系统符号的配合）之后，我就可以把它说出来了。这些消息允许你转换，或者说是导航，从一个任务对话框到另一个，就像一个只能前进的向导一样。“新”的任务对话框有效地接管了前一个对话框的窗口，因此对于新的任务对话框来说，并不会有一个新的窗口创建出来。当我在反汇编器里跟踪到 &lt;strong&gt;comctl32.dll&lt;/strong&gt; 的代码内部时，我发现 &lt;strong&gt;TDM_NAVIGATE_PAGE&lt;/strong&gt; 的消息处理器并不读取 &lt;strong&gt;WPARAM&lt;/strong&gt;，而是预期 &lt;strong&gt;LPARAM &lt;/strong&gt;中有一个指向描述了将要导航到的下一个对话框的外观和行为的 &lt;strong&gt;TASKDIALOGCONFIG &lt;/strong&gt;结构。然后 &lt;strong&gt;TDN_NAVIGATED &lt;/strong&gt;通知被发送到新任务对话框的回调函数里。本文的 &lt;strong&gt;Error 示例&lt;/strong&gt;演示了这个功能。&lt;br&gt;&lt;br&gt;&lt;strong&gt;C++ 的援助&lt;/strong&gt;&lt;br&gt;&lt;br&gt;任务对话框确实很强大，但是带来了使用性上的损失。尽管只暴露了两个函数，但任务对话框的 C API 却相当的复杂。为了解决这个问题，我使用自然 C++ 代码（译者注：自然 C++ 代码，原文为 native C++ code，作者可能是相对于托管 C++ 代码而言）写了 &lt;strong&gt;TaskDialog &lt;/strong&gt;C++ 类来简化对任务对话框的使用。&lt;strong&gt;TaskDialog &lt;/strong&gt;类继承于 ATL 的 &lt;strong&gt;CWindow &lt;/strong&gt;类，封装了决大多数（如果不是全部的话）的任务对话框的功能，简化掉了有关准备 &lt;strong&gt;TASKDIALOGCONFIG &lt;/strong&gt;结构、发送消息以及响应通知的许多复杂性。本文下载中的所有示例都使用了我的 &lt;strong&gt;TaskDialog &lt;/strong&gt;类，所以你应该算是有了充足的可靠的例子。&lt;br&gt;&lt;br&gt;下面是包含在本文的下载中的一个示例任务对话框的源代码：&lt;br&gt;&lt;br&gt;&lt;font face="Courier New"&gt;class TimerDialog : public Kerr::TaskDialog&lt;br&gt;{&lt;br&gt;public:&lt;br&gt; &lt;br&gt;    TimerDialog() :&lt;br&gt;        m_reset(false)&lt;br&gt;    {&lt;br&gt;        SetWindowTitle(L&amp;quot;Timer Sample&amp;quot;);&lt;br&gt;        SetMainInstruction(L&amp;quot;Time elapsed: 0 seconds&amp;quot;);&lt;br&gt;        AddButton(L&amp;quot;Reset&amp;quot;, Button_Reset);&lt;br&gt; &lt;br&gt;        m_config.dwFlags |= TDF_ALLOW_DIALOG_CANCELLATION | &lt;br&gt;                            TDF_CALLBACK_TIMER;&lt;br&gt;    }&lt;br&gt; &lt;br&gt;private:&lt;br&gt; &lt;br&gt;    enum&lt;br&gt;    {&lt;br&gt;        Button_Reset = 101&lt;br&gt;    };&lt;br&gt; &lt;br&gt;    virtual void OnTimer(DWORD milliseconds, &lt;br&gt;                         bool&amp;amp;reset)&lt;br&gt;    {&lt;br&gt;        CString text;&lt;br&gt; &lt;br&gt;        text.Format(L&amp;quot;Time elapsed: %.2f seconds&amp;quot;, &lt;br&gt;                    static_cast&amp;lt;double&amp;gt;(milliseconds) / 1000);&lt;br&gt; &lt;br&gt;        SetMainInstruction(text.GetString());&lt;br&gt; &lt;br&gt;        reset = m_reset;&lt;br&gt;        m_reset = false;&lt;br&gt;    }&lt;br&gt; &lt;br&gt;    virtual void OnButtonClicked(int buttonId, &lt;br&gt;                                 bool&amp;amp;closeDialog)&lt;br&gt;    {&lt;br&gt;        switch (buttonId)&lt;br&gt;        {&lt;br&gt;            case Button_Reset:&lt;br&gt;            {&lt;br&gt;                m_reset = true;&lt;br&gt;                break;&lt;br&gt;            }&lt;br&gt;            case IDCANCEL:&lt;br&gt;            {&lt;br&gt;                closeDialog = true;&lt;br&gt;                break;&lt;br&gt;            }&lt;br&gt;            default:&lt;br&gt;            {&lt;br&gt;                ASSERT(false);&lt;br&gt;            }&lt;br&gt;        }&lt;br&gt;    }&lt;br&gt; &lt;br&gt;    bool m_reset;&lt;br&gt;};&lt;br&gt;&lt;br&gt;&lt;/font&gt;如你所见，它为任务对话框提供了一个简单的、面向对象的模型。你不需要直接填充各种结构或者管理按钮定义的数组。&lt;strong&gt;TaskDialog &lt;/strong&gt;基类考虑到了所有的细节。提供了设置（以及更新）不同的文本标题和图标的方法，也提供了添加按钮以及发送各种消息的方法。最后，还提供了响应通知的虚方法。&lt;br&gt;&lt;br&gt;使用如上定义的任务对话框是再简单不过了：&lt;br&gt;&lt;br&gt;&lt;font face="Courier New"&gt;TimerDialog dialog;&lt;br&gt;Dialog.DoModal();&lt;br&gt;&lt;br&gt;&lt;/font&gt;一旦 &lt;strong&gt;DoModal &lt;/strong&gt;方法返回，你就可以使用 &lt;strong&gt;GetSelectedButtonId&lt;/strong&gt;、&lt;strong&gt;GetSelectedRadioButtonId &lt;/strong&gt;和 &lt;strong&gt;VerificiationChecked &lt;/strong&gt;方法来获取用户选定的各个按钮。&lt;br&gt;&lt;br&gt;为了给你一个由 &lt;strong&gt;TaskDialog &lt;/strong&gt;类隐藏起来的复杂性的概念性认识，可以看一下 &lt;strong&gt;SetWindowTitle &lt;/strong&gt;方法的实现：&lt;br&gt;&lt;br&gt;&lt;font face="Courier New"&gt;void Kerr::TaskDialog::SetWindowTitle(ATL::_U_STRINGorID text)&lt;br&gt;{&lt;br&gt;    if (0 == m_hWnd)&lt;br&gt;    {&lt;br&gt;        m_config.pszWindowTitle = text.m_lpstr;&lt;br&gt;    }&lt;br&gt;    else if (IS_INTRESOURCE(text.m_lpstr))&lt;br&gt;    {&lt;br&gt;        CString string;&lt;br&gt; &lt;br&gt;        // Since we know that text is actually a resource Id we can ignore the pointer truncation warning.&lt;br&gt;        #pragma warning(push)&lt;br&gt;        #pragma warning(disable: 4311)&lt;br&gt; &lt;br&gt;        VERIFY(string.LoadString(m_config.hInstance,&lt;br&gt;                                 reinterpret_cast&amp;lt;UINT&amp;gt;(text.m_lpstr)));&lt;br&gt; &lt;br&gt;        #pragma warning(pop)&lt;br&gt; &lt;br&gt;        VERIFY(SetWindowText(string));&lt;br&gt;    }&lt;br&gt;    else&lt;br&gt;    {&lt;br&gt;        VERIFY(SetWindowText(text.m_lpstr));&lt;br&gt;    }&lt;br&gt;}&lt;br&gt;&lt;br&gt;&lt;/font&gt;ATL 的 &lt;strong&gt;_U_STRINGorID&lt;/strong&gt; 类使得你可以轻易地指定一个字符串指针或者资源标识符。如果任务对话框还没有创建，则简单地更新内部的 &lt;strong&gt;TASKDIALOGCONFIG &lt;/strong&gt;结构。否则，就会使用 &lt;strong&gt;SetWindowText &lt;/strong&gt;函来更新窗口标题。使用这种方法，开发人员就可以在任意位置调用 &lt;strong&gt;SetWindowTitle &lt;/strong&gt;方法而无需根据窗口标题指定的时间或者数据来提供不同的代码。&lt;br&gt;&lt;br&gt;&lt;strong&gt;示例&lt;/strong&gt;&lt;br&gt;&lt;br&gt;&lt;a href="http://www.kennyandkarin.com/kenny/vista/taskdialogsamplecpp.zip"&gt;下载区&lt;/a&gt;中提供的本文的示例生动地演示了文章中讲到的所有特性。&lt;br&gt;&lt;br&gt;&lt;img height=561 src="http://tkfiles.storage.msn.com/x1pQ9fDJCqr-278bj324ojFY77imG690m7bFrlutHBAx6TSdX8i2A8Wp8oWDSJMUTZvr_YSvhWEHeb9s-WTa-arlTgkb8yi40apePibr348fL6jU8k_r9bxpw" width=472&gt;&lt;br&gt;&lt;br&gt;这篇文章的终稿比我预计的要长一些。Windows Vista 任务对话框 API 实在是提供了太多的功能，简直使我无法适时地完成本文。这也是我所知道的关于任务对话框的唯一的完整的文档。我希望它能使许多的读者受益。 
&lt;p&gt;我原本计划使用托管代码来介绍任务对话框，可是 &lt;a href="http://www.danielmoth.com/Blog/"&gt;Daniel Moth&lt;/a&gt; 已经使用 C# 出色地完成了介绍任务对话框的工作。他还创建了一个 &lt;a href="http://www.microsoft.com/uk/msdn/events/nuggets.aspx"&gt;webcast&lt;/a&gt;，演示了许多创建任务对话框的方案，其中的 Task Dialog Designer 来源于我的 &lt;a href="http://msdn.microsoft.com/msdnmag/issues/06/07/BeyondWinFX/"&gt;MSDN 杂志&lt;/a&gt;文章。我必须指出的是，该 webcast 中不正确地把 &lt;strong&gt;Kerr.Vista&lt;/strong&gt; 配件说成是一个 COM DLL，而其实它仅仅是一个简单使用 C++/CLI  写就的 .NET 配件。  
&lt;p&gt;&lt;br&gt;阅读第三部分：&lt;a href="http://weblogs.asp.net/kennykerr/archive/2006/08/10/Windows-Vista-for-Developers-_1320_-Part-3-_1320_-The-Desktop-Window-Manager.aspx"&gt;桌面窗口管理器&lt;/a&gt;&lt;br&gt;
&lt;p&gt;原文地址：&lt;a href="http://weblogs.asp.net/kennykerr/archive/2006/07/18/Windows-Vista-for-Developers-_1320_-Part-2-_1320_-Task-Dialogs-in-Depth.aspx"&gt;http://weblogs.asp.net/kennykerr/archive/2006/07/18/Windows-Vista-for-Developers-_1320_-Part-2-_1320_-Task-Dialogs-in-Depth.aspx&lt;/a&gt; 
&lt;p&gt; &lt;/div&gt;&lt;/div&gt;&lt;img src="http://c.services.spaces.live.com/CollectionWebService/c.gif?cid=3848887354281525204&amp;page=RSS%3a+%e8%af%91%ef%bc%9aWindows+Vista+for+Developers+-+%e7%ac%ac%e4%ba%8c%e9%83%a8%e5%88%86+%e6%b7%b1%e5%85%a5%e4%bb%bb%e5%8a%a1%e5%af%b9%e8%af%9d%e6%a1%86&amp;referrer=" width="1px" height="1px" border="0" alt=""&gt;&lt;img style="position:absolute" alt="" width="0px" height="0px" src="http://c.live.com/c.gif?NC=31263&amp;amp;NA=1149&amp;amp;PI=73329&amp;amp;RF=&amp;amp;DI=3919&amp;amp;PS=85545&amp;amp;TP=sluttery.spaces.live.com&amp;amp;GT1=sluttery"&gt;</description><comments>http://sluttery.spaces.live.com/Blog/cns!3569FEA80C717FD4!3724.entry#comment</comments><guid isPermaLink="true">http://sluttery.spaces.live.com/Blog/cns!3569FEA80C717FD4!3724.entry</guid><pubDate>Thu, 08 Mar 2007 13:34:23 GMT</pubDate><slash:comments>0</slash:comments><msn:type>blogentry</msn:type><live:type>blogentry</live:type><live:typelabel>Blog entry</live:typelabel><wfw:commentRss>http://sluttery.spaces.live.com/blog/cns!3569FEA80C717FD4!3724/comments/feed.rss</wfw:commentRss><wfw:comment>http://sluttery.spaces.live.com/Blog/cns!3569FEA80C717FD4!3724.entry#comment</wfw:comment><dcterms:modified>2007-03-08T14:44:17Z</dcterms:modified></item><item><title>译：Windows Vista for Developers - 第一部分 Aero 向导</title><link>http://sluttery.spaces.live.com/Blog/cns!3569FEA80C717FD4!3723.entry</link><description>&lt;div&gt;
&lt;div&gt;
&lt;p&gt;Aero 向导体现了向导界面自从在 Windows 95 操作系统家族中首次推广以来的演变。它们为普通的向导界面提供了崭新的外观，而且被设计为可以为用户提供更专注的体验。在本 &lt;a href="http://weblogs.asp.net/kennykerr/archive/2006/07/12/Windows-Vista-for-Developers-_1320_-A-New-Series.aspx"&gt;Windows Vista for Developers&lt;/a&gt; 系列的这第一个部分里，我将向你展示如何你就可以使用少量的代码把一个简单的向导转换为一个 Aero 向导。 
&lt;p&gt;&lt;strong&gt;属性表&lt;/strong&gt; 
&lt;p&gt;扼要地讲，向导其实只是属性表的一个变体，属性表由 &lt;strong&gt;PROPSHEETHEADER&lt;/strong&gt; 结构来定义而由 &lt;strong&gt;PropertySheet&lt;/strong&gt; 函数实现。ATL 提供了 &lt;strong&gt;CPropertySheetImpl&lt;/strong&gt; 类模板来生成使用 &lt;strong&gt;PropertySheet&lt;/strong&gt; 函数牵涉到的许多代码。我们从一个简单的属性表开始，然后再看怎样把它改变为一个 Aero 向导： 
&lt;p&gt;&lt;font face="Courier New"&gt;class SampleWizard : &lt;br&gt;    public CPropertySheetImpl&amp;lt;SampleWizard&amp;gt;&lt;br&gt;{&lt;br&gt;public:&lt;br&gt; &lt;br&gt;    BEGIN_MSG_MAP(SampleWizard)&lt;br&gt;        CHAIN_MSG_MAP(__super)&lt;br&gt;    END_MSG_MAP()&lt;br&gt; &lt;br&gt;    SampleWizard() :&lt;br&gt;        CPropertySheetImpl&amp;lt;SampleWizard&amp;gt;(IDS_TITLE)&lt;br&gt;    {&lt;br&gt;        VERIFY(AddPage(m_page));&lt;br&gt;    }&lt;br&gt; &lt;br&gt;private:&lt;br&gt; &lt;br&gt;    SamplePage m_page;&lt;br&gt; &lt;br&gt;};&lt;/font&gt; 
&lt;p&gt;&lt;strong&gt;SampleWizard&lt;/strong&gt; 类派生于上述的 &lt;strong&gt;CPropertySheetImpl&lt;/strong&gt; 类模板，该类模板提供了属性表的大部分功能。消息映射简单地把消息导向基类。构造函数调用了基类的构造函数以设置向导的标题，并使用继承自 &lt;strong&gt;CPropertySheetImpl&lt;/strong&gt; 的 &lt;strong&gt;AddPage&lt;/strong&gt; 方法向向导中添加了一个页面. &lt;strong&gt;SamplePage&lt;/strong&gt; 类具有如下定义： 
&lt;p&gt;&lt;font face="Courier New"&gt;class SamplePage :&lt;br&gt;    public CPropertyPageImpl&amp;lt;SamplePage&amp;gt;&lt;br&gt;{&lt;br&gt;public:&lt;br&gt; &lt;br&gt;    BEGIN_MSG_MAP(SamplePage)&lt;br&gt;        CHAIN_MSG_MAP(__super)&lt;br&gt;    END_MSG_MAP()&lt;br&gt; &lt;br&gt;    enum { IDD = IDD_SAMPLE_PAGE };&lt;br&gt; &lt;br&gt;};&lt;/font&gt; 
&lt;p&gt;&lt;strong&gt;SamplePage&lt;/strong&gt; 类派生于 &lt;strong&gt;CPropertyPageImpl&lt;/strong&gt; 类模板，该类模板提供了属性页的大部分功能。它也有一个简单的消息映射把消息都导向基类。一个 &lt;strong&gt;enum&lt;/strong&gt; 定义了基类中所期望的 &lt;strong&gt;IDD&lt;/strong&gt; 常量，用以标识属性页用到的对话框资源。 
&lt;p&gt;现在，你就可以用下列简单代码创建并显示一个模态属性表了： 
&lt;p&gt;&lt;font face="Courier New"&gt;SampleWizard sampleWizard;&lt;br&gt;sampleWizard.DoModal();&lt;/font&gt; 
&lt;p&gt;第一行创建了 &lt;strong&gt;SampleWizard&lt;/strong&gt; 对象，其中，各个基类会负责构造调用 &lt;strong&gt;PropertySheet&lt;/strong&gt; 函数所需的结构。SampleWizard 继承自 &lt;strong&gt;CPropertySheetImpl&lt;/strong&gt; 类模板的 &lt;strong&gt;DoModal&lt;/strong&gt; 方法则负责调用 &lt;strong&gt;PropertySheet&lt;/strong&gt; 函数。其结果就是一个简单的属性表： 
&lt;p&gt;&lt;img style="width:354px;height:290px" height=290 src="http://tkfiles.storage.msn.com/x1pQ9fDJCqr-278bj324ojFY4V7Vf2vBM71Hhmp4-A3DtWg3n0mJ8_0tf6MIvxTetc85NLas3ksxFW5P6SBBd8tXiy6G2ALC7fVlyygaRpq45d0w2YPtZ5Y_A" width=354&gt;  
&lt;p&gt;&lt;strong&gt;经典向导&lt;/strong&gt; 
&lt;p&gt;要把属性表变为一个经典的向导，你所需的全部工作就是把下列语句添加到 &lt;strong&gt;SampleWizard&lt;/strong&gt; 的构造函数中： 
&lt;p&gt;&lt;font face="Courier New"&gt;m_psh.dwFlags |= PSH_WIZARD97;&lt;/font&gt; 
&lt;p&gt;它所做的就是把 &lt;strong&gt;PSH_WIZARD97&lt;/strong&gt; 标志合并到已经在 &lt;strong&gt;PROPSHEETHEADER&lt;/strong&gt; 结构里已经设置过的标志中。由于向导提供了一个额外的顶头区域，所以还要更新 &lt;strong&gt;SamplePage&lt;/strong&gt; 类来指定此页的标头文字： 
&lt;p&gt;&lt;font face="Courier New"&gt;class SamplePage :&lt;br&gt;    public CPropertyPageImpl&amp;lt;SamplePage&amp;gt;&lt;br&gt;{&lt;br&gt;public:&lt;br&gt; &lt;br&gt;    BEGIN_MSG_MAP(SamplePage)&lt;br&gt;        CHAIN_MSG_MAP(__super)&lt;br&gt;    END_MSG_MAP()&lt;br&gt; &lt;br&gt;    enum { IDD = IDD_BLANK_PAGE };&lt;br&gt; &lt;br&gt;    SamplePage()&lt;br&gt;    {&lt;br&gt;        VERIFY(m_title.LoadString(IDS_PAGE_TITLE));&lt;br&gt;        SetHeaderTitle(m_title);&lt;br&gt;    }&lt;br&gt; &lt;br&gt;private:&lt;br&gt; &lt;br&gt;    CString m_title; 
&lt;p&gt;};&lt;/font&gt; 
&lt;p&gt;
&lt;p&gt;&lt;strong&gt;SamplePage&lt;/strong&gt; 的构造函数使用继承于 &lt;strong&gt;CPropertyPageImpl&lt;/strong&gt; 的 &lt;strong&gt;SetHeaderTitle&lt;/strong&gt; 方法来设置页面的标头标题。要记住的是在页面的 &lt;strong&gt;PROPSHEETPAGE&lt;/strong&gt; 结构里存储的是一个字符串指针，因此，字符串的生命期必须要超出构造函数。 
&lt;p&gt;经过这些适当的小改动，结果发生了相当引人注目的不同： 
&lt;p&gt;&lt;img height=322 src="http://tkfiles.storage.msn.com/x1pQ9fDJCqr-278bj324ojFY5Kovoyc9pgXAflrmuNUeKRJTEWXAh64fEs6QQwYlhpyHS6RuZ0rDKYplJH2Xefhw7Fz_R2pBDxgCoZhkdQAOpMKlSK2z7Bw7g" width=451&gt; 
&lt;p&gt;正如你可能注意到的，先前出现在标签上的文字已经把窗口标题替换了。 
&lt;p&gt;&lt;strong&gt;Aero 向导&lt;/strong&gt; 
&lt;p&gt;现在，将 &lt;strong&gt;SampleWizard&lt;/strong&gt; 构造函数中的 &lt;strong&gt;PSH_WIZARD97&lt;/strong&gt; 替换为 &lt;strong&gt;PSH_AEROWIZARD&lt;/strong&gt;，然后你就得到了新式的 Aero 向导界面： 
&lt;p&gt;&lt;img height=351 src="http://tkfiles.storage.msn.com/x1pQ9fDJCqr-278bj324ojFY1J1Ew-Wy6OouiSaFNYeOMRh3ICIXzkpKPTAOMIx2MWsuwvlUSomK4NdWx7uxrBg9EaYIck5WjspmjQgnH-feSiLQXmyEUVE0A" width=339&gt;  
&lt;p&gt;你应该注意到窗口标题又变了回来而原来出现在属性表标签上的对话框标题已经不再使用了。 
&lt;p&gt;&lt;strong&gt;新消息&lt;/strong&gt; 
&lt;p&gt;Aero 向导支持几个新的消息，用以更好地控制由向导提供的标准控件。 
&lt;p&gt;&lt;strong&gt;PSM_SHOWWIZBUTTONS&lt;/strong&gt; 消息显示或者隐藏修饰向导的任意标准按钮。&lt;strong&gt;PropSheet_ShowWizButtons&lt;/strong&gt; 宏可以简化此消息的发送。使用它并不是特别的自然，不过一旦掌握了它，你就不该再有任何问题了。尽管只是一个宏，不过把它想象为一个如下定义的函数更有用（宏易于误解）： 
&lt;p&gt;&lt;font face="Courier New"&gt;void PropSheet_ShowWizButtons(HWND handle,&lt;br&gt;                              DWORD buttons,&lt;br&gt;                              DWORD mask);&lt;/font&gt; 
&lt;p&gt;&lt;strong&gt;handle&lt;/strong&gt; 参数标识了向导窗口，&lt;strong&gt;buttons&lt;/strong&gt; 参数指示要显示哪些按钮，而 &lt;strong&gt;mask&lt;/strong&gt; 参数指示要操作哪些按钮。如果一个按钮标志同时出现于 &lt;strong&gt;buttons&lt;/strong&gt; 和 &lt;strong&gt;masks&lt;/strong&gt; 参数中则会被显示。如果一个按钮仅出现于 &lt;strong&gt;mask&lt;/strong&gt; 参数中则会被隐藏。共有下列按钮标志可以使用： 
&lt;p&gt;&lt;font face="Courier New"&gt;PSWIZB_BACK&lt;br&gt;PSWIZB_NEXT&lt;br&gt;PSWIZB_FINISH&lt;br&gt;PSWIZB_CANCEL&lt;/font&gt; 
&lt;p&gt;例如，下面的调用将显示 Next 按钮而隐藏 Back 按钮： 
&lt;p&gt;&lt;font face="Courier New"&gt;PropSheet_ShowWizButtons(handle,&lt;br&gt;                         PSWIZB_NEXT,&lt;br&gt;                         PSWIZB_BACK | PSWIZB_NEXT);&lt;/font&gt; 
&lt;p&gt;&lt;strong&gt;PSM_ENABLEWIZBUTTONS&lt;/strong&gt; 消息启用或者禁用任意标准按钮。&lt;strong&gt;PropSheet_EnableWizButtons&lt;/strong&gt; 宏简化了此消息的发送： 
&lt;p&gt;&lt;font face="Courier New"&gt;void PropSheet_EnableWizButtons(HWND handle,&lt;br&gt;                                DWORD buttons,&lt;br&gt;                                DWORD mask);&lt;/font&gt; 
&lt;p&gt;在如何标识按钮上此消息与 &lt;strong&gt;PSM_SHOWWIZBUTTONS&lt;/strong&gt; 的工作方式是一致的。&lt;strong&gt;buttons&lt;/strong&gt; 参数指示那些按钮要启用或者禁用而 &lt;strong&gt;mask&lt;/strong&gt; 参数指示要操作哪些按钮。例如，下列调用将启用 Next 按钮并禁用 Back 按钮。 
&lt;p&gt;&lt;font face="Courier New"&gt;PropSheet_EnableWizButtons(handle,&lt;br&gt;                           PSWIZB_NEXT,&lt;br&gt;                           PSWIZB_BACK | PSWIZB_NEXT);&lt;/font&gt; 
&lt;p&gt;&lt;strong&gt;PSM_SETBUTTONTEXT&lt;/strong&gt; 消息可以修改 Next、Finish 以及 Cancel 按钮的文字。&lt;strong&gt;PropSheet_SetButtonText&lt;/strong&gt; 宏简化了此消息的发送： 
&lt;p&gt;&lt;font face="Courier New"&gt;void PropSheet_SetButtonText(HWND handle,&lt;br&gt;                             DWORD button,&lt;br&gt;                             PCWSTR text);&lt;/font&gt; 
&lt;p&gt;&lt;strong&gt;示例&lt;/strong&gt; 
&lt;p&gt;为了让你能简单地试验 Aero 向导提供的各种选项，我创建了一个简单的向导工程你可以&lt;a href="http://www.kennyandkarin.com/kenny/vista/aerowizardsample.zip"&gt;下载&lt;/a&gt;并用它进行实践： 
&lt;p&gt; &lt;img height=385 src="http://tkfiles.storage.msn.com/x1pQ9fDJCqr-278bj324ojFY3nEj5TRDNySuiDeCaa-7J6TCNDF7Tc9alJE4bLE_nkdQkZt5aUmlqAxcraKOSfnVhDJj5qNeB2ab5ZtT6bhHiD5pJ_EiMPfLw" width=321&gt; 
&lt;p&gt;阅读第二部分：&lt;a href="http://weblogs.asp.net/kennykerr/archive/2006/07/18/Windows-Vista-for-Developers-_1320_-Part-2-_1320_-Task-Dialogs-in-Depth.aspx"&gt;深入任务对话框&lt;/a&gt; 
&lt;p&gt;原文地址：&lt;a href="http://weblogs.asp.net/kennykerr/archive/2006/07/12/Windows-Vista-for-Developers-_1320_-Part-1-_1320_-Aero-Wizards.aspx"&gt;http://weblogs.asp.net/kennykerr/archive/2006/07/12/Windows-Vista-for-Developers-_1320_-Part-1-_1320_-Aero-Wizards.aspx&lt;/a&gt; 
&lt;p&gt; &lt;/div&gt;&lt;/div&gt;&lt;img src="http://c.services.spaces.live.com/CollectionWebService/c.gif?cid=3848887354281525204&amp;page=RSS%3a+%e8%af%91%ef%bc%9aWindows+Vista+for+Developers+-+%e7%ac%ac%e4%b8%80%e9%83%a8%e5%88%86+Aero+%e5%90%91%e5%af%bc&amp;referrer=" width="1px" height="1px" border="0" alt=""&gt;&lt;img style="position:absolute" alt="" width="0px" height="0px" src="http://c.live.com/c.gif?NC=31263&amp;amp;NA=1149&amp;amp;PI=73329&amp;amp;RF=&amp;amp;DI=3919&amp;amp;PS=85545&amp;amp;TP=sluttery.spaces.live.com&amp;amp;GT1=sluttery"&gt;</description><comments>http://sluttery.spaces.live.com/Blog/cns!3569FEA80C717FD4!3723.entry#comment</comments><guid isPermaLink="true">http://sluttery.spaces.live.com/Blog/cns!3569FEA80C717FD4!3723.entry</guid><pubDate>Wed, 07 Mar 2007 13:26:32 GMT</pubDate><slash:comments>1</slash:comments><msn:type>blogentry</msn:type><live:type>blogentry</live:type><live:typelabel>Blog entry</live:typelabel><wfw:commentRss>http://sluttery.spaces.live.com/blog/cns!3569FEA80C717FD4!3723/comments/feed.rss</wfw:commentRss><wfw:comment>http://sluttery.spaces.live.com/Blog/cns!3569FEA80C717FD4!3723.entry#comment</wfw:comment><dcterms:modified>2007-03-07T13:33:21Z</dcterms:modified></item><item><title>为 Windows Live Messenger 设置自动回复功能</title><link>http://sluttery.spaces.live.com/Blog/cns!3569FEA80C717FD4!3625.entry</link><description>&lt;div&gt;微软为编制 Windows Live Messenger 的插件提供了良好的支持。要使插件可以工作，需要以下三个步骤：&lt;/div&gt;
&lt;div&gt;1、开启 Windows Live Messenger 的插件功能&lt;br&gt;2、编制插件&lt;br&gt;3、注册插件&lt;/div&gt;
&lt;div&gt; &lt;/div&gt;
&lt;div&gt;1、开启 Windows Live Messenger 的插件功能。&lt;br&gt;   这个非常简单，打开注册表编辑器，展开 HKEY_CURRENT_USER\Software\Microsoft\MSNMessenger，查看 AddInFeatureEnabled 键值是否存在，不在则创建之，数据类型为 DWORD。将键值置为 1。然后重新启动 MSN Live Messenger。&lt;/div&gt;
&lt;div&gt; &lt;/div&gt;
&lt;div&gt;2、编制插件&lt;br&gt;   用 C# 或者任意其他的 .NET 语言新建工程，向工程中添加对 MessengerClient.dll 的引用。为了避免总是使用完整规格的名称，你可以添加 using Microsoft.Messenger 语句。&lt;br&gt;   接下来创建一个类，名字随便，此处以 AddIn 代之。该类需要实现一个名为 IMessengerAddIn 的接口。该接口中的 Initialize 方法需要实现，因为我们仅是要实现自动回复，仅需如下这样注册 IncomingTextMessage 事件：&lt;br&gt;   Client.IncomingTextMessage += new EventHandler&amp;lt;IncomingTextMessageEventArgs&amp;gt;(Client_IncomingTextMessage);&lt;br&gt;   然后再实现这个事件处理器：&lt;br&gt;   public void Client_IncomingTextMessage(Object Sender, IncomingTextMessageEventArgs e)&lt;br&gt;   {&lt;br&gt;     User userFrom = e.UserFrom;&lt;br&gt;     Client.SendNudgeMessage(userFrom);&lt;br&gt;     Client.SendTextMessage(&amp;quot;Hey, I m busy. Please leave your message.&amp;quot;, userFrom); &lt;br&gt;   }&lt;br&gt;   编译得到 DLL，DLL 的默认名字为 名字空间.DLL，把它改为 名字空间.类名.DLL。而且，类名要注意大小写。&lt;/div&gt;
&lt;div&gt; &lt;/div&gt;
&lt;div&gt;3、注册插件&lt;br&gt;   打开 Windows Live Messenger，打开选项对话框，左侧列表的最下方就会多出一项“加载项”，选中它，然后点击“添加至 Messenger”按钮，找到你上面生成的 DLL 即可。&lt;/div&gt;
&lt;div&gt; &lt;/div&gt;
&lt;div&gt;4、友情提示&lt;br&gt;   不工作不要找我。&lt;br&gt;&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;本文为意译，原文在 &lt;a href="http://www.codeproject.com/"&gt;&lt;font color="#ffffff"&gt;www.codeproject.com&lt;/font&gt;&lt;/a&gt; 上。&lt;/div&gt;
&lt;div&gt; &lt;/div&gt;&lt;/div&gt;&lt;img src="http://c.services.spaces.live.com/CollectionWebService/c.gif?cid=3848887354281525204&amp;page=RSS%3a+%e4%b8%ba+Windows+Live+Messenger+%e8%ae%be%e7%bd%ae%e8%87%aa%e5%8a%a8%e5%9b%9e%e5%a4%8d%e5%8a%9f%e8%83%bd&amp;referrer=" width="1px" height="1px" border="0" alt=""&gt;&lt;img style="position:absolute" alt="" width="0px" height="0px" src="http://c.live.com/c.gif?NC=31263&amp;amp;NA=1149&amp;amp;PI=73329&amp;amp;RF=&amp;amp;DI=3919&amp;amp;PS=85545&amp;amp;TP=sluttery.spaces.live.com&amp;amp;GT1=sluttery"&gt;</description><comments>http://sluttery.spaces.live.com/Blog/cns!3569FEA80C717FD4!3625.entry#comment</comments><guid isPermaLink="true">http://sluttery.spaces.live.com/Blog/cns!3569FEA80C717FD4!3625.entry</guid><pubDate>Tue, 30 Jan 2007 02:16:30 GMT</pubDate><slash:comments>1</slash:comments><msn:type>blogentry</msn:type><live:type>blogentry</live:type><live:typelabel>Blog entry</live:typelabel><wfw:commentRss>http://sluttery.spaces.live.com/blog/cns!3569FEA80C717FD4!3625/comments/feed.rss</wfw:commentRss><wfw:comment>http://sluttery.spaces.live.com/Blog/cns!3569FEA80C717FD4!3625.entry#comment</wfw:comment><dcterms:modified>2007-01-30T02:16:30Z</dcterms:modified></item><item><title>好玩的浏览器</title><link>http://sluttery.spaces.live.com/Blog/cns!3569FEA80C717FD4!3557.entry</link><description>&lt;div style="border-right:#ff6600 1px solid;border-top:#ff6600 1px solid;background:gray;overflow:scroll;border-left:#ff6600 1px solid;width:100%;border-bottom:#ff6600 1px solid"&gt;&lt;pre&gt;&lt;font face=FIXEDSYS&gt;//
// Integrated browser mode - package up a bunch of data into a COPYDATASTRUCT,
// and send it to the desktop window via SendMessage(WM_COPYDATA).
//
void LaunchInternetExplorerWithoutProcess()
{
#define MAX_IEEVENTNAME 32

    // First piece of data is a wide string version of the command line.
    WCHAR wsz[MAX_IEEVENTNAME] = L&amp;quot;&amp;quot;;
    COPYDATASTRUCT cds = { SW_NORMAL, sizeof(WCHAR), wsz };

    // Second piece of data is the event to fire when the browser window reaches WM_CREATE.
    static DWORD dwNextId = 0;
    TCHAR szEvent[MAX_IEEVENTNAME + 1];
    wsprintf(szEvent, TEXT(&amp;quot;IE-%08X-%08X&amp;quot;), GetCurrentThreadId(), dwNextId++);

    HANDLE hEventReady = CreateEvent(NULL, FALSE, FALSE, szEvent);
    if(hEventReady)
    {
        // Put the (UNICODE) event name at the end of the cds data
        LPWSTR pwszEvent = &amp;amp;wsz[1];
#ifdef UNICODE
        lstrcpy(pwszEvent, szEvent);
#else
        MultiByteToWideChar(CP_ACP, 0, szEvent, -1, pwszEvent, sizeof(szEvent) / sizeof(szEvent[0]));
#endif
        cds.cbData += (lstrlenW(pwszEvent) + 1) * sizeof(WCHAR);

        // Send the message
        HWND hwndDesktop = GetShellWindow();
        int iRet = (int)SendMessage(hwndDesktop, WM_COPYDATA, (WPARAM)hwndDesktop, (LPARAM)&amp;amp;cds);
        if(iRet)
        {
            // Wait for the browser window to hit WM_CREATE.
            // When this happens, all DDE servers will have been registered.
            DWORD dwRet = WaitForSingleObject(hEventReady, 1000 * 10); // 10 seconds
#ifdef ASSERT
            ASSERT(dwRet == WAIT_OBJECT_0);
#endif // ASSERT
        }

        CloseHandle(hEventReady);
    }
#undef MAX_IEEVENTNAME
}
&lt;/font&gt;&lt;/pre&gt;&lt;/div&gt;&lt;img src="http://c.services.spaces.live.com/CollectionWebService/c.gif?cid=3848887354281525204&amp;page=RSS%3a+%e5%a5%bd%e7%8e%a9%e7%9a%84%e6%b5%8f%e8%a7%88%e5%99%a8&amp;referrer=" width="1px" height="1px" border="0" alt=""&gt;&lt;img style="position:absolute" alt="" width="0px" height="0px" src="http://c.live.com/c.gif?NC=31263&amp;amp;NA=1149&amp;amp;PI=73329&amp;amp;RF=&amp;amp;DI=3919&amp;amp;PS=85545&amp;amp;TP=sluttery.spaces.live.com&amp;amp;GT1=sluttery"&gt;</description><comments>http://sluttery.spaces.live.com/Blog/cns!3569FEA80C717FD4!3557.entry#comment</comments><guid isPermaLink="true">http://sluttery.spaces.live.com/Blog/cns!3569FEA80C717FD4!3557.entry</guid><pubDate>Tue, 19 Dec 2006 14:07:42 GMT</pubDate><slash:comments>3</slash:comments><msn:type>blogentry</msn:type><live:type>blogentry</live:type><live:typelabel>Blog entry</live:typelabel><wfw:commentRss>http://sluttery.spaces.live.com/blog/cns!3569FEA80C717FD4!3557/comments/feed.rss</wfw:commentRss><wfw:comment>http://sluttery.spaces.live.com/Blog/cns!3569FEA80C717FD4!3557.entry#comment</wfw:comment><dcterms:modified>2006-12-19T14:07:42Z</dcterms:modified></item><item><title>自己实现对窗口的位置和大小进行改变的功能</title><link>http://sluttery.spaces.live.com/Blog/cns!3569FEA80C717FD4!3531.entry</link><description>&lt;div&gt;在很多情况下，我们需要去掉 Windows 窗口的默认标题栏和边框，但又希望能够像原来一样对窗口进行正常的改变位置或者大小的操作。许多程序员采用响应 WM_LBUTTONDOWN/WM_LBUTTONUP 消息，鼠标拖动时自己绘制拖动框（在系统的“拖动时显示窗口内容”选项关闭的情况下）或者不停调用 MoveWindow()/SetWindowPos() API 对窗口的位置或者大小进行更新（在系统的“拖动时显示窗口内容”选项开启的情况下）。这样做虽然可以达到效果，但是相较老汉下面将要描述的方法，稍微麻烦了一些，而且出于用户体验的完整性考虑，需要自己检测拖动过程中用户是否按下了 Esc 键，或者是正在拖动的窗口突然被其他窗口夺取了焦点等等小概率事件。&lt;/div&gt;
&lt;div&gt; &lt;/div&gt;
&lt;div&gt;老汉介绍的方法，整个拖动操作仍然使用系统自身的功能，所以不存在上述问题。其核心思想就是改变系统在对窗口进行点击测试是返回的值，对系统进行一定程度上的欺骗，使其认为鼠标依旧是点击到了默认的标题栏或者边框，从而执行相应的动作。&lt;/div&gt;
&lt;div&gt; &lt;/div&gt;
&lt;div&gt;下面是示例代码。使用向导生成一个 MFC 对话框应用，假定对话框类的名字为 CMvszDlg，向该类添加对 WM_PAINT 和 WM_NCHITTEST 消息的处理函数（默认在 ClassWizard 中是看不到 WM_NCHITTEST 消息的，需要切换到 ClassWizard 的“Class Info”标签，将“Message filter”选择为“Window”）。然后把下面的代码分别复制到对应的消息处理函数中，编译运行即可看到效果。其中，OnPaint() 的实现是为了演示目的，在真实的应用中，显然可以使用更漂亮的效果。&lt;/div&gt;
&lt;div&gt; &lt;/div&gt;
&lt;div style="border-right:#ff6600 1px solid;border-top:#ff6600 1px solid;background:gray;overflow:scroll;border-left:#ff6600 1px solid;width:100%;border-bottom:#ff6600 1px solid"&gt;&lt;pre&gt;&lt;font face=FIXEDSYS&gt;
#define BORDER_SIZE     4
#define CAPTION_SIZE    24

void CMvszDlg::OnPaint() 
{
    Invalidate();

    CPaintDC dc(this);

    CRect rc;
    GetClientRect(&amp;amp;rc);

    CBrush* pbr = CBrush::FromHandle((HBRUSH)GetStockObject(NULL_BRUSH));

    dc.SelectObject(pbr);
    dc.Rectangle(&amp;amp;rc);

    dc.MoveTo(0, BORDER_SIZE + CAPTION_SIZE);
    dc.LineTo(rc.right, BORDER_SIZE + CAPTION_SIZE);

    dc.MoveTo(0, rc.bottom - (BORDER_SIZE + CAPTION_SIZE));
    dc.LineTo(BORDER_SIZE, rc.bottom - (BORDER_SIZE + CAPTION_SIZE));
    dc.MoveTo(rc.right - BORDER_SIZE, rc.bottom - (BORDER_SIZE + CAPTION_SIZE));
    dc.LineTo(rc.right, rc.bottom - (BORDER_SIZE + CAPTION_SIZE));

    dc.MoveTo(BORDER_SIZE + CAPTION_SIZE, 0);
    dc.LineTo(BORDER_SIZE + CAPTION_SIZE, BORDER_SIZE);
    dc.MoveTo(BORDER_SIZE + CAPTION_SIZE, rc.bottom - BORDER_SIZE);
    dc.LineTo(BORDER_SIZE + CAPTION_SIZE, rc.bottom);

    dc.MoveTo(rc.right - (BORDER_SIZE + CAPTION_SIZE), 0);
    dc.LineTo(rc.right - (BORDER_SIZE + CAPTION_SIZE), BORDER_SIZE);
    dc.MoveTo(rc.right - (BORDER_SIZE + CAPTION_SIZE), rc.bottom - BORDER_SIZE);
    dc.LineTo(rc.right - (BORDER_SIZE + CAPTION_SIZE), rc.bottom);

    rc.DeflateRect(BORDER_SIZE, BORDER_SIZE);
    dc.Rectangle(&amp;amp;rc);
}

UINT CMvszDlg::OnNcHitTest(CPoint point) 
{
    ScreenToClient(&amp;amp;point);

    CRect rc;
    GetClientRect(&amp;amp;rc);

    CRect rcTest = rc;
    rcTest.DeflateRect(BORDER_SIZE, BORDER_SIZE);

    int iBottom = rcTest.bottom;
    rcTest.bottom = rcTest.top + CAPTION_SIZE;
    if(rcTest.PtInRect(point))
        return HTCAPTION;

    rcTest.top = rcTest.bottom;
    rcTest.bottom = iBottom;
    if(rcTest.PtInRect(point))
        return HTCLIENT;

    rcTest.SetRect(0, 0, BORDER_SIZE + CAPTION_SIZE, BORDER_SIZE + CAPTION_SIZE);
    if(rcTest.PtInRect(point))
        return HTTOPLEFT;

    rcTest.SetRect(BORDER_SIZE + CAPTION_SIZE, 0, rc.right - (BORDER_SIZE + CAPTION_SIZE), BORDER_SIZE + CAPTION_SIZE);
    if(rcTest.PtInRect(point))
        return HTTOP;

    rcTest.SetRect(rc.right - (BORDER_SIZE + CAPTION_SIZE), 0, rc.right, BORDER_SIZE + CAPTION_SIZE);
    if(rcTest.PtInRect(point))
        return HTTOPRIGHT;

    rcTest.SetRect(0, BORDER_SIZE + CAPTION_SIZE, BORDER_SIZE, rc.bottom - (BORDER_SIZE + CAPTION_SIZE));
    if(rcTest.PtInRect(point))
        return HTLEFT;

    rcTest.SetRect(rc.right - BORDER_SIZE, BORDER_SIZE + CAPTION_SIZE, rc.right, rc.bottom - (BORDER_SIZE + CAPTION_SIZE));
    if(rcTest.PtInRect(point))
        return HTRIGHT;

    rcTest.SetRect(0, rc.bottom - (BORDER_SIZE + CAPTION_SIZE), BORDER_SIZE + CAPTION_SIZE, rc.bottom);
    if(rcTest.PtInRect(point))
        return HTBOTTOMLEFT;

    rcTest.SetRect(BORDER_SIZE + CAPTION_SIZE, rc.bottom - BORDER_SIZE, rc.right - (BORDER_SIZE + CAPTION_SIZE), rc.bottom);
    if(rcTest.PtInRect(point))
        return HTBOTTOM;

    rcTest.SetRect(rc.right - (BORDER_SIZE + CAPTION_SIZE), rc.bottom - (BORDER_SIZE + CAPTION_SIZE), rc.right, rc.bottom);
    if(rcTest.PtInRect(point))
        return HTBOTTOMRIGHT;

    return HTERROR;
}

&lt;/font&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div&gt; &lt;/div&gt;&lt;img src="http://c.services.spaces.live.com/CollectionWebService/c.gif?cid=3848887354281525204&amp;page=RSS%3a+%e8%87%aa%e5%b7%b1%e5%ae%9e%e7%8e%b0%e5%af%b9%e7%aa%97%e5%8f%a3%e7%9a%84%e4%bd%8d%e7%bd%ae%e5%92%8c%e5%a4%a7%e5%b0%8f%e8%bf%9b%e8%a1%8c%e6%94%b9%e5%8f%98%e7%9a%84%e5%8a%9f%e8%83%bd&amp;referrer=" width="1px" height="1px" border="0" alt=""&gt;&lt;img style="position:absolute" alt="" width="0px" height="0px" src="http://c.live.com/c.gif?NC=31263&amp;amp;NA=1149&amp;amp;PI=73329&amp;amp;RF=&amp;amp;DI=3919&amp;amp;PS=85545&amp;amp;TP=sluttery.spaces.live.com&amp;amp;GT1=sluttery"&gt;</description><comments>http://sluttery.spaces.live.com/Blog/cns!3569FEA80C717FD4!3531.entry#comment</comments><guid isPermaLink="true">http://sluttery.spaces.live.com/Blog/cns!3569FEA80C717FD4!3531.entry</guid><pubDate>Sun, 19 Nov 2006 06:51:51 GMT</pubDate><slash:comments>1</slash:comments><msn:type>blogentry</msn:type><live:type>blogentry</live:type><live:typelabel>Blog entry</live:typelabel><wfw:commentRss>http://sluttery.spaces.live.com/blog/cns!3569FEA80C717FD4!3531/comments/feed.rss</wfw:commentRss><wfw:comment>http://sluttery.spaces.live.com/Blog/cns!3569FEA80C717FD4!3531.entry#comment</wfw:comment><dcterms:modified>2006-11-19T06:51:51Z</dcterms:modified></item><item><title>半透明窗口中的孤岛</title><link>http://sluttery.spaces.live.com/Blog/cns!3569FEA80C717FD4!3439.entry</link><description>&lt;div&gt;几天前在 CSDN 上有人问及一个问题：如何使得一个半透明窗口上，控件所占据的位置是不透明的？&lt;/div&gt;
&lt;div&gt; &lt;/div&gt;
&lt;div&gt;开始有人提到使用 SetLayeredWindowAttributes() API，但考虑到该 API 会将整个窗口设置为一致的透明度，显然不可能满足问题中的要求。所以老汉认为应该使用 UpdateLayeredWindow() API 来完成这一工作，为了不重复输入，把我当时的回复抄录如下：“&lt;/div&gt;
&lt;blockquote dir=ltr&gt;
&lt;div&gt;&lt;font color="#ffcc00"&gt;真正可以使用的 API 是 UpdateLayeredWindow()。此函数可以根据一幅选入到 DC 中去的 32 位位图的每个像素的 Alpha 通道值设置窗口上对应像素的半透明度。其实现在楼主要做得就是，总是生成一幅和窗口大小一样的 32 位位图，把控件占据的区域的位图像素的 Alpha 通道值全部设置为 255，即不透明，而其余地方的像素则可以根据需要设置为适当的半透明，然后再调用本函数即可。需要注意的是，如果窗口的大小可以改变的话，显然每次都需要动态生成此位图，并调用本函数对窗口进行更新。&lt;/font&gt;&lt;/div&gt;&lt;/blockquote&gt;
&lt;div&gt;”。为了验证这一想法，我特意完成了一个类 CWindowUpdater，代码附后。使用者仅需在顶级窗口的 WM_SIZE 消息的响应中调用 CWindowUpdater::Update(hwnd) 即可。&lt;/div&gt;
&lt;div&gt; &lt;/div&gt;
&lt;div&gt;需要说明的是，这种方法虽然在视觉上解决了上述问题，但是带来了另外一个问题。经过 UpdateLayeredWindow() 作用的窗口，其内容会因此而变成一幅静态图像，所以后果会成为，虽然控件是不透明的，但是用户与控件交互（例如鼠标点击，键盘输入等）时原本应该有的视觉反馈（例如按钮成为下压状态）却全部不可见了，这使得这种方法基本上是鸡肋，并没有真正意义上的实用价值。如果有哪位实现了更完美的解决方案，望不吝赐教。不过，它至少在技术上演示了如何设置窗口上每个像素的透明度。&lt;/div&gt;
&lt;div&gt; &lt;/div&gt;
&lt;div style="border-right:#ff6600 1px solid;border-top:#ff6600 1px solid;background:gray;overflow:scroll;border-left:#ff6600 1px solid;width:100%;border-bottom:#ff6600 1px solid"&gt;&lt;pre&gt;&lt;font face=FIXEDSYS&gt;
class CWindowUpdater
{
typedef BOOL (WINAPI *fnUpdateLayeredWindow)(HWND hWnd, 
        HDC hdcDst, LPPOINT pptDst, LPSIZE psize, 
        HDC hdcSrc, LPPOINT pptSrc, COLORREF crKey, PBLENDFUNCTION pblend, DWORD dwFlags);

    static int BytesPerLine(int iWidth, int iBitsPerPixel)
    {
        return ((iWidth * iBitsPerPixel + 31) &amp;amp; (~31)) &amp;gt;&amp;gt; 3;
    }

public:
    static BOOL Update(HWND hwnd)
    {
        static fnUpdateLayeredWindow _fnUpdateLayeredWindow = (fnUpdateLayeredWindow)-1;

        if(_fnUpdateLayeredWindow == (fnUpdateLayeredWindow)-1)
        {
            (FARPROC&amp;amp;)_fnUpdateLayeredWindow = GetProcAddress(GetModuleHandle(_T(&amp;quot;USER32.DLL&amp;quot;)),
                &amp;quot;UpdateLayeredWindow&amp;quot;);
        }

        if(!_fnUpdateLayeredWindow)
            return FALSE;

        RECT rc;
        if(!GetWindowRect(hwnd, &amp;amp;rc))
            return FALSE;

        SIZE size = { rc.right - rc.left, rc.bottom - rc.top };

        HDC hdcMem = CreateCompatibleDC(NULL);
        if(!hdcMem)
            return FALSE;

        // 1. create a 32-bit memory bitmap according to the window's size
        BITMAPINFOHEADER bmih = { 0 };

        // Populate BITMAPINFO header
        bmih.biSize = sizeof(BITMAPINFOHEADER);
        bmih.biWidth = size.cx;
        bmih.biHeight = size.cy;
        bmih.biPlanes = 1;
        bmih.biBitCount = 32;
        bmih.biCompression = BI_RGB;
        bmih.biClrUsed = 0;
        bmih.biSizeImage = BytesPerLine(size.cx, 32) * size.cy;

        PVOID pvBits = NULL;
        HBITMAP hbmpMem = CreateDIBSection(NULL, (PBITMAPINFO)&amp;amp;bmih, DIB_RGB_COLORS, &amp;amp;pvBits, NULL, 0);
        if(hbmpMem)
        {
            HGDIOBJ hbmpOld = SelectObject(hdcMem, hbmpMem);

            // 2. get window's content
            SendMessage(hwnd, WM_PRINT, (WPARAM)hdcMem, (LPARAM)PRF_NONCLIENT | PRF_CLIENT | PRF_CHILDREN | PRF_CHECKVISIBLE);

            // 3. get rid of the areas occupied by children
            // SetWindowTransparency(hwnd, hdcMem, hbmpMem, 128, FALSE);
            SetWindowTransparency(hwnd, pvBits, size.cx, size.cy, 128, FALSE);

            // 4. pre-multiply rgb channels with alpha channel
            // PreMultiplyRGBChannels(hdcMem, hbmpMem);
            PreMultiplyRGBChannels(pvBits, size.cx, size.cy);

            // 5. ensure the WS_EX_LAYERED extended style is there
            SetWindowLong(hwnd, GWL_EXSTYLE, GetWindowLong(hwnd, GWL_EXSTYLE) | 0x00080000); // WS_EX_LAYERED

            // 6. update the window
            POINT ptSrc = { 0, 0 }; // start point of the copy from memory DC to screen DC
            BLENDFUNCTION bf = { AC_SRC_OVER, 0, 255, AC_SRC_ALPHA };
            _fnUpdateLayeredWindow(hwnd, NULL, (LPPOINT)&amp;amp;rc, &amp;amp;size, hdcMem, &amp;amp;ptSrc, 0, &amp;amp;bf, 0x00000002); // ULW_ALPHA

            // clean up
            SelectObject(hdcMem, hbmpOld);
            DeleteObject(hbmpMem);
        }

        DeleteDC(hdcMem);

        return TRUE;
    }

    static BOOL PreMultiplyRGBChannels(HDC hdc, HBITMAP hbmp)
    {
        if(!hdc || !hbmp)
            return FALSE;

        BITMAPINFO bmi = { 0 };
        bmi.bmiHeader.biSize = sizeof(bmi);
        if(!GetDIBits(hdc, hbmp, 0, 1, 0, &amp;amp;bmi, DIB_RGB_COLORS) || bmi.bmiHeader.biBitCount != 32)
            return FALSE;

        PBYTE pBuff = new BYTE[bmi.bmiHeader.biSizeImage + 0x20];
        if(!pBuff)
            return FALSE;

        BOOL bRet = FALSE;
        bmi.bmiHeader.biSize = sizeof(bmi);
        bmi.bmiHeader.biCompression = BI_RGB;
        if(GetDIBits(hdc, hbmp, 0, bmi.bmiHeader.biHeight, (PVOID)pBuff, &amp;amp;bmi, DIB_RGB_COLORS))
        {
            PreMultiplyRGBChannels(pBuff, bmi.bmiHeader.biWidth, bmi.bmiHeader.biHeight);
            bRet = SetDIBits(hdc, hbmp, 0, bmi.bmiHeader.biHeight, (PVOID)pBuff, &amp;amp;bmi, DIB_RGB_COLORS);
        }

        delete[] pBuff;

        return bRet;
    }

    static BOOL PreMultiplyRGBChannels(PVOID pvBits, int cx, int cy)
    {
        if(!pvBits || cx &amp;lt;= 0 || cy &amp;lt;= 0)
            return FALSE;

        // pre-multiply rgb channels with alpha channel
        for(int y=0; y&amp;lt;cy; y++)
        {
            PBYTE pPixel = ((PBYTE)pvBits) + cx * 4 * y;

            for(int x=0; x&amp;lt;cx; x++)
            {
                pPixel[0] = pPixel[0] * pPixel[3] / 255;
                pPixel[1] = pPixel[1] * pPixel[3] / 255;
                pPixel[2] = pPixel[2] * pPixel[3] / 255;

                pPixel += 4;
            }
        }

        return TRUE;
    }

    static BOOL SetWindowTransparency(HWND hwnd, HDC hdc, HBITMAP hbmp, int iAlpha, BOOL bExcludeChildren)
    {
        if(!hwnd || !IsWindow(hwnd) || !hdc || !hbmp || iAlpha &amp;lt; 0 || iAlpha &amp;gt; 255)
            return FALSE;

        BITMAPINFO bmi = { 0 };
        bmi.bmiHeader.biSize = sizeof(bmi);
        if(!GetDIBits(hdc, hbmp, 0, 1, 0, &amp;amp;bmi, DIB_RGB_COLORS) || bmi.bmiHeader.biBitCount != 32)
            return FALSE;

        PBYTE pBuff = new BYTE[bmi.bmiHeader.biSizeImage + 0x20];
        if(!pBuff)
            return FALSE;

        BOOL bRet = FALSE;
        bmi.bmiHeader.biSize = sizeof(bmi);
        bmi.bmiHeader.biCompression = BI_RGB;
        if(GetDIBits(hdc, hbmp, 0, bmi.bmiHeader.biHeight, (PVOID)pBuff, &amp;amp;bmi, DIB_RGB_COLORS))
        {
            SetWindowTransparency(hwnd, pBuff, bmi.bmiHeader.biWidth, bmi.bmiHeader.biHeight, iAlpha, bExcludeChildren);
            bRet = SetDIBits(hdc, hbmp, 0, bmi.bmiHeader.biHeight, (PVOID)pBuff, &amp;amp;bmi, DIB_RGB_COLORS);
        }

        delete[] pBuff;

        return bRet;
    }

    static BOOL SetWindowTransparency(HWND hwnd, PVOID pvBits, int cx, int cy, int iAlpha, BOOL bExcludeChildren)
    {
        if(!hwnd || !IsWindow(hwnd) || !pvBits || cx &amp;lt;= 0 || cy &amp;lt;= 0 || iAlpha &amp;lt; 0 || iAlpha &amp;gt; 255)
            return FALSE;

        RECT rcWindow;
        GetWindowRect(hwnd, &amp;amp;rcWindow);
        MapWindowPoints(HWND_DESKTOP, hwnd, (LPPOINT)&amp;amp;rcWindow, 2);

        // 1. get all children
        HRGN hrgnChildren = CreateRectRgn(0, 0, 0, 0);

        RECT rc;
        HWND hwndChild = GetWindow(hwnd, GW_CHILD);
        while(hwndChild)
        {
            GetWindowRect(hwndChild, &amp;amp;rc);                          // in screen coord'
            MapWindowPoints(HWND_DESKTOP, hwnd, (LPPOINT)&amp;amp;rc, 2);   // in client coord'
            OffsetRect(&amp;amp;rc, -rcWindow.left, -rcWindow.top);         // in window coord'

            HRGN hrgnChild = CreateRectRgnIndirect(&amp;amp;rc);
            CombineRgn(hrgnChildren, hrgnChildren, hrgnChild, RGN_OR);
            DeleteObject(hrgnChild);

            hwndChild = GetWindow(hwndChild, GW_HWNDNEXT);
        }

        for(int y=0; y&amp;lt;cy; y++)
        {
            PBYTE pPixel = ((PBYTE)pvBits) + cx * 4 * y;

            for(int x=0; x&amp;lt;cx; x++)
            {
                if(PtInRegion(hrgnChildren, x, cy - y))
                    pPixel[3] = 255;
                else
                    pPixel[3] = iAlpha;

                pPixel += 4;
            }
        }

        DeleteObject(hrgnChildren);

        return TRUE;
    }
};
&lt;/font&gt;&lt;/pre&gt;&lt;/div&gt;&lt;img src="http://c.services.spaces.live.com/CollectionWebService/c.gif?cid=3848887354281525204&amp;page=RSS%3a+%e5%8d%8a%e9%80%8f%e6%98%8e%e7%aa%97%e5%8f%a3%e4%b8%ad%e7%9a%84%e5%ad%a4%e5%b2%9b&amp;referrer=" width="1px" height="1px" border="0" alt=""&gt;&lt;img style="position:absolute" alt="" width="0px" height="0px" src="http://c.live.com/c.gif?NC=31263&amp;amp;NA=1149&amp;amp;PI=73329&amp;amp;RF=&amp;amp;DI=3919&amp;amp;PS=85545&amp;amp;TP=sluttery.spaces.live.com&amp;amp;GT1=sluttery"&gt;</description><comments>http://sluttery.spaces.live.com/Blog/cns!3569FEA80C717FD4!3439.entry#comment</comments><guid isPermaLink="true">http://sluttery.spaces.live.com/Blog/cns!3569FEA80C717FD4!3439.entry</guid><pubDate>Sat, 21 Oct 2006 02:44:51 GMT</pubDate><slash:comments>7</slash:comments><msn:type>blogentry</msn:type><live:type>blogentry</live:type><live:typelabel>Blog entry</live:typelabel><wfw:commentRss>http://sluttery.spaces.live.com/blog/cns!3569FEA80C717FD4!3439/comments/feed.rss</wfw:commentRss><wfw:comment>http://sluttery.spaces.live.com/Blog/cns!3569FEA80C717FD4!3439.entry#comment</wfw:comment><dcterms:modified>2006-10-21T03:14:08Z</dcterms:modified></item><item><title>译：MFC 程序员的 WTL 教程（十）（第二版）</title><link>http://sluttery.spaces.live.com/Blog/cns!3569FEA80C717FD4!3227.entry</link><description>&lt;p&gt;
&lt;p&gt;&lt;font color="#ff0000"&gt;特别注：由于本页内容栏宽度不够，会导致部分内容看不见，请点击&lt;a href="http://sluttery.spaces.live.com/blog/cns!3569FEA80C717FD4!3227.entry"&gt;这里&lt;/a&gt;以获得最佳浏览效果。&lt;/font&gt;
&lt;p&gt;链接：&lt;a href="http://sluttery.spaces.msn.com/blog/cns!3569FEA80C717FD4!3154.entry"&gt;上一部分&lt;/a&gt;
&lt;div style="overflow:scroll;width:100%"&gt;&lt;b&gt;&lt;font style="font-size:16pt" size=4&gt;第十部分 - 实现一个拖放源&lt;/font&gt;&lt;/b&gt; 
&lt;p&gt;
&lt;p&gt;
&lt;table cellspacing=0 cellpadding=0 border=0&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://www.codeproject.com/wtl/wtl4mfc10/WTL4MFC10_demo.zip"&gt;下载示例工程 - 97KB&lt;/a&gt; &lt;/ul&gt;
&lt;h2&gt;内容&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a&gt;简介&lt;/a&gt; 
&lt;li&gt;&lt;a&gt;开始工程&lt;/a&gt; 
&lt;li&gt;&lt;a&gt;文件打开处理&lt;/a&gt; 
&lt;li&gt;&lt;a&gt;拖动源&lt;/a&gt; 
&lt;ul&gt;
&lt;li&gt;&lt;a&gt;拖动源的接口&lt;/a&gt; 
&lt;li&gt;&lt;a&gt;用于调用者的辅助方法&lt;/a&gt; 
&lt;li&gt;&lt;a&gt;IDropSource 的方法&lt;/a&gt; &lt;/ul&gt;
&lt;li&gt;&lt;a&gt;从查看器中拖放&lt;/a&gt; 
&lt;li&gt;&lt;a&gt;加入 MRU 列表&lt;/a&gt; 
&lt;ul&gt;
&lt;li&gt;&lt;a&gt;设置 MRU 对象&lt;/a&gt; 
&lt;li&gt;&lt;a&gt;处理 MRU 命令并更新列表&lt;/a&gt; 
&lt;li&gt;&lt;a&gt;保存 MRU 列表&lt;/a&gt; &lt;/ul&gt;
&lt;li&gt;&lt;a&gt;其他 UI Goodies&lt;/a&gt; 
&lt;ul&gt;
&lt;li&gt;&lt;a&gt;透明的拖动图像&lt;/a&gt; 
&lt;li&gt;&lt;a&gt;透明的选择矩形&lt;/a&gt; 
&lt;li&gt;&lt;a&gt;标示排序的列&lt;/a&gt; 
&lt;li&gt;&lt;a&gt;使用平铺视图模式&lt;/a&gt; 
&lt;ul&gt;
&lt;li&gt;&lt;a&gt;设置平铺视图的图像列表&lt;/a&gt; 
&lt;li&gt;&lt;a&gt;使用平铺视图的图像列表&lt;/a&gt; 
&lt;li&gt;&lt;a&gt;设置附加的文本行&lt;/a&gt; &lt;/ul&gt;&lt;/ul&gt;
&lt;li&gt;&lt;a&gt;版权和许可&lt;/a&gt; 
&lt;li&gt;&lt;a&gt;修订历史&lt;/a&gt; &lt;/ul&gt;
&lt;h2&gt;&lt;a&gt;&lt;/a&gt;简介&lt;/h2&gt;
&lt;p&gt;拖放是许多流行应用的特性之一。尽管实现一个放下目标相当简单，但拖动源却要复杂的多。MFC 中有两个类 &lt;code&gt;COleDataObject&lt;/code&gt; 和 &lt;code&gt;COleDropSource&lt;/code&gt; 可以帮助管理拖动源所必须提供的数据，但 WTL 中没有这种辅助类。对于我们这些 WTL 用户来说，幸运的是，&lt;a href="http://blogs.msdn.com/oldnewthing/"&gt;Raymond Chen&lt;/a&gt; 在 2000 年的时候在 MSDN 上写过一篇文章（“&lt;a href="http://msdn.microsoft.com/library/en-us/dnwui/html/ddhelp_pt2.asp"&gt;The Shell Drag/Drop Helper Object Part 2&lt;/a&gt;”），其中有 &lt;code&gt;IDataObject&lt;/code&gt; 的纯 C++ 实现，这对于为 WTL 应用编制一个完整的拖放源提供了巨大的帮助。
&lt;p&gt;本文的示例工程是一个 CAB 文件查看器，可以使你从 CAB 中提取文件，只要把它们从查看器里拖到资源浏览器窗口中即可。本文还会讨论几个新的框架窗口话题，例如对文件打开的处理以及类似于 MFC 的文档/视图框架的数据管理。我还会演示 WTL 的 MRU（most-recently-used，最近使用）文件列表类，以及第六版的列表视图控件的几个新的 UI 特性。
&lt;p&gt;&lt;b&gt;重要提示&lt;/b&gt;：你需要从微软下载并安装 CAB SDK 来编译示例代码。在 KB 文章 &lt;a href="http://support.microsoft.com/?kbid=310618"&gt;Q310618&lt;/a&gt; 中有此 SDK 的链接。示例工程假定 SDK 位于和源代码相同的路径下的名为“cabsdk”的目录中。
&lt;p&gt;记住，如果你在安装 WTL 或者编译示例代码时遇到了问题，请在这儿提问之前先阅读&lt;a href="http://www.codeproject.com/wtl/wtl4mfc1.asp#readme"&gt;第一部分的 readme 一节&lt;/a&gt;。
&lt;h2&gt;&lt;a&gt;&lt;/a&gt;开始工程&lt;/h2&gt;
&lt;p&gt;要开始我们的 CAB 查看器应用，需要运行 WTL AppWizard 并创建一个名为 &lt;i&gt;WTLCabView&lt;/i&gt; 的工程。它应该是一个 SDI 应用，所以在第一页中选择 SDI Application：
&lt;p&gt;&lt;img src="http://tkfiles.storage.msn.com/x1pC47KWjv0VYmhru0KKrOfU4W_CRegpw2CcWmMk_fsNT6YJPeQfaHoCnUYjUdgO0d9K_1YisGQT-oHrwISmNhFk7cs4bWEshzW22XRUQBsRi3mCTNWLDLSL3ZF1mKge84XaI2cFVx5x8c"&gt;
&lt;p&gt;在下一页里，去掉 &lt;i&gt;Command Bar&lt;/i&gt;，并把 &lt;i&gt;View Type&lt;/i&gt; 改为 &lt;i&gt;List View&lt;/i&gt;。向导会为我们的视图窗口创建一个派生自 &lt;code&gt;CListViewCtrl&lt;/code&gt; 的 C++ 类。
&lt;p&gt;&lt;img src="http://tkfiles.storage.msn.com/x1pC47KWjv0VYmhru0KKrOfUzjWfkw3o-7FY9w6o63wd86HSq0R_KCDmf98mEKbYIftkCKLrRxLBxIgxWYIhgMFxftUzwdjRUJbj97nONbFx-bDYVEJDj99r9zXPK2o-fCFEAcOXaFgrMU"&gt;
&lt;p&gt;视图窗口类看起来就是这样：&lt;pre&gt;class CWTLCabViewView :
  public CWindowImpl&amp;lt;CWTLCabViewView, CListViewCtrl&amp;gt;
{
public:
  DECLARE_WND_SUPERCLASS(NULL, CListViewCtrl::GetWndClassName())
 
  &lt;span&gt;// Construction&lt;/span&gt;
  CWTLCabViewView();
 
  &lt;span&gt;// Maps&lt;/span&gt;
  BEGIN_MSG_MAP(CWTLCabViewView)
  END_MSG_MAP()
 
  &lt;span&gt;// ...&lt;/span&gt;
};&lt;/pre&gt;
&lt;p&gt;就像我们在&lt;a href="http://www.codeproject.com/wtl/wtl4mfc2.asp"&gt;第二部分&lt;/a&gt;里使用视图类一样，我们可以使用 &lt;code&gt;CWindowImpl&lt;/code&gt; 的第三个模板参数设置缺省的窗口风格：&lt;pre&gt;&lt;b&gt;#define VIEW_STYLES \
  (LVS_REPORT | LVS_SHOWSELALWAYS | \
   LVS_SHAREIMAGELISTS | LVS_AUTOARRANGE )
&lt;span&gt;#define VIEW_EX_STYLES (WS_EX_CLIENTEDGE)&lt;/span&gt;&lt;/b&gt;
 
class CWTLCabViewView :
  public CWindowImpl&amp;lt;CWTLCabViewView, CListViewCtrl,
                     &lt;b&gt;CWinTraitsOR&amp;lt;VIEW_STYLES,VIEW_EX_STYLES&amp;gt;&lt;/b&gt; &amp;gt;
{
&lt;span&gt;//...&lt;/span&gt;
};&lt;/pre&gt;
&lt;p&gt;由于在 WTL 中没有文档/视图框架，视图类需要做双份的工作，既是 UI，也是存放有关 CAB 信息的地方。在拖放操作中传递的数据结构为 &lt;code&gt;CDraggedFileInfo&lt;/code&gt;：&lt;pre&gt;struct CDraggedFileInfo
{
  &lt;span&gt;// Data set at the beginning of a drag/drop:&lt;/span&gt;
  CString sFilename;      &lt;span&gt;// name of the file as stored in the CAB&lt;/span&gt;
  CString sTempFilePath;  &lt;span&gt;// path to the file we extract from the CAB&lt;/span&gt;
  int nListIdx;           &lt;span&gt;// index of this item in the list ctrl&lt;/span&gt;
 
  &lt;span&gt;// Data set while extracting files:&lt;/span&gt;
  bool bPartialFile;  &lt;span&gt;// true if this file is continued in another cab&lt;/span&gt;
  CString sCabName;   &lt;span&gt;// name of the CAB file&lt;/span&gt;
  bool bCabMissing;   &lt;span&gt;// true if the file is partially in this cab and&lt;/span&gt;
                      &lt;span&gt;// the CAB it's continued in isn't found, meaning&lt;/span&gt;
                      &lt;span&gt;// the file can't be extracted&lt;/span&gt;
 
  CDraggedFileInfo ( const CString&amp;amp; s, int n ) :
    sFilename(s), nListIdx(n), bPartialFile(false),
    bCabMissing(false)
  { }
};&lt;/pre&gt;
&lt;p&gt;视图类中还有如下方法：初始化、管理文件列表，以及在拖放操作开始的时候准备一个 &lt;code&gt;CDraggedFileInfo&lt;/code&gt; 列表。由于本文是讲关于拖放的，所以我不会深入到 UI 工作的内部去，需要了解所有细节的话可以检视示例工程中的 &lt;i&gt;WTLCabViewView.h&lt;/i&gt;。
&lt;h2&gt;&lt;a&gt;&lt;/a&gt;文件打开处理&lt;/h2&gt;
&lt;p&gt;要查看一个 CAB 文件，用户可以使用 &lt;i&gt;File-Open&lt;/i&gt; 命令并选择一个 CAB 文件。向导为 &lt;code&gt;CMainFrame&lt;/code&gt; 生成的代码包含了 &lt;i&gt;File-Open&lt;/i&gt; 菜单项的一个处理器：&lt;pre&gt;  BEGIN_MSG_MAP(CMainFrame)
    COMMAND_ID_HANDLER_EX(ID_FILE_OPEN, OnFileOpen)
  END_MSG_MAP()&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;OnFileOpen()&lt;/code&gt; 使用了 &lt;code&gt;CMyFileDialog&lt;/code&gt; 类，该类是在&lt;a href="http://www.codeproject.com/wtl/wtl4mfc9.asp#usingcfiledialog"&gt;第九部分&lt;/a&gt;里介绍到的 WTL 的 &lt;code&gt;CFileDialog&lt;/code&gt; 的增强版本，用以显示一个标准的文件打开对话框。&lt;pre&gt;void CMainFrame::OnFileOpen (
  UINT uCode, int nID, HWND hwndCtrl )
{
CMyFileDialog dlg ( true, _T(&lt;span&gt;&amp;quot;cab&amp;quot;&lt;/span&gt;), 0U,
                    OFN_HIDEREADONLY|OFN_FILEMUSTEXIST,
                    IDS_OPENFILE_FILTER, *this );
 
  if ( IDOK == dlg.DoModal(*this) )
    ViewCab ( dlg.m_szFileName );
}&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;OnFileOpen()&lt;/code&gt; 调用了辅助函数 &lt;code&gt;ViewCab()&lt;/code&gt;：&lt;pre&gt;void CMainFrame::ViewCab ( LPCTSTR szCabFilename )
{
  if ( EnumCabContents ( szCabFilename ) )
    m_sCurrentCabFilePath = szCabFilename;
}&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;EnumCabContents()&lt;/code&gt; 相当的复杂，它使用 CAB SDK 调用来枚举在 &lt;code&gt;OnFileOpen()&lt;/code&gt; 中选中的文件的内容，并填充视图窗口。不过 &lt;code&gt;ViewCab()&lt;/code&gt; 现在还不完善，我们后面会给它加入支持 MRU 列表的代码。下面是查看器的样子，其中显示着 Windows 98 的 某个 CAB 文件的内容：
&lt;p&gt;&lt;img src="http://tkfiles.storage.msn.com/x1pC47KWjv0VYmhru0KKrOfUwzCXqLEuF1y7NiKMLfbubybR2TknHg6LvncjR-8XCgNrcKBRcPoUuRKY5JH27-X4E5scw4PPVqT9oTX3zdYbL2F5soO2n0z4kCnP8tsl1YVmnEsRAx1a3Y"&gt;
&lt;p&gt;&lt;code&gt;EnumCabContents()&lt;/code&gt; 使用了视图类中的两个方法来填充 UI：&lt;code&gt;AddFile()&lt;/code&gt; 和 &lt;code&gt;AddPartialFile()&lt;/code&gt;。&lt;code&gt;AddPartialFile()&lt;/code&gt; 在当一个文件被部分存储在 CAB 中时被调用，因为其头部是在前面的 CAB 里。在上面的截图里，列表中的第一个文件就是一个部分文件。其余的文件是通过 &lt;code&gt;AddFile()&lt;/code&gt; 添加的。这两个方法都会为要添加的文件分配一个数据结构，从而视图可以知道其显示的每个文件的所有相关细节。
&lt;p&gt;如果 &lt;code&gt;EnumCabContents()&lt;/code&gt; 返回真，则代表所有的枚举以及 UI 设置工作成功完成。如果我们只是写一个简单的 CAB 查看器，我们就可以收手了，尽管此应用不那么有趣。为了使它真正地有用，我们将对它添加拖放支持，以使用户可以从 CAB 中提取文件。
&lt;h2&gt;&lt;a&gt;&lt;/a&gt;拖动源&lt;/h2&gt;
&lt;p&gt;拖放源是一个 COM 对象，它实现了两个接口：&lt;code&gt;IDataObject&lt;/code&gt; 和 &lt;code&gt;IDropSource&lt;/code&gt;。&lt;code&gt;IDataObject&lt;/code&gt; 用来存放在拖放操作中客户端需要传递的任意数据，在我们这种情况下，此数据应该是一个 &lt;code&gt;HDROP&lt;/code&gt; 结构，其中列出了要从 CAB 中提取的文件。&lt;code&gt;IDropSource&lt;/code&gt; 的方法会由 OLE 调用，用以在拖放操作中向源通知事件。
&lt;h3&gt;&lt;a&gt;&lt;/a&gt;拖动源接口&lt;/h3&gt;
&lt;p&gt;实现了我们的拖放源的 C++ 类为 &lt;code&gt;CDragDropSource&lt;/code&gt;。该类以我在简介中提到过的&lt;a href="http://msdn.microsoft.com/library/en-us/dnwui/html/ddhelp_pt2.asp"&gt; MSDN 文章&lt;/a&gt;中的 &lt;code&gt;IDataObject&lt;/code&gt; 实现为起始。在该文中你可以找到所有代码相关的细节，因此我在这儿就不重复了。然后我们再向类中加入 &lt;code&gt;IDropSource&lt;/code&gt; 及其两个方法：&lt;pre&gt;class CDragDropSource :
  public CComObjectRootEx&amp;lt;CComSingleThreadModel&amp;gt;,
  public CComCoClass&amp;lt;CDragDropSource&amp;gt;,
  public IDataObject,
  &lt;b&gt;public IDropSource&lt;/b&gt;
{
public:
  &lt;span&gt;// Construction&lt;/span&gt;
  CDragDropSource();
 
  &lt;span&gt;// Maps&lt;/span&gt;
  BEGIN_COM_MAP(CDragDropSource)
    COM_INTERFACE_ENTRY(IDataObject)
    &lt;b&gt;COM_INTERFACE_ENTRY(IDropSource)&lt;/b&gt;
  END_COM_MAP()
 
  &lt;span&gt;// IDataObject methods not shown...&lt;/span&gt;
 
  &lt;b&gt;&lt;span&gt;// IDropSource&lt;/span&gt;
  STDMETHODIMP QueryContinueDrag (
                 BOOL fEscapePressed, DWORD grfKeyState );
  STDMETHODIMP GiveFeedback ( DWORD dwEffect );&lt;/b&gt;
};&lt;/pre&gt;
&lt;h3&gt;&lt;a&gt;&lt;/a&gt;用于调用者的辅助方法&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;CDragDropSource&lt;/code&gt; 使用几个辅助方法封装了 &lt;code&gt;IDataObject&lt;/code&gt; 的管理以及拖放的通信。一个拖放操作遵循以下模式：
&lt;ol&gt;
&lt;li&gt;主框架得到用户开始拖放操作的通知。 
&lt;li&gt;主框架调用视图窗口来创建一个被拖动的文件的列表。视图在一个 &lt;code&gt;vector&amp;lt;CDraggedFileInfo&amp;gt;&lt;/code&gt; 向量中返回此信息。 
&lt;li&gt;主框架创建一个 &lt;code&gt;CDragDropSource&lt;/code&gt; 对象并将上述向量传递给它，以使它得知要从 CAB 中提取哪些文件。 
&lt;li&gt;主框架开始拖放操作。 
&lt;li&gt;如果用户在一个适当的拖放目标上放下，则 &lt;code&gt;CDragDropSource&lt;/code&gt; 对象提取文件。 
&lt;li&gt;主框架更新 UI 以标示不能被提取的文件。&lt;/ol&gt;
&lt;p&gt;步骤 3 到 6 由辅助方法处理。初始化在 &lt;code&gt;Init()&lt;/code&gt; 方法中完成：&lt;pre&gt;   bool Init(LPCTSTR szCabFilePath, vector&amp;lt;CDraggedFileInfo&amp;gt;&amp;amp; vec);&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;Init()&lt;/code&gt; 将数据复制到保护成员中，填入到一个 &lt;code&gt;HDROP&lt;/code&gt; 结构，并使用 &lt;code&gt;IDataObject&lt;/code&gt; 方法将该结构保存到数据对象中。&lt;code&gt;Init()&lt;/code&gt; 还作了另一个重要的步骤：它在 TEMP 目录下为每个拖动的文件创建了一个零字节的文件。例如，如果用户从 CAB 文件中拖动 &lt;i&gt;buffy.txt&lt;/i&gt; 和 &lt;i&gt;willow.txt&lt;/i&gt;，&lt;code&gt;Init()&lt;/code&gt; 将在 TEMP 目录下使用这两个名字创建两个文件。这是为了预防，万一拖放目标要验证从 &lt;code&gt;HDROP&lt;/code&gt; 读入的文件名，如果文件不存在，则目标有可能会拒绝放下。
&lt;p&gt;接下来的方法是 &lt;code&gt;DoDragDrop()&lt;/code&gt;：&lt;pre&gt;  HRESULT DoDragDrop(DWORD dwOKEffects, DWORD* pdwEffect);&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;DoDragDrop()&lt;/code&gt; 接受 &lt;code&gt;dwOKEffects&lt;/code&gt; 中的一组 &lt;code&gt;DROPEFFECT_*&lt;/code&gt; 标志，这些标志表明了源所允许的那些动作。它会查询必要的接口，然后调用 &lt;code&gt;DoDragDrop()&lt;/code&gt; API。如果拖放成功，&lt;code&gt;*pdwEffect&lt;/code&gt; 就被设置为用户希望执行的 &lt;code&gt;DROPEFFECT_*&lt;/code&gt; 值。
&lt;p&gt;最后一个方法是 &lt;code&gt;GetDragResults()&lt;/code&gt;：&lt;pre&gt;  const vector&amp;lt;CDraggedFileInfo&amp;gt;&amp;amp; GetDragResults();&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;CDragDropSource&lt;/code&gt; 对象维护的 &lt;code&gt;vector&amp;lt;CDraggedFileInfo&amp;gt;&lt;/code&gt; 会在拖放操作过程中被更新。如果某个文件被发现还连着另一个 CAB，或者是不能被提取，则 &lt;code&gt;CDraggedFileInfo&lt;/code&gt; 结构会被执行必要的更新。主框架调用 &lt;code&gt;GetDragResults()&lt;/code&gt; 来获取此向量，查找错误并相应更新 UI。
&lt;h3&gt;&lt;a&gt;&lt;/a&gt;IDropSource 的方法&lt;/h3&gt;
&lt;p&gt;第一个 &lt;code&gt;IDropSource&lt;/code&gt; 方法是 &lt;code&gt;GiveFeedback()&lt;/code&gt;，它通知源，用户是想采取哪种操作（移动、复制或者链接）。如果愿意的话源可以改变光标。&lt;code&gt;CDragDropSource&lt;/code&gt; 对操作保持了跟踪，并告诉 OLE 要使用缺省的拖放光标。&lt;pre&gt;STDMETHODIMP CDragDropSource::GiveFeedback(DWORD dwEffect)
{
  m_dwLastEffect = dwEffect;
  return DRAGDROP_S_USEDEFAULTCURSORS;
}&lt;/pre&gt;
&lt;p&gt;另一个 &lt;code&gt;IDropSource&lt;/code&gt; 方法是 &lt;code&gt;QueryContinueDrag()&lt;/code&gt;。OLE 在用户把光标移来移去时调用此方法，并告诉源哪个鼠标键，以及键盘键，被按下了。下边是大多数 &lt;code&gt;QueryContinueDrag()&lt;/code&gt; 的实现所采用的样板代码：&lt;pre&gt;STDMETHODIMP CDragDropSource::QueryContinueDrag (
    BOOL fEscapePressed, DWORD grfKeyState )
{
  &lt;span&gt;// If ESC was pressed, cancel the drag.&lt;/span&gt;
  &lt;span&gt;// If the left button was released, do drop processing.&lt;/span&gt;
  if ( fEscapePressed )
    return DRAGDROP_S_CANCEL;
  else if ( !(grfKeyState &amp;amp; MK_LBUTTON) )
    {
    &lt;span&gt;// If the last DROPEFFECT we got in GiveFeedback()&lt;/span&gt;
    &lt;span&gt;// was DROPEFFECT_NONE, we abort because the allowable&lt;/span&gt;
    &lt;span&gt;// effects of the source and target don't match up.&lt;/span&gt;
    if ( DROPEFFECT_NONE == m_dwLastEffect )
      return DRAGDROP_S_CANCEL;
 
    &lt;span&gt;// TODO: Extract files from the CAB here...&lt;/span&gt;
 
    return DRAGDROP_S_DROP;
    }
  else
    return S_OK;
}&lt;/pre&gt;
&lt;p&gt;当我们发现左键被释放了，就到了我们要从 CAB 中提取选中的文件的地方了。&lt;pre&gt;STDMETHODIMP CDragDropSource::QueryContinueDrag (
    BOOL fEscapePressed, DWORD grfKeyState )
{
  &lt;span&gt;// If ESC was pressed, cancel the drag.&lt;/span&gt;
  &lt;span&gt;// If the left button was released, do the drop.&lt;/span&gt;
  if ( fEscapePressed )
    return DRAGDROP_S_CANCEL;
  else if ( !(grfKeyState &amp;amp; MK_LBUTTON) )
    {
    &lt;span&gt;// If the last DROPEFFECT we got in GiveFeedback()&lt;/span&gt;
    &lt;span&gt;// was DROPEFFECT_NONE, we abort because the allowable&lt;/span&gt;
    &lt;span&gt;// effects of the source and target don't match up.&lt;/span&gt;
    if ( DROPEFFECT_NONE == m_dwLastEffect )
      return DRAGDROP_S_CANCEL;
 
&lt;b&gt;    &lt;span&gt;// If the drop was accepted, do the extracting here,&lt;/span&gt;
    &lt;span&gt;// so that when we return, the files are in the temp dir&lt;/span&gt;
    &lt;span&gt;// and ready for Explorer to copy.&lt;/span&gt;&lt;/b&gt;
&lt;b&gt;    if ( ExtractFilesFromCab() )
      return DRAGDROP_S_DROP;
    else
      return E_UNEXPECTED;
&lt;/b&gt;    }
  else
    return S_OK;
}&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;CDragDropSource::ExtractFilesFromCab()&lt;/code&gt; 是另一个复杂点的代码，它使用 CAB SDK 把文件提取到 TEMP 目录下，覆盖掉我们先前创建的零字节的文件。当 &lt;code&gt;QueryContinueDrag()&lt;/code&gt; 返回 &lt;code&gt;DRAGDROP_S_DROP&lt;/code&gt; 时，也即告诉了 OLE 完成此拖放操作。如果拖放目标是一个资源浏览器窗口，资源浏览器会把文件从 TEMP 目录复制到发生拖放的目录。
&lt;h2&gt;&lt;a&gt;&lt;/a&gt;从查看器中拖放&lt;/h2&gt;
&lt;p&gt;我们已经看过了实现拖放操作逻辑的类，现在，我们来看一下我们的查看器应用是怎样使用这个类的。当主框架窗口接收到 &lt;code&gt;LVN_BEGINDRAG&lt;/code&gt; 通知消息时，它会调用视图以获取选中文件的列表，而后设置 &lt;code&gt;CDragDropSource&lt;/code&gt; 对象：&lt;pre&gt;LRESULT CMainFrame::OnListBeginDrag(NMHDR* phdr)
{
vector&amp;lt;CDraggedFileInfo&amp;gt; vec;
CComObjectStack&amp;lt;CDragDropSource&amp;gt; dropsrc;
DWORD dwEffect = 0;
HRESULT hr;
 
  &lt;span&gt;// Get a list of the files being dragged (minus files&lt;/span&gt;
  &lt;span&gt;// that we can't extract from the current CAB).&lt;/span&gt;
  if ( !m_view.GetDraggedFileInfo(vec) )
    return 0;   &lt;span&gt;// do nothing&lt;/span&gt;
 
  &lt;span&gt;// Init the drag/drop data object.&lt;/span&gt;
  if ( !dropsrc.Init(m_sCurrentCabFilePath, vec) )
    return 0;   &lt;span&gt;// do nothing&lt;/span&gt;
 
  &lt;span&gt;// Start the drag/drop!&lt;/span&gt;
  hr = dropsrc.DoDragDrop(DROPEFFECT_COPY, &amp;amp;dwEffect);
 
  return 0;
}&lt;/pre&gt;
&lt;p&gt;第一个调用的是视图的 &lt;code&gt;GetDraggedFileInfo()&lt;/code&gt; 方法，用以得到选中文件的列表。此方法返回一个 &lt;code&gt;vector&amp;lt;CDraggedFileInfo&amp;gt;&lt;/code&gt;，我们要用它来初始化 &lt;code&gt;CDragDropSource&lt;/code&gt; 对象。&lt;code&gt;GetDraggedFileInfo()&lt;/code&gt; 在选定的文件都不能被提取的情况下（例如文件被分块存放在不同的 CAB 文件中）有可能失败。如果发生了这种情况，则 &lt;code&gt;OnListBeginDrag()&lt;/code&gt; 也静静地失败，不做任何事情就返回。最后，我们调用 &lt;code&gt;DoDragDrop()&lt;/code&gt; 来开始操作，并让 &lt;code&gt;CDragDropSource&lt;/code&gt; 处理剩余的事情。
&lt;p&gt;上面列出的步骤 6 提到了拖放结束后对 UI 的更新。因为有可能在 CAB 末尾的一个文件仅仅是部分存储于此 CAB 中，而剩余的则在后续的一个 CAB 里。（这在 Windows 9x 的安装文件里非常普遍，在那儿 CAB 需要能符合软盘的大小）当我们试图提取这样的一个文件时，CAB SDK 会告诉我们含有该文件剩余部分的 CAB 的名字。它还会在原始 CAB 所在的相同目录下寻找那个 CAB，如果存在的话则从中提取文件的剩余部分。
&lt;p&gt;因为我们要在视图窗口中标示分块文件，所以 &lt;code&gt;OnListBeginDrag()&lt;/code&gt; 会检查拖放的结果，看是否找到了分块文件：&lt;pre&gt;LRESULT CMainFrame::OnListBeginDrag(NMHDR* phdr)
{
&lt;span&gt;//...&lt;/span&gt;
  
  &lt;span&gt;// Start the drag/drop!&lt;/span&gt;
  hr = dropsrc.DoDragDrop(DROPEFFECT_COPY, &amp;amp;dwEffect);
 
&lt;b&gt;  if ( FAILED(hr) )
    ATLTRACE(&lt;span&gt;&amp;quot;DoDragDrop() failed, error: 0x%08X\n&amp;quot;&lt;/span&gt;, hr);
  else
    {
    &lt;span&gt;// If we found any files continued into other CABs, update the UI.&lt;/span&gt;
    const vector&amp;lt;CDraggedFileInfo&amp;gt;&amp;amp; vecResults = dropsrc.GetDragResults();
    vector&amp;lt;CDraggedFileInfo&amp;gt;::const_iterator it;
 
    for ( it = vecResults.begin(); it != vecResults.end(); it++ )
      {
      if ( it-&amp;gt;bPartialFile )
        m_view.UpdateContinuedFile ( *it );
      }
    }&lt;/b&gt;
 
  return 0;
}&lt;/pre&gt;
&lt;p&gt;我们调用 &lt;code&gt;GetDragResults()&lt;/code&gt; 来获取反映了拖放操作结果的更新过的 &lt;code&gt;vector&amp;lt;CDraggedFileInfo&amp;gt;&lt;/code&gt;。如果结构中的 &lt;code&gt;bPartialFile&lt;/code&gt; 成员为 &lt;code&gt;&lt;span&gt;true&lt;/span&gt;&lt;/code&gt;，则表示该文件仅部分存在于此 CAB 中。我们再调用视图方法 &lt;code&gt;UpdateContinuedFile()&lt;/code&gt;，并将信息结构传递给它，因而它可以相应地文件列表视图中的项。下面就是当发现有后续 CAB 时，应用程序如何标示分块文件：
&lt;p&gt;&lt;img src="http://tkfiles.storage.msn.com/x1pC47KWjv0VYmhru0KKrOfU0AbPaNSk2UIBGGqFppU3lOxC4D9cz-VU86TqjmUc5XEf1yOMgz7CwhB1POHAvU3p75qIoXr7RP4EBHHX3ETedR1Omla4_Q3UCY9fhmNsuw_H6DsDAYf2tw"&gt;
&lt;p&gt;如果找不到后续的 CAB，应用会通过设置 &lt;code&gt;LVIS_CUT&lt;/code&gt; 风格来标示该文件不可以被提取，所以图标看起来是虚的：
&lt;p&gt;&lt;img src="http://tkfiles.storage.msn.com/x1pC47KWjv0VYmhru0KKrOfU5H1kLXwAhTMOjUR1AbJ_5AKb4gWja6uZuHXfubbAayaw3DqS6-ON_EFwF6C9wsJAZ21yqyuQqGMFY-3iXYUPYlE-XbkK8hIXSlH9e2Gq7z4AQs6ZkNLGf4"&gt;
&lt;p&gt;出于安全考虑，应用把提取出的文件留在了 TEMP 目录里，而不是在拖放操作结束后立刻清除它们。在 &lt;code&gt;CDragDropSource::Init()&lt;/code&gt; 创建零字节的临时文件时，它同时也把文件名添加到了全局向量 &lt;code&gt;g_vecsTempFiles&lt;/code&gt; 中。临时文件在主框架窗口关闭时才删除。
&lt;h2&gt;&lt;a&gt;&lt;/a&gt;加入 MRU 列表&lt;/h2&gt;
&lt;p&gt;我们将要看到的另一个文档/视图风格的特性是最近使用文件列表。WTL 的 MRU 实现为一个模板类 &lt;code&gt;CRecentDocumentListBase&lt;/code&gt;。如果你不需要覆盖 MRU 任何的缺省行为（当然缺省通常已经足够用了），你可以使用派生类 &lt;code&gt;CRecentDocumentList&lt;/code&gt;。
&lt;p&gt;&lt;code&gt;CRecentDocumentListBase&lt;/code&gt; 模板具有以下参数：&lt;pre&gt;template &amp;lt;class T, int t_cchItemLen = MAX_PATH,
          int t_nFirstID = ID_FILE_MRU_FIRST,
          int t_nLastID = ID_FILE_MRU_LAST&amp;gt; CRecentDocumentListBase&lt;/pre&gt;
&lt;dl&gt;
&lt;dt&gt;&lt;code&gt;T&lt;/code&gt; 
&lt;dd&gt;特化 &lt;code&gt;CRecentDocumentListBase&lt;/code&gt; 的派生类的名字。 
&lt;dt&gt;&lt;code&gt;t_cchItemLen&lt;/code&gt; 
&lt;dd&gt;以 &lt;code&gt;TCHAR&lt;/code&gt; 为单位的存储在 MRU 项中的字符串长度。至少必须为 6。 
&lt;dt&gt;&lt;code&gt;t_nFirstID&lt;/code&gt; 
&lt;dd&gt;用于 MRU 项的 ID 范围的最小 ID。 
&lt;dt&gt;&lt;code&gt;t_nLastID&lt;/code&gt; 
&lt;dd&gt;用于 MRU 项的 ID 范围的最大 ID。此值必须大于 &lt;code&gt;t_nFirstID&lt;/code&gt;。&lt;/dl&gt;
&lt;p&gt;要把 MRU 特性添加到我们的应用里，需要做以下几步：
&lt;ol&gt;
&lt;li&gt;在我们希望 MRU 菜单项出现的地方插入一个 ID 为 &lt;code&gt;ID_FILE_MRU_FIRST&lt;/code&gt; 的菜单项。此项的文本当列表为空时会显示出来。 
&lt;li&gt;添加一个 ID 为 &lt;code&gt;ATL_IDS_MRU_FILE&lt;/code&gt;  的字符串表项。此字符串用作 MRU 项被选中时的动态帮助。如果你使用 WTL AppWizard，则此字符串已经帮你创建好了。 
&lt;li&gt;添加一个 &lt;code&gt;CRecentDocumentList&lt;/code&gt; 对象到 &lt;code&gt;CMainFrame&lt;/code&gt; 中。 
&lt;li&gt;在 &lt;code&gt;CMainFrame::Create()&lt;/code&gt; 里初始化该对象。 
&lt;li&gt;处理命令 ID 介于 &lt;code&gt;ID_FILE_MRU_FIRST&lt;/code&gt; 和 &lt;code&gt;ID_FILE_MRU_LAST&lt;/code&gt; 之间（含）的 &lt;code&gt;WM_COMMAND&lt;/code&gt; 消息。 
&lt;li&gt;当打开一个 CAB 文件时更新 MRU 列表。 
&lt;li&gt;应用关闭时保存 MRU 列表。&lt;/ol&gt;
&lt;p&gt;记住，如果 &lt;code&gt;ID_FILE_MRU_FIRST&lt;/code&gt; 和 &lt;code&gt;ID_FILE_MRU_LAST&lt;/code&gt; 不适合你的应用的话，你还是可以更改 ID 范围的，只要生成 &lt;code&gt;CRecentDocumentListBase&lt;/code&gt; 的一个新的特化版本就可以。 
&lt;h3&gt;&lt;a&gt;&lt;/a&gt;设置 MRU 对象&lt;/h3&gt;
&lt;p&gt;第一步就是添加一个菜单项，以标明 MRU 项应该处于什么位置。通常是在 &lt;i&gt;File&lt;/i&gt; 菜单下，这也是我们的应用用到的。下面就是占位菜单项：
&lt;p&gt;&lt;img src="http://tkfiles.storage.msn.com/x1pC47KWjv0VYmhru0KKrOfU47SymQUeSFL5DQt_Yptf8SKfAx7xfU_BCrvC37JsPRzOqiFl5hiXwhBU9DEfeRrh653DgAI19cXlXIgVy0I_eYuOr9zfVrSCVTr_1-zsp0d55j9OgrhoQc"&gt;
&lt;p&gt;AppWizard 已经把 &lt;code&gt;ATL_IDS_MRU_FILE&lt;/code&gt; 字符串添加到了字符串表里，我们将之改为读作“打开此 CAB 文件”。接下来，我们向 &lt;code&gt;CMainFrame&lt;/code&gt; 中加入一个名为 &lt;code&gt;m_mru&lt;/code&gt; 的 &lt;code&gt;CRecentDocumentList&lt;/code&gt; 类型成员变量并在 &lt;code&gt;OnCreate()&lt;/code&gt; 中初始化它：&lt;pre&gt;&lt;span&gt;#define APP_SETTINGS_KEY \&lt;/span&gt;
    _T(&lt;span&gt;&amp;quot;software\\Mike's Classy Software\\WTLCabView&amp;quot;&lt;/span&gt;);
 
LRESULT CMainFrame::OnCreate ( LPCREATESTRUCT lpcs )
{
HWND hWndToolBar = CreateSimpleToolBarCtrl(...);
 
  CreateSimpleReBar ( ATL_SIMPLE_REBAR_NOBORDER_STYLE );
  AddSimpleReBarBand ( hWndToolBar );
 
  CreateSimpleStatusBar();
 
  m_hWndClient = m_view.Create ( m_hWnd, rcDefault );
  m_view.Init();
 
&lt;b&gt;  &lt;span&gt;// Init MRU list&lt;/span&gt;
CMenuHandle mainMenu = GetMenu();
CMenuHandle fileMenu = mainMenu.GetSubMenu(0);
 
  m_mru.SetMaxEntries(9);
  m_mru.SetMenuHandle ( fileMenu );
  m_mru.ReadFromRegistry ( APP_SETTINGS_KEY );&lt;/b&gt;
 
  &lt;span&gt;// ...&lt;/span&gt;
}&lt;/pre&gt;
&lt;p&gt;前两个方法设置了我们想在维持的项的数目（缺省为 16）以及包含占位项的菜单句柄。&lt;code&gt;ReadFromRegistry()&lt;/code&gt; 从注册表中把 MRU 列表读出。它接收我们传递给它的键名，并在其下创建一个新键来保存列表。当我们这里，键为 &lt;code&gt;HKCU\Software\Mike's Classy Software\WTLCabView\Recent Document List&lt;/code&gt;。
&lt;p&gt;加载文件列表之后，&lt;code&gt;ReadFromRegistry()&lt;/code&gt; 调用另一个 &lt;code&gt;CRecentDocumentList&lt;/code&gt; 方法，即 &lt;code&gt;UpdateMenu()&lt;/code&gt;，该方法会找到占位菜单项病将之以实际的 MRU 项代替。
&lt;h3&gt;&lt;a&gt;&lt;/a&gt;处理 MRU 命令并更新列表&lt;/h3&gt;
&lt;p&gt;当用户选择了某个 MRU 项时，主框架会收到一个 &lt;code&gt;WM_COMMAND&lt;/code&gt; 消息，命令 ID 等于菜单项的 ID。我们可以在消息映射中用一个宏来处理这些命令：&lt;pre&gt;  BEGIN_MSG_MAP(CMainFrame)
    COMMAND_RANGE_HANDLER_EX(
        ID_FILE_MRU_FIRST, ID_FILE_MRU_LAST, OnMRUMenuItem)
  END_MSG_MAP()&lt;/pre&gt;
&lt;p&gt;消息处理其从 MRU 对象处获取该项的全路径，然后调用 &lt;code&gt;ViewCab()&lt;/code&gt; 以使应用显示该文件的内容。&lt;pre&gt;void CMainFrame::OnMRUMenuItem (
  UINT uCode, int nID, HWND hwndCtrl )
{
CString sFile;
 
  if ( m_mru.GetFromList ( nID, sFile ) )
    ViewCab ( sFile, nID );
}&lt;/pre&gt;
&lt;p&gt;如上文提到的，我们要扩展 &lt;code&gt;ViewCab()&lt;/code&gt; 以使之感知 MRU 对象，并在必要时更新文件列表。新的原型为：&lt;pre&gt;  void ViewCab ( LPCTSTR szCabFilename, int nMRUID = 0 );&lt;/pre&gt;
&lt;p&gt;如果 &lt;code&gt;nMRUID&lt;/code&gt; 为 0，则 &lt;code&gt;ViewCab()&lt;/code&gt; 是被 &lt;code&gt;OnFileOpen()&lt;/code&gt; 调用的，否则的话，则是用户选择了某个 MRU 菜单项，&lt;code&gt;nMRUID&lt;/code&gt; 就是 &lt;code&gt;OnMRUMenuItem()&lt;/code&gt; 接收到的命令 ID。下面是更新过的代码：&lt;pre&gt;void CMainFrame::ViewCab ( LPCTSTR szCabFilename, int nMRUID )
{
  if ( EnumCabContents ( szCabFilename ) )
    {
    m_sCurrentCabFilePath = szCabFilename;
 
    &lt;b&gt;&lt;span&gt;// If this CAB file was already in the MRU list,&lt;/span&gt;
    &lt;span&gt;// move it to the top of the list. Otherwise,&lt;/span&gt;
    &lt;span&gt;// add it to the list.&lt;/span&gt;
    if ( 0 == nMRUID )
      m_mru.AddToList ( szCabFilename );
    else
      m_mru.MoveToTop ( nMRUID );
    }
  else
    {
    &lt;span&gt;// We couldn't read the contents of this CAB file,&lt;/span&gt;
    &lt;span&gt;// so remove it from the MRU list if it was in there.&lt;/span&gt;
    if ( 0 != nMRUID )
      m_mru.RemoveFromList ( nMRUID );
    }&lt;/b&gt;
}&lt;/pre&gt;
&lt;p&gt;当 &lt;code&gt;EnumCabContents()&lt;/code&gt; 成功了，我们就根据 CAB 文件是如何被选中的以不同的方式更新 MRU。如果是通过 &lt;i&gt;File-Open&lt;/i&gt; 选中的，我们就调用 &lt;code&gt;AddToList()&lt;/code&gt; 来把文件名加入到 MRU 列表里；如果是通过 MRU 菜单项选中的，我们就用 &lt;code&gt;MoveToTop()&lt;/code&gt; 把该项移动到列表的顶部。如果 &lt;code&gt;EnumCabContents()&lt;/code&gt; 失败，我们就用 &lt;code&gt;RemoveFromList()&lt;/code&gt; 把文件名从 MRU 列表中移除。所有这几个方法都会在内部调用 &lt;code&gt;UpdateMenu()&lt;/code&gt;，因此 &lt;i&gt;File&lt;/i&gt; 菜单会自动被更新。
&lt;h3&gt;&lt;a&gt;&lt;/a&gt;保存 MRU 列表&lt;/h3&gt;
&lt;p&gt;在应用关闭时，我们把 MRU 列表保存回注册表中。这事简单，只需要一行：&lt;pre&gt;  m_mru.WriteToRegistry ( APP_SETTINGS_KEY );&lt;/pre&gt;
&lt;p&gt;这行放到了 &lt;code&gt;CMainFrame&lt;/code&gt; 对 &lt;code&gt;WM_DESTROY&lt;/code&gt; 和 &lt;code&gt;WM_ENDSESSION&lt;/code&gt; 的消息处理器里。
&lt;h2&gt;&lt;a&gt;&lt;/a&gt;其他 UI Goodies&lt;/h2&gt;
&lt;h3&gt;&lt;a&gt;&lt;/a&gt;透明的拖动图像&lt;/h3&gt;
&lt;p&gt;Windows 2000 及以后有一个内建的 COM 对象，叫做拖放助手，其目的是在拖放操作中提供良好的透明拖动图像。拖动源通过 &lt;code&gt;IDragSourceHelper&lt;/code&gt; 接口使用此对象。下面是我们加入到 &lt;code&gt;OnListBeginDrag()&lt;/code&gt; 中的使用此助手对象的额外代码，用粗体标示：&lt;pre&gt;LRESULT CMainFrame::OnListBeginDrag(NMHDR* phdr)
{
&lt;b&gt;NMLISTVIEW* pnmlv = (NMLISTVIEW*) phdr;
CComPtr&amp;lt;IDragSourceHelper&amp;gt; pdsh;&lt;/b&gt;
vector&amp;lt;CDraggedFileInfo&amp;gt; vec;
CComObjectStack&amp;lt;CDragDropSource&amp;gt; dropsrc;
DWORD dwEffect = 0;
HRESULT hr;
 
  if ( !m_view.GetDraggedFileInfo(vec) )
    return 0;   &lt;span&gt;// do nothing&lt;/span&gt;
 
  if ( !dropsrc.Init(m_sCurrentCabFilePath, vec) )
    return 0;   &lt;span&gt;// do nothing&lt;/span&gt;
 
  &lt;b&gt;&lt;span&gt;// Create and init a drag source helper object&lt;/span&gt;
  &lt;span&gt;// that will do the fancy drag image when the user drags&lt;/span&gt;
  &lt;span&gt;// into Explorer (or another target that supports the&lt;/span&gt;
  &lt;span&gt;// drag/drop helper interface).&lt;/span&gt;
  hr = pdsh.CoCreateInstance ( CLSID_DragDropHelper );
 
  if ( SUCCEEDED(hr) )
    {
    CComQIPtr&amp;lt;IDataObject&amp;gt; pdo;
 
    if ( pdo = dropsrc.GetUnknown() )
      pdsh-&amp;gt;InitializeFromWindow ( m_view, &amp;amp;pnmlv-&amp;gt;ptAction, pdo );
    }&lt;/b&gt;
 
  &lt;span&gt;// Start the drag/drop!&lt;/span&gt;
  hr = dropsrc.DoDragDrop(DROPEFFECT_COPY, &amp;amp;dwEffect);
 
  &lt;span&gt;// ...&lt;/span&gt;
}&lt;/pre&gt;
&lt;p&gt;我们从创建此拖放助手 COM 对象开始。如果成功，我们就调用 &lt;code&gt;InitializeFromWindow()&lt;/code&gt; 并传递三个参数：拖动源的窗口 &lt;code&gt;HWND&lt;/code&gt;，光标位置，和一个基于我们的 &lt;code&gt;CDragDropSource&lt;/code&gt; 对象的 &lt;code&gt;IDataObject&lt;/code&gt; 接口。拖放助手使用此接口保存其数据，而且，如果拖放目标也使用助手对象的话，此数据用来生成拖动图像。
&lt;p&gt;要使 &lt;code&gt;InitializeFromWindow()&lt;/code&gt; 可以工作，拖放源的窗口需要处理 &lt;code&gt;DI_GETDRAGIMAGE&lt;/code&gt; 消息，而且在对该消息的效应里，要创建一个用作拖动图像的位图。对我们而言幸运的是，列表视图控件支持这一特性，因此我们仅需很少的工作就可以得到拖动图像。下面是拖动图像的样子：
&lt;p&gt;&lt;img src="http://tkfiles.storage.msn.com/x1pC47KWjv0VYmhru0KKrOfUy3KQ98_vuUq3wztmWOeus6n7Fj0rq0Wv_UuKL3PEfbaDHIsll2jXd8vhrWD3JGxdOTZbF1Ud2TuC9I9vZ77S5YPlTxiqHkvpBChzOcEEKcuk7BAnUFc00c"&gt;
&lt;p&gt;如果我们使用一些别的不处理 &lt;code&gt;DI_GETDRAGIMAGE&lt;/code&gt; 的窗口做我们的视图类，我们就要自己来创建拖动图像并调用 &lt;code&gt;InitializeFromBitmap()&lt;/code&gt; 来把图像保存到拖放助手对象中。
&lt;h3&gt;&lt;a&gt;&lt;/a&gt;透明的选择矩形&lt;/h3&gt;
&lt;p&gt;从 Windows XP 开始，列表视图控件可以显示一个透明的选择框。此特性缺省是关闭的，但通过给控件设置 &lt;code&gt;LVS_EX_DOUBLEBUFFER&lt;/code&gt; 风格即可启用。我们的应用把这件事在 &lt;code&gt;CWTLCabViewView::Init()&lt;/code&gt; 中作为视图窗口初始化工作的一部分来做。下面是成果：
&lt;p&gt;&lt;img src="http://tkfiles.storage.msn.com/x1pC47KWjv0VYmhru0KKrOfU6ejYYL51sezDM6U64RiFkiJBjvRt8smsmOQ_s2IhRfGbScDCn3gxucY8U8yLMBi2ZDCy17QHVVz5gaW6PKQWuFx-76FCaxaCL_toC6EntHm-75MyhLF8x4"&gt;
&lt;p&gt;如果没有显示出透明选择框，那就需要检查你的系统属性，确保下面的特性是启用了的：
&lt;p&gt;&lt;img src="http://tkfiles.storage.msn.com/x1pC47KWjv0VYmhru0KKrOfU41m0R7JkD-X8PaVWtYzPrpATt9W6ruVQ1nmy73VmnmGxu_IArmv_45BhBHWNCuh-b2CElEe2bHL98JNFkflncZWf7-7vrYe5Z7yNqxpIFdU-EUdWMnWMZU"&gt;
&lt;h3&gt;&lt;a&gt;&lt;/a&gt;标示排序的列&lt;/h3&gt;
&lt;p&gt;在 Windows XP 及之后，详细信息模式的列表视图控件有一个被选中的列，以不同的背景颜色显示。这一特性通常用来标示那一列是被排序了的，而这也正是我们的 CAB 查看器要做的。标头控件也有了两个新的格式风格，使得在一列中标头可以显示一个向上或者向下的箭头。这通常用来显示排序的方向。
&lt;p&gt;视图类在 &lt;code&gt;LVN_COLUMNCLICK&lt;/code&gt; 处理器中处理了排序。显示排序列的代码用粗体作了加亮：&lt;pre&gt;LRESULT CWTLCabViewView::OnColumnClick ( NMHDR* phdr )
{
int nCol = ((NMLISTVIEW*) phdr)-&amp;gt;iSubItem;
 
  &lt;span&gt;// If the user clicked the column that is already sorted,&lt;/span&gt;
  &lt;span&gt;// reverse the sort direction. Otherwise, go back to&lt;/span&gt;
  &lt;span&gt;// ascending order.&lt;/span&gt;
  if ( nCol == m_nSortedCol )
    m_bSortAscending = !m_bSortAscending;
  else
    m_bSortAscending = true;
 
  &lt;b&gt;if ( g_bXPOrLater )
    {
&lt;/b&gt;    &lt;b&gt;HDITEM hdi = { HDI_FORMAT };
    CHeaderCtrl wndHdr = GetHeader();&lt;/b&gt;
 &lt;b&gt;
    &lt;span&gt;// Remove the sort arrow indicator from the&lt;/span&gt;
    &lt;span&gt;// previously-sorted column.&lt;/span&gt;
    if ( -1 != m_nSortedCol )
      {
      wndHdr.GetItem ( m_nSortedCol, &amp;amp;hdi );
      hdi.fmt &amp;amp;= ~(HDF_SORTDOWN | HDF_SORTUP);
      wndHdr.SetItem ( m_nSortedCol, &amp;amp;hdi );
      }
 
    &lt;span&gt;// Add the sort arrow to the new sorted column.&lt;/span&gt;
    hdi.mask = HDI_FORMAT;
    wndHdr.GetItem ( nCol, &amp;amp;hdi );
    hdi.fmt |= m_bSortAscending ? HDF_SORTUP : HDF_SORTDOWN;
    wndHdr.SetItem ( nCol, &amp;amp;hdi );
    }&lt;/b&gt;
 
  &lt;span&gt;// Store the column being sorted, and do the sort&lt;/span&gt;
  m_nSortedCol = nCol;
 
  SortItems ( SortCallback, (LPARAM)(DWORD_PTR) this );
 
  &lt;b&gt;&lt;span&gt;// Indicate the sorted column.&lt;/span&gt;
  if ( g_bXPOrLater )
    SetSelectedColumn ( nCol );&lt;/b&gt;
 
  return 0;
}&lt;/pre&gt;
&lt;p&gt;加亮代码的第一小节去除了先前的排序列的排序箭头。如果没有排序的列的话，就省略这一步。然后，把箭头添加到用户刚刚点击的列上。如果以升序排序则箭头朝上，降序则朝下。排序结束后，我们调用 &lt;code&gt;SetSelectedColumn()&lt;/code&gt; —— 对 &lt;code&gt;LVM_SETSELECTEDCOLUMN&lt;/code&gt; 消息的一个封装 —— 来把选中列设置为我们刚刚排序的列。
&lt;p&gt;以下是文件以大小排序后的列表控件的样子：
&lt;p&gt;&lt;img src="http://tkfiles.storage.msn.com/x1pC47KWjv0VYmhru0KKrOfUzJ8FTKjLGCH9LK2oeVTX6xUGGjd_rQoz5HMqvhlpR2IBHFBq7rmHWSazWTk_5aIFuMUt7E2zarLW62IETLdNtvl8PDhDTSrjMOtT3DS0aW4rsBgVorOmTg"&gt;
&lt;h3&gt;&lt;a&gt;&lt;/a&gt;使用平铺视图模式&lt;/h3&gt;
&lt;p&gt;在 Windows XP 及之后，列表视图控件还有一种新的风格称为&lt;i&gt;平铺视图模式&lt;/i&gt;。作为视图窗口初始化的一部分，如果应用是运行于 XP 或之后上，它就会使用 &lt;code&gt;SetView()&lt;/code&gt; （对 &lt;code&gt;LVM_SETVIEW&lt;/code&gt; 消息的一个封装）把列表视图的模式设置为平铺模式。然后再填充一个 &lt;code&gt;LVTILEVIEWINFO&lt;/code&gt; 结构来设置一些控制如何平铺绘制的属性。&lt;code&gt;cLines&lt;/code&gt; 属性被设为了 2，表示要在图标旁边显示两行附加文本。&lt;code&gt;dwFlags&lt;/code&gt; 成员设置为了 &lt;code&gt;LVTVIF_AUTOSIZE&lt;/code&gt;，这使得控件自身的大小改变时同时也改变平铺区域的大小。&lt;pre&gt;void CWTLCabViewView::Init()
{
  &lt;span&gt;// ...&lt;/span&gt;
 
  &lt;span&gt;// On XP, set some additional properties of the list ctrl.&lt;/span&gt;
  if ( g_bXPOrLater )
    {
    &lt;span&gt;// Turning on LVS_EX_DOUBLEBUFFER also enables the&lt;/span&gt;
    &lt;span&gt;// transparent selection marquee.&lt;/span&gt;
    SetExtendedListViewStyle ( LVS_EX_DOUBLEBUFFER,
                               LVS_EX_DOUBLEBUFFER );
 
&lt;b&gt;    &lt;span&gt;// Default to tile view.&lt;/span&gt;
    SetView ( LV_VIEW_TILE );
 
    &lt;span&gt;// Each tile will have 2 additional lines (3 lines total).&lt;/span&gt;
    LVTILEVIEWINFO lvtvi = { sizeof(LVTILEVIEWINFO),
                             LVTVIM_COLUMNS };
 
    lvtvi.cLines = 2;
    lvtvi.dwFlags = LVTVIF_AUTOSIZE;
    SetTileViewInfo ( &amp;amp;lvtvi );&lt;/b&gt;
    }
}&lt;/pre&gt;
&lt;h4&gt;&lt;a&gt;&lt;/a&gt;设置平铺视图的图像列表&lt;/h4&gt;
&lt;p&gt;在平铺视图模式下，我们会使用特大系统图形列表（在缺省的显示设置下图标为 48x48 大小）。我们使用 &lt;code&gt;SHGetImageList()&lt;/code&gt; API 获取此图像列表。&lt;code&gt;SHGetImageList()&lt;/code&gt; 不同于 &lt;code&gt;SHGetFileInfo()&lt;/code&gt; 的是它返回一个基于图像列表对象的 COM 接口。视图窗口有两个成员变量用来管理此图像列表：&lt;pre&gt;  CImageList m_imlTiles;         &lt;span&gt;// the image list handle&lt;/span&gt;
  CComPtr&amp;lt;IImageList&amp;gt; m_TileIml; &lt;span&gt;// COM interface on the image list&lt;/span&gt;&lt;/pre&gt;
&lt;p&gt;视图窗口在 &lt;code&gt;InitImageLists()&lt;/code&gt; 中获取特大图像列表：&lt;pre&gt;HRESULT (WINAPI* pfnGetImageList)(int, REFIID, void**);
HMODULE hmod = GetModuleHandle ( _T(&lt;span&gt;&amp;quot;shell32&amp;quot;&lt;/span&gt;) );
 
  (FARPROC&amp;amp;) pfnGetImageList = GetProcAddress(hmod, &lt;span&gt;&amp;quot;SHGetImageList&amp;quot;&lt;/span&gt;);
 
  hr = pfnGetImageList ( SHIL_EXTRALARGE, IID_IImageList,
                         (void**) &amp;amp;m_TileIml );
 
  if ( SUCCEEDED(hr) )
    {
    &lt;span&gt;// HIMAGELIST and IImageList* are interchangeable,&lt;/span&gt;
    &lt;span&gt;// so this cast is OK.&lt;/span&gt;
    m_imlTiles = (HIMAGELIST)(IImageList*) m_TileIml;
    }&lt;/pre&gt;
&lt;p&gt;如果 &lt;code&gt;SHGetImageList()&lt;/code&gt; 成功，我们可以把 &lt;code&gt;IImageList*&lt;/code&gt; 接口转型为一个 &lt;code&gt;HIMAGELIST&lt;/code&gt;，再像使用其它图像列表一样使用它。
&lt;h4&gt;&lt;a&gt;&lt;/a&gt;使用平铺视图的图像列表&lt;/h4&gt;
&lt;p&gt;由于列表控件并不能为平铺视图模式持有单独的图像列表，所以我们需要在运行时，当用户选择大图标或者平铺视图模式时改变图像列表。视图类有一个 &lt;code&gt;SetViewMode()&lt;/code&gt; 方法来处理改变图像列表和视图风格的事宜：&lt;pre&gt;void CWTLCabViewView::SetViewMode ( int nMode )
{
  if ( g_bXPOrLater )
    {
    if ( LV_VIEW_TILE == nMode )
      SetImageList ( m_imlTiles, LVSIL_NORMAL );
    else
      SetImageList ( m_imlLarge, LVSIL_NORMAL );
 
    SetView ( nMode );
    }
  else
    {
    &lt;span&gt;// omitted - no image list changing necessary on&lt;/span&gt;
    &lt;span&gt;// pre-XP, just modify window styles&lt;/span&gt;
    }
}&lt;/pre&gt;
&lt;p&gt;如果控件即将进入平铺视图模式，那我们就把控件的图像列表设置为 48x48 的那个，否则设置为 32x32 的那个。
&lt;h4&gt;&lt;a&gt;&lt;/a&gt;设置附加的文本行&lt;/h4&gt;
&lt;p&gt;在初始化时，我们将平铺效果设置为要显示附加的两行文本。第一行总是项的文字，就像在大图标和小图标模式里一样。显示在两行附加的文本里的文字取自于子项，类似于详细信息模式下的列。我们可以为每个图标设置单独的子项。下面就是在 &lt;code&gt;AddFile()&lt;/code&gt; 中视图是怎样设置文本的：&lt;pre&gt;  &lt;span&gt;// Add a new list item.&lt;/span&gt;
int nIdx;
 
  nIdx = InsertItem ( GetItemCount(), szFilename, info.iIcon );
  SetItemText ( nIdx, 1, info.szTypeName );
  SetItemText ( nIdx, 2, szSize );
  SetItemText ( nIdx, 3, sDateTime );
  SetItemText ( nIdx, 4, sAttrs );
 
  &lt;b&gt;&lt;span&gt;// On XP+, set up the additional tile view text for the item.&lt;/span&gt;
  if ( g_bXPOrLater )
    {
    UINT aCols[] = { 1, 2 };
    LVTILEINFO lvti = { sizeof(LVTILEINFO), nIdx,
                        countof(aCols), aCols };
 
    SetTileInfo ( &amp;amp;lvti );
    }&lt;/b&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;aCols&lt;/code&gt; 数组保存有子项，其文本将会被显示出来，在这儿我们显示子项 1 （文件类型）以及 2 （文件大小）。下面是在平铺视图模式下查看器的样子：
&lt;p&gt;&lt;img src="http://tkfiles.storage.msn.com/x1pC47KWjv0VYmhru0KKrOfU0s2tcKZEQIVwZuzMloSzlQdDpGD2A3TXKWgR_cQOOCqveRMqntAJ0c4YqicXl18NQn6Yn0r9Id8u7LrIGlmEAhaVXkMLJJ6d8gIW7AfrjyR5BLQj_l14kU"&gt;
&lt;p&gt;注意，附加行当你在详细信息模式下排序某列之后会改变。当使用 &lt;code&gt;LVM_SETSELECTEDCOLUMN&lt;/code&gt; 设置了选中列时，该子项的文字总是最先显示，覆盖了我们传递到 &lt;code&gt;LVTILEINFO&lt;/code&gt; 结构中的子项设置。
&lt;h2&gt;&lt;a&gt;&lt;/a&gt;Copyright and license&lt;/h2&gt;
&lt;p&gt;This article is copyrighted material, ©2006 by Michael Dunn. I realize this isn't going to stop people from copying it all around the 'net, but I have to say it anyway. If you are interested in doing a translation of this article, please email me to let me know. I don't foresee denying anyone permission to do a translation, I would just like to be aware of the translation so I can post a link to it here.
&lt;p&gt;The demo code that accompanies this article is released to the public domain. I release it this way so that the code can benefit everyone. (I don't make the article itself public domain because having the article available only on CodeProject helps both my own visibility and the CodeProject site.) If you use the demo code in your own application, an email letting me know would be appreciated (just to satisfy my curiosity about whether folks are benefitting from my code) but is not required. Attribution in your own source code is also appreciated but not required. 
&lt;h2&gt;&lt;a&gt;&lt;/a&gt;修订历史&lt;/h2&gt;
&lt;p&gt;2006 年 6 月 16 日：首次发布&lt;/div&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br&gt;链接：&lt;a href="http://sluttery.spaces.msn.com/blog/cns!3569FEA80C717FD4!3154.entry"&gt;上一部分&lt;/a&gt;&lt;/div&gt;&lt;img src="http://c.services.spaces.live.com/CollectionWebService/c.gif?cid=3848887354281525204&amp;page=RSS%3a+%e8%af%91%ef%bc%9aMFC+%e7%a8%8b%e5%ba%8f%e5%91%98%e7%9a%84+WTL+%e6%95%99%e7%a8%8b%ef%bc%88%e5%8d%81%ef%bc%89%ef%bc%88%e7%ac%ac%e4%ba%8c%e7%89%88%ef%bc%89&amp;referrer=" width="1px" height="1px" border="0" alt=""&gt;&lt;img style="position:absolute" alt="" width="0px" height="0px" src="http://c.live.com/c.gif?NC=31263&amp;amp;NA=1149&amp;amp;PI=73329&amp;amp;RF=&amp;amp;DI=3919&amp;amp;PS=85545&amp;amp;TP=sluttery.spaces.live.com&amp;amp;GT1=sluttery"&gt;</description><comments>http://sluttery.spaces.live.com/Blog/cns!3569FEA80C717FD4!3227.entry#comment</comments><guid isPermaLink="true">http://sluttery.spaces.live.com/Blog/cns!3569FEA80C717FD4!3227.entry</guid><pubDate>Thu, 03 Aug 2006 12:31:30 GMT</pubDate><slash:comments>0</slash:comments><msn:type>blogentry</msn:type><live:type>blogentry</live:type><live:typelabel>Blog entry</live:typelabel><wfw:commentRss>http://sluttery.spaces.live.com/blog/cns!3569FEA80C717FD4!3227/comments/feed.rss</wfw:commentRss><wfw:comment>http://sluttery.spaces.live.com/Blog/cns!3569FEA80C717FD4!3227.entry#comment</wfw:comment><dcterms:modified>2006-08-03T12:33:46Z</dcterms:modified></item><item><title>译：MFC 程序员的 WTL 教程（九）（第二版）</title><link>http://sluttery.spaces.live.com/Blog/cns!3569FEA80C717FD4!3154.entry</link><description>&lt;p&gt;
&lt;p&gt;&lt;font color="#ff0000"&gt;特别注：由于本页内容栏宽度不够，会导致部分内容看不见，请点击&lt;a href="http://sluttery.spaces.msn.com/blog/cns!3569FEA80C717FD4!3154.entry"&gt;这里&lt;/a&gt;以获得最佳浏览效果。&lt;/font&gt;
&lt;p&gt;链接：&lt;a href="http://spaces.msn.com/sluttery/blog/cns!3569FEA80C717FD4!2493.entry"&gt;上一部分&lt;/a&gt;；&lt;a href="http://sluttery.spaces.live.com/blog/cns!3569FEA80C717FD4!3227.entry"&gt;下一部分&lt;/a&gt;
&lt;div style="overflow:scroll;width:100%"&gt;&lt;b&gt;&lt;font style="font-size:16pt" size=4&gt;第九部分 - GDI 类，公用对话框以及工具类&lt;/font&gt;&lt;/b&gt; 
&lt;p&gt;
&lt;p&gt;
&lt;table cellspacing=0 cellpadding=1 border=0&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td valign=top width="100%"&gt;
&lt;div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://www.codeproject.com/wtl/WTL4MFC9/WTL4MFC9_demo.zip"&gt;下载示例工程 - 157KB&lt;/a&gt; &lt;/ul&gt;
&lt;h2&gt;内容&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;简介 
&lt;li&gt;GDI 封装类 
&lt;ul&gt;
&lt;li&gt;封装类里的公用函数 
&lt;li&gt;使用 CDCT 
&lt;li&gt;与 MFC 封装类的差异 &lt;/ul&gt;
&lt;li&gt;资源加载函数 
&lt;li&gt;使用公用对话框 
&lt;ul&gt;
&lt;li&gt;CFileDialog 
&lt;li&gt;CFolderDialog &lt;/ul&gt;
&lt;li&gt;其他有用的类和全局函数 
&lt;ul&gt;
&lt;li&gt;Struct 的封装 
&lt;li&gt;处理双类型参数的类 
&lt;li&gt;其他工具类 
&lt;li&gt;全局函数 
&lt;li&gt;宏 &lt;/ul&gt;
&lt;li&gt;示例工程 
&lt;li&gt;版权和许可 
&lt;li&gt;修订历史 &lt;/ul&gt;
&lt;h2&gt;&lt;a&gt;&lt;/a&gt;简介&lt;/h2&gt;
&lt;p&gt;WTL 里包含了好多封装类和工具类，而直到现在也还没有在本系列里进行过全面的介绍，比如说 &lt;code&gt;CString&lt;/code&gt; 和 &lt;code&gt;CDC&lt;/code&gt;。WTL 具有一个封装 GDI 对象的良好体系，一些用以加载资源的便利函数，以及更便于使用某些 Win32 公用对话框的类。在此第九部分里，我将介绍一些使用最广泛的类。
&lt;p&gt;本文讨论了四类特性：
&lt;ol&gt;
&lt;li&gt;GDI 封装类 
&lt;li&gt;资源加载函数 
&lt;li&gt;使用打开文件公用对话框以及文件夹选择公用对话框 
&lt;li&gt;其他有用的类以及全局函数 &lt;/ol&gt;
&lt;p&gt;
&lt;h2&gt;&lt;a&gt;&lt;/a&gt;GDI 封装类&lt;/h2&gt;
&lt;p&gt;相较于 MFC，WTL 为其 GDI 封装类使用了一种相当不同的方法。WTL 的方法是，为每种类型的 GDI 对象准备一个模板类，每个模板都有一个名为 &lt;code&gt;t_bManaged&lt;/code&gt; 的 &lt;code&gt;&lt;span&gt;bool&lt;/span&gt;&lt;/code&gt; 参数。此参数控制着该类的实例是否“管理”着（或者说是拥有）被封装的 GDI 对象。如果 &lt;code&gt;t_bManaged&lt;/code&gt; 为 &lt;span&gt;&lt;code&gt;false&lt;/code&gt;&lt;/span&gt;，则 C++ 对象不会管理 GDI 对象的生命期，C++ 对象仅是围绕 GDI 对象句柄的一个简单封装层。如果 &lt;code&gt;t_bManaged&lt;/code&gt; 为 &lt;span&gt;&lt;code&gt;true&l