日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 运维知识 > windows >内容正文

windows

[转]Windows Shell 编程 第十四章【来源:http://blog.csdn.net/wangqiulin123456/article/details/7988010】...

發(fā)布時(shí)間:2025/3/15 windows 55 豆豆
生活随笔 收集整理的這篇文章主要介紹了 [转]Windows Shell 编程 第十四章【来源:http://blog.csdn.net/wangqiulin123456/article/details/7988010】... 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

第十四章?設(shè)計(jì)Shell集成應(yīng)用

有一些工具可以使應(yīng)用程序更緊密地與Shell和底層系統(tǒng)進(jìn)行集成。也就是說,用戶可以象處理系統(tǒng)文檔和程序那樣處理你的文檔和程序。例如,右擊文件來顯示可用功能列表等。Windows為每一個(gè)文件提供默認(rèn)的功能集,如‘打開…’,‘屬性’,‘拷貝’等。是否能為特定的文檔增加特殊功能。為此,我們必須客戶化這個(gè)文檔類的關(guān)聯(lián)菜單。

另一個(gè)應(yīng)該與Shell集成的例子是:假設(shè)你的程序有建立空文檔的能力,用戶使用系統(tǒng)的‘新建’菜單項(xiàng)在任一文件夾上飛快地建立新文檔,要想如此,就必須在系統(tǒng)注冊(cè)表中記入一些信息。

當(dāng)然,這是特殊情形,作為開發(fā)人員和應(yīng)用設(shè)計(jì)人員還應(yīng)該重視許多其它有用的特征。在這一章中我們將討論Shell集成的各方面技術(shù),以幫助開發(fā)者將應(yīng)用與系統(tǒng)Shell無縫集成,使你的產(chǎn)品更專業(yè)。這些技術(shù)包括:

?????????怎樣客戶化關(guān)聯(lián)菜單

?????????怎樣注冊(cè)新文件類型

?????????怎樣設(shè)計(jì)和編程處理命令行

?????????怎樣編程定制‘打開’對(duì)話框

我們將設(shè)計(jì)一個(gè)基于文檔的全屬性應(yīng)用,這個(gè)應(yīng)用顯示和打印所有Windows支持的元文件,從傳統(tǒng)的(*.wmf)到增強(qiáng)的(*.emf)。我們將應(yīng)用所有我們前面討論過的理論技術(shù),以及Shell對(duì)元文件的豐富支持。

?

Shell集成應(yīng)用

?????????頭一個(gè)問題顯然是Shell集成應(yīng)用的組成結(jié)構(gòu)是怎樣的。就Win32的面向文檔程序概念,有幾個(gè)關(guān)系到系統(tǒng)Shell的特征需要給出。簡單地說,有三組定義Shell集成應(yīng)用的特征:

?????????為程序處理的任何文檔注冊(cè)圖標(biāo)和類型名

?????????為程序處理的文檔客戶化關(guān)聯(lián)菜單

?????????在系統(tǒng)的‘新建’菜單中可能有一個(gè)或多個(gè)客戶化條目

?????????單一程序?qū)嵗?/p>

?????????對(duì)每一個(gè)打開過的文檔在‘最近文檔’文件夾中有一個(gè)新條目

?????????完全支持長文件名,尤其是對(duì)于用戶文檔。

除了這些基本特征外,我們還需增加一些不常使用的特征:

?????????在系統(tǒng)的‘發(fā)送到’菜單中客戶化一個(gè)或多個(gè)條目

?????????在‘開始’和‘程序’菜單中客戶化一個(gè)或多個(gè)條目

?????????在‘Favorites’文件夾中客戶化一個(gè)或多個(gè)項(xiàng)

?????????在桌面上生成一個(gè)或多個(gè)新快捷方式

?????????有一個(gè)應(yīng)用桌面工具條來集中程序的所有主要功能

?????????客戶化某些系統(tǒng)公共對(duì)話框

?????????在用戶下一次登錄時(shí)注冊(cè)應(yīng)用自動(dòng)啟動(dòng)

第三組特征主要受限于特定安裝器,如InstallShield?和WISE,它們是:

?????????拷貝共享文件到系統(tǒng)公共路徑

?????????在‘程序文件’文件夾下安裝應(yīng)用

?????????提供卸載程序

?????????探測(cè)Shell的應(yīng)用路徑名來定義查找文件的路徑

這些要求來自于Windows標(biāo)記規(guī)范,然而對(duì)于Windows環(huán)境下更高層次的抽象概念而言它們是最基礎(chǔ)的:為了打開和使用文檔,用戶不必知道實(shí)際裝入和顯示文檔的是什么程序,只需找到和雙擊文檔的圖標(biāo)和這個(gè)文檔的名字即可(實(shí)際在桌面設(shè)置中也可以設(shè)置單機(jī)打開方式)。

?

文檔和Shell

?????????在Windows95發(fā)布以后,文檔成為系統(tǒng)Shell更中心的角色,文檔已經(jīng)成為了主角,而實(shí)際處理它的程序則縮減成配角。甚至它們?cè)谟脖P驅(qū)動(dòng)器上的位置都標(biāo)明其狀態(tài)的低下:程序被分組在‘程序文件’文件夾下,每一個(gè)都有自己的子文件夾,以及一個(gè)存儲(chǔ)DLL和其它幫助文件的子目錄。這樣的許多文件夾都是隱藏的——這進(jìn)一步確認(rèn)程序相對(duì)于文檔是次要的。

?????

?

查看上面的截圖,我們可以看到文檔有它們自己的圖標(biāo)和描述,更進(jìn)一步,每一個(gè)文檔都有專門的關(guān)聯(lián)菜單,從菜單中可以執(zhí)行一些Shell功能。有些功能可以應(yīng)用于各種文檔,因而出現(xiàn)在所有文檔的關(guān)聯(lián)菜單中,有些則是個(gè)別文檔類型所具有的。

?

基本的文檔功能

?????????Windows Shell提供一些菜單動(dòng)詞,可以自由使用,它們是:

???????????????????拷貝、剪切、粘貼

???????????????????刪除

???????????????????重命名

???????????????????建立快捷方式

???????????????????屬性

此外,還有兩個(gè)菜單命令,‘打開’或‘打開方式…’,但是這兩個(gè)是相互排斥的——后者僅僅在沒有注冊(cè)程序打開指定文檔時(shí)出現(xiàn),并且導(dǎo)出下面的對(duì)話框:

??????????????????????

?

?

相反‘打開’命令則依賴于存儲(chǔ)在注冊(cè)表中的條目內(nèi)容,這就是我們將要討論的。

?

‘發(fā)送到’命令

?????????另一個(gè)我們總能看到的命令是‘發(fā)送到’,它有一個(gè)子菜單。這個(gè)子菜單列出了選中文檔可能的目的地。‘目的地’是一個(gè)在命令行上接收給定文件名的程序。下圖說明‘發(fā)送到’菜單是怎樣把文件設(shè)置成新郵件的附件:

???????????????????

?

?

通過我們?cè)谶@一節(jié)中列出的命令,Shell保證了對(duì)PC上各種文檔的最低層支持。對(duì)于有經(jīng)驗(yàn)的用戶和軟件工程師,這就等于提供了使用更多的文檔特定特征擴(kuò)展這些基本行為的機(jī)會(huì)。

?

注冊(cè)文檔類型

?????????關(guān)于Shell構(gòu)造的所有信息都存儲(chǔ)在系統(tǒng)注冊(cè)表中,所以修改Shell表現(xiàn)或行為的任何方法都必須通過注冊(cè)表。

?????????為了使Shell識(shí)別和適當(dāng)處理一定種類的文檔,它就必須是一個(gè)注冊(cè)類型。一個(gè)文檔的類型由它的文件名的擴(kuò)展所標(biāo)識(shí),而且所有注冊(cè)的文檔類型都存儲(chǔ)在HKEY_CLASSES_ROOT注冊(cè)表節(jié)點(diǎn)下:

?????????????

?

文件擴(kuò)展(.ext)的條目指向同一個(gè)節(jié)點(diǎn)下的另一個(gè)鍵,其名字存儲(chǔ)在.ext的默認(rèn)值中,在上圖中,對(duì)EML文件(微軟郵件消息,Outlook Express格式文件),其值為:

Microsoft Internet Mail Message

如果需要獲取這個(gè)文檔類型的注冊(cè)信息,你就必須探索:

HKEY_CLASSES_ROOT

/Microsoft Internet Mail Message

?

?????????

?

在這個(gè)鍵下,存儲(chǔ)了應(yīng)用于三個(gè)方面的信息:

???????????????????用戶接口

???????????????????關(guān)聯(lián)菜單

???????????????????Shell擴(kuò)展

?

文檔的Shell用戶接口

?????????從這一節(jié)的標(biāo)題可以看出,我們主要是想說明設(shè)置文檔的圖像屬性——即圖標(biāo)和類型名。‘DefaultIcon’鍵使你可以指派圖標(biāo)到特定類型的所有文件。這個(gè)鍵的Default值包含一個(gè)如下格式的字符串:

C:/PROGRAMS/THEPROG.EXE,0

再次注意,這個(gè)信息不是存儲(chǔ)在.ext鍵下的,而是在.ext指向的鍵下。

????這個(gè)串標(biāo)識(shí)默認(rèn)圖標(biāo)由全路徑名、逗號(hào)和索引號(hào)構(gòu)成。要顯示的圖標(biāo)是給定文件中指定索引號(hào)的圖標(biāo)——記住圖標(biāo)索引總是從0開始的。如果索引是負(fù)數(shù),則表示是一個(gè)資源ID,比如對(duì)于EML,DefaultIcon串為:

C:/PROGRAM FILES/OUTLOOK EXPRESS/MSIMN.EXE,-4

正象上面顯示的,主鍵(上面示例中是Microsoft Internet Mail Message)的默認(rèn)值包含了用于這個(gè)文檔類型名的串。要修改這個(gè)設(shè)置并不需要專門的Windows程序員進(jìn)行,任何老練的Windows用戶都可以改變EML文件的描述,或表示的圖標(biāo)。然而要編程地插入鍵來注冊(cè)文檔完全不同于手動(dòng)修改注冊(cè)表操作。我們需要集中討論軟件自動(dòng)集成其文檔與Shell的操作關(guān)系。

?

關(guān)聯(lián)菜單的特定文檔命令

?????????命名為Shell的鍵可以包含一定數(shù)量的子鍵,每一個(gè)子鍵都對(duì)應(yīng)一個(gè)特殊的命令,這是將要顯示在文檔關(guān)聯(lián)菜單上的。在Shell鍵下的這些鍵稱為動(dòng)詞(此時(shí)的動(dòng)詞是‘打開’),這些鍵的默認(rèn)值包含了將要顯示在關(guān)聯(lián)菜單上的串。如果沒有設(shè)置,則菜單顯示鍵本身的名字。因此動(dòng)詞打開可以在菜單中顯示一個(gè)相當(dāng)不同的名字。在我們討論的郵件消息這個(gè)例子中,‘Read’命令在菜單中替代了‘打開’。如果你改變了默認(rèn)值的內(nèi)容并且調(diào)出關(guān)聯(lián)菜單,你將看到這個(gè)結(jié)果:

?????????????

?

?

關(guān)聯(lián)菜單顯示的是‘讀’,而實(shí)際行為一點(diǎn)也沒改變,因?yàn)檫@是在命令子鍵的默認(rèn)值中確立的。每一個(gè)動(dòng)詞必須有一個(gè)命令子鍵包含可執(zhí)行文件路徑和命令行,以及任何其它必要的設(shè)置。指定命令行以及適當(dāng)?shù)拈_關(guān),用%1表示要操作的文件也是十分重要的:

?

C:/PROGRAM FILES/OUTLOOK EXPRESS/MSIMN.EXE" /eml:%1

?

上面這行顯示EML文件的命令行——在你的機(jī)器上是否有相同的設(shè)置依賴于Outlook Express的安裝設(shè)置。

?

Shell對(duì)文檔的擴(kuò)展

?????????通過修改注冊(cè)表你可以添加靜態(tài)動(dòng)詞到文檔的關(guān)聯(lián)菜單。你所定義的任何靜態(tài)動(dòng)詞總是被顯示,并且總是執(zhí)行同樣的命令行。

?????????更靈活的動(dòng)態(tài)的行為可以通過Shell擴(kuò)展獲得,我們將在下一章中進(jìn)行討論。現(xiàn)在,我們可以說Shell擴(kuò)展是運(yùn)行在探測(cè)器地址空間中的一段代碼,每次探測(cè)器需要做某個(gè)客戶化行為時(shí)都調(diào)用這段代碼,如繪制圖標(biāo)、顯示關(guān)聯(lián)菜單等。你的代碼段給出一個(gè)動(dòng)態(tài)確定菜單項(xiàng)添加的機(jī)會(huì)和響應(yīng)用戶的點(diǎn)擊的操作。

?????????對(duì)于給定文檔類的所有Shell擴(kuò)展,都列出在shellex鍵下,它與Shell鍵同層。

?

怎樣影響程序

?????????我們現(xiàn)在接觸到了一定數(shù)量的影響文檔的屬性,并且在這一章的開始我們就說明了文檔是Windows Shell的重點(diǎn)。然而,我們并沒有脫離文檔仍然通過程序顯示這樣一個(gè)事實(shí)——問題是,通過我們致力的Shell集成怎樣和什么程度上影響到程序。

?????????有兩個(gè)重點(diǎn),首先是,用戶可以點(diǎn)擊重復(fù)打開不同的文檔,甚或是同一個(gè)文檔的多個(gè)拷貝。當(dāng)這種情況發(fā)生時(shí),程序被重復(fù)調(diào)用,所以,為了避免窗口增殖,你可能希望只允許運(yùn)行一個(gè)程序?qū)嵗F浯问?#xff0c;程序命令行的重要性,因?yàn)殪o態(tài)動(dòng)詞通常是通過命令行的開關(guān)實(shí)現(xiàn)的。你應(yīng)該以非常標(biāo)準(zhǔn)的方法輸出最重要的功能。

?????????在第11章中我們討論了RunDLL32模塊,它給出了通過命令行調(diào)用具有固定原型的動(dòng)態(tài)庫函數(shù)的好方法。在這兩種情形下程序的功能都必須明顯地隔離開,而且可容易地從外部模塊調(diào)用。

?????????當(dāng)某人在Windows Shell下點(diǎn)擊文檔時(shí),程序被調(diào)用,程序每次啟動(dòng),都檢查是否有運(yùn)行中的副本,如果有,則傳遞控制和命令行,而當(dāng)前的實(shí)例則退出,我們將在后面進(jìn)一步討論這個(gè)問題。

?

MDI?與?SDI

?????????MDISDI是Windows基于文件應(yīng)用的兩個(gè)典型設(shè)計(jì)。MDI表示多文檔界面,說明程序可以同時(shí)打開幾個(gè)文檔,每一個(gè)都有自己的窗口。相反,SDI是單文檔界面的首字母縮寫——SDI程序每次僅能打開一個(gè)文檔。傳統(tǒng)上主要的Windows應(yīng)用都是MDI形式的——Office套件是一個(gè)典型的MDI例子,而記事本和圖畫程序則是SDI的例子。

?????????從Shell的觀點(diǎn)上看,選擇MDI或SDI并不是實(shí)際的結(jié)果。然而在進(jìn)一步的探索后,你可能會(huì)認(rèn)識(shí)到對(duì)MDI和SDI之間差異的討論實(shí)際是要在一個(gè)更寬泛的對(duì)比上打開窗口:應(yīng)用為中心對(duì)比文檔為中心的環(huán)境。

?????????MDI方案由應(yīng)用來支配,是應(yīng)用打開和管理各個(gè)子文檔。反之SDI界面是更加文檔為中心的:你查看由可用工具環(huán)繞的單一文檔,可以使用這些工具來修改它。

?????????自從Windows95發(fā)布以后微軟就開始推薦盡可能使用SDI開發(fā)應(yīng)用,但是有許多人都對(duì)這個(gè)建議不感冒。

?

建立新文檔

?????????在任何時(shí)候你在探測(cè)器顯示文件夾內(nèi)容的窗口上右擊,都會(huì)有下圖樣式的菜單出現(xiàn):

???????????????????

?

?

‘New’命令列出了所有文檔類型,這些是可以經(jīng)由Shell建立的文檔類型。當(dāng)你選擇了一個(gè)列出的文檔類型后,Shell調(diào)用注冊(cè)的應(yīng)用,并請(qǐng)求它來建立一個(gè)新的文檔,其名字是一個(gè)來自文檔類型名(與它在菜單中的相同),前綴有一個(gè)‘New’字。例如,要建立一個(gè)新的bitmap圖像文檔,文件名默認(rèn)為:

???????????????????New Bitmap Image.bmp

?

New菜單

出現(xiàn)在‘New’菜單中的每一項(xiàng)(除了文件夾和快捷方式)都有相關(guān)的文件類對(duì)應(yīng)的ShellNew鍵存在,它在下面的注冊(cè)表路徑上:

HKEY_CLASSES_ROOT

/.ext

/ShellNew

ShellNew鍵的內(nèi)容確定了New菜單上顯示的內(nèi)容,以及當(dāng)點(diǎn)擊時(shí)所需要做的動(dòng)作。實(shí)際上有四種通過Shell建立新文檔的方法,你可以建立:

???????????????????空,零長度文檔

???????????????????從默認(rèn)文檔拷貝的文檔

???????????????????來自注冊(cè)表存儲(chǔ)的二進(jìn)制數(shù)據(jù)的文檔

???????????????????由特殊外部程序建立的文檔,例如建立大師軟件

很自然這些選擇要求不同的注冊(cè)表設(shè)置:

?

內(nèi)容

NullFile

空字符串

FileName

作為模板使用的文件名。這是假設(shè)這種文件是駐留在Windows/ShellNew目錄下的。

Data

一塊從注冊(cè)表讀出的二進(jìn)制數(shù)據(jù)

Command

建立文檔所需要的命令行

?

下面圖像顯示對(duì)機(jī)器上的BMP文件的設(shè)置:

?????????????????????

?

?

一般使用NULLFile設(shè)置,令應(yīng)用處理空或零長度文件。FileName設(shè)置與Word和Excel密切相關(guān),如果使用復(fù)雜的、混合文件作為文件所需要的最小結(jié)構(gòu),即使是空文件,這個(gè)設(shè)置是有用的。此時(shí),你可以準(zhǔn)備一個(gè)標(biāo)準(zhǔn)文件(空的或不空的),把它保存到FileNew值中,每次建立這種類型的新文件時(shí),就建立這個(gè)文件的一個(gè)拷貝。Data是可以包含二進(jìn)制數(shù)據(jù)的值,它們可以填充到新建立的文件中。這與FileNew的情況稍有不同,在FileNew中,模板是單獨(dú)文件,而Data,是一塊存儲(chǔ)在注冊(cè)表中的數(shù)據(jù)。

?????????在第11章討論置換快建立捷方式標(biāo)準(zhǔn)處理器時(shí)我們遇到過Command值。如果給出這個(gè)值,將限制Shell運(yùn)行指定的命令行,并斷定它將建立指定類型的新文檔。這個(gè)選項(xiàng)由逐步建立文檔的大師程序所特殊設(shè)定。

?????????下面我們將考察一個(gè)例子,其作用是添加一個(gè)命令到Shell的‘New’菜單,建立一個(gè)新的具有最小內(nèi)容的HTML文件。

?

建立HTML新文件

?????????我們假設(shè)在PC上注冊(cè)了一個(gè)處理HTML文件的程序。當(dāng)需要從腹稿建立新的HTML文檔時(shí),通常是:

???????????????????運(yùn)行可視HTML編輯器(例如微軟的FrontPage)

???????????????????運(yùn)行記事本或其它普通的文字編輯器

像絕大多數(shù)其它Windows文檔一樣,在你需要編寫HTML頁面時(shí),需要借助‘記事本’。然而,HTML文件不是ASCII文件,它需要有標(biāo)記來描述使它成為有效的瀏覽器可處理的文檔。最小HTML文件可以有如下形式:

<html>

<body>

</body>

</html>

保存這段代碼到一個(gè)命名為html4.htm的文件,并把它放置在Windows/ShellNew(或Winnt/ShellNew)目錄中。然后打開注冊(cè)表編輯器,添加ShellNew鍵到:

HKEY_CLASSES_ROOT

/.htm

下。這個(gè)新建立的鍵也必須給定FileName串值:

?

?

保存了這些設(shè)置后,你就能右擊桌面,激活菜單:

???????????????

?

?

這個(gè)圖說明在PC上改變了htmlfile注冊(cè)表鍵的初始描述為‘Web Page’之后任何從這個(gè)菜單項(xiàng)建立的新文件都將調(diào)用‘New Web Page.htm’。

????注意:只有在文件類型被正確地注冊(cè)之后,你才可以添加項(xiàng)目到‘New’菜單項(xiàng)。

?

其它特征

?????????要設(shè)計(jì)和編寫良好的Shell集成應(yīng)用,還有兩個(gè)特征需要注意。它們是:

???????????????????存儲(chǔ)目錄列表到輔助模塊如DLL可以找到的位置

???????????????????安排在下一次登錄時(shí)自動(dòng)運(yùn)行

第一個(gè)特征可能與設(shè)置程序有更大的關(guān)聯(lián),但是并不是所有安裝器都確實(shí)完成你所要求的任務(wù),因此需要你自己編寫功能擴(kuò)展,以及深入專研注冊(cè)表路徑。第二個(gè)特征典型是探測(cè)器和其他幾個(gè)應(yīng)用的特征。在關(guān)閉系統(tǒng)時(shí),如果應(yīng)用仍然在運(yùn)行,Shell將在下一次登錄時(shí)自動(dòng)重新啟動(dòng)它。現(xiàn)在就讓我們看一下怎樣編碼這個(gè)行為。

?

應(yīng)用路徑

?????????幾乎所有的Windows應(yīng)用都由一個(gè)或多個(gè)文件組成。典型地是有一個(gè)EXE文件和多個(gè)DLL(不是提及的系統(tǒng)Dlls,如kernel32.dll和user32.dll)。輔助的DLL必須由安裝器拷貝到某個(gè)地方。它們可以在‘程序’文件夾或其它地方,但是,微軟反對(duì)將DLL拷貝到系統(tǒng)主文件夾下,如Windows或Windows/System等。如果你確定不將DLL放到與EXE文件相同的目錄下,很快你就會(huì)獲得系統(tǒng)通知的錯(cuò)誤信息,說明系統(tǒng)不能定位指定的DLL。

?????????決定不把DLL放置到與EXE相同的文件夾下是為什么。因?yàn)槟愕膽?yīng)用可以是一套共享輔助DLL的很多程序的一部分,如果每一部分都備份這些DLL,顯然是一種浪費(fèi),所以,你可以建立一個(gè)公共文件夾,放置可共享的每一件東西到這個(gè)文件夾。現(xiàn)在的問題是怎樣使Shell知道它的存在——當(dāng)你導(dǎo)出需要確定的庫的應(yīng)用時(shí),你必須保證這個(gè)庫的路徑是全程可視的。

?????????MS-DOS基礎(chǔ)程序(Windows程序也一樣)依賴于PATH環(huán)境變量。類似地,對(duì)于Shell把這個(gè)置換成應(yīng)用路徑。要使用這個(gè)概念,在安裝了應(yīng)用之后,需要添加下面的注冊(cè)表鍵(比如對(duì)于Program.exe):

HKEY_LOCAL_MACHINE

/SOFTWARE

/Microsoft

/Windows

/CurrentVersion

/App Paths

/Program.exe

這個(gè)鍵的默認(rèn)值包含可執(zhí)行文件的全路徑名,如果給出,則路徑值列出所有可以查找其它文件的路徑:

?

?

應(yīng)用的自動(dòng)啟動(dòng)

?????????當(dāng)一個(gè)特殊用戶登錄時(shí),Windows將努力讀出下面的鍵:

HKEY_CURRENT_USER

/Software

/Microsoft

/Windows

/CurrentVersion

/RunOnce

如果這個(gè)鍵存在,名字被存儲(chǔ)在鍵值中的任何程序都將被執(zhí)行。在檢查過之后所有條目都將被刪除。所以,它們僅被執(zhí)行一次。據(jù)此,我們可以編碼使應(yīng)用能夠在下一次特殊用戶登錄時(shí)執(zhí)行。注意,這僅僅是在下一次登陸時(shí),不是每一次持續(xù)登陸時(shí)。要每一次特殊用戶登錄都運(yùn)行應(yīng)用有一個(gè)注冊(cè)表?xiàng)l目在上面位置的Run鍵下。自動(dòng)執(zhí)行不完全是系統(tǒng)特征,程序必須協(xié)同操作。特別是,程序添加其本身(或任何其它應(yīng)用)到RunOne鍵下,正確地作這個(gè)操作就是響應(yīng)WM_ENDSESSION消息。當(dāng)然,在任何時(shí)候都可以做,然而因?yàn)槲覀兊哪繕?biāo)是獲得持續(xù)的交互會(huì)話,所以,只有在關(guān)閉當(dāng)前會(huì)話后我們的程序仍然在運(yùn)行時(shí)才建立這個(gè)條目。這也WM_ENDSESSION消息到達(dá)時(shí)。關(guān)于應(yīng)用運(yùn)行的信息在注冊(cè)表中有如下格式:

ID = program name

你需要建立一個(gè)值,其內(nèi)容是可執(zhí)行程序的路徑名。ID必須是唯一的,但是與你指定什么名字并不重要。下圖顯示一個(gè)例子:

???????????

?

?

這些條目以它們鍵入的順序依次取出——這個(gè)順序不一定符合注冊(cè)表編輯器的輸出順序,在編輯器中條目是按字母順序顯示的。程序是一個(gè)接一個(gè)異步導(dǎo)出的,如果你有上面圖中顯示的注冊(cè)表?xiàng)l目設(shè)置,你就會(huì)發(fā)現(xiàn),當(dāng)你登錄時(shí)記事本程序在桌面上打開。

?

另一個(gè)RunOnce鍵

?????????還有另一個(gè)比RunOnce更強(qiáng)的RunOnce鍵在下面的路徑下:

HKEY_LOCAL_MACHINE

/SOFTWARE

/Microsoft

/Windows

/CurrentVersion

/RunOnce

使用這個(gè)鍵的語法實(shí)際與前面介紹的相同,但是,其工作方法有三方面不同,它們是:

?

???????????????????任何用戶登錄,這個(gè)鍵的內(nèi)容都被檢查

???????????????????各個(gè)注冊(cè)程序同步執(zhí)行——僅在前一程序完成之后,下一條目開始運(yùn)行。

???????????????????在這個(gè)鍵下注冊(cè)的程序運(yùn)行在HKEY_CURRENT_USER節(jié)點(diǎn)相同子鍵注冊(cè)的程序之前。

?

如果你在HKEY_LOCAL_MACHINE/.../RunOnce鍵下注冊(cè)了記事本程序,下一次登錄或重啟動(dòng)時(shí)記事本將在任務(wù)條和桌面圖標(biāo)之前出現(xiàn)在桌面上。更要緊的是你不能看到桌面和圖標(biāo),直道關(guān)閉這個(gè)窗口終止過程之后才可以。

?

運(yùn)行鍵

?????????Run鍵,我們?cè)谏厦嬗懻撝信R時(shí)提及的鍵,也是在HKEY_LOCAL_MACHINE和HKEY_CURRENT_USER

下都存在的鍵。Run和RunOnce除了在最后刪除所讀出的注冊(cè)條目外,它們有一致的邏輯。在Run下的項(xiàng)目每次用戶登錄時(shí)都執(zhí)行。

?

RunServices鍵

????在Windows95和Windows98下有兩個(gè)鍵允許你模仿NT服務(wù)——即,在用戶登錄之前運(yùn)行模塊。它們是:

HKEY_LOCAL_MACHINE

/SOFTWARE

/Microsoft

/Windows

/CurrentVersion

/RunServices

/RunServicesOnce

它們的語法與我們前面討論過的其它鍵的語法相同。RunService在每次登錄之前運(yùn)行應(yīng)用,而RunServiceOnce則與下一次登陸作相同的操作。執(zhí)行程序是異步的,在用戶實(shí)際登錄之后可以終止。在任何場合,比照RunOnce和Run鍵,所有服務(wù)都必須在系統(tǒng)開始之前執(zhí)行,

?????????下面的表給出啟動(dòng)期間,Windows操作注冊(cè)表鍵的實(shí)際順序:

?

1

HKLM/.../RunServicesOnce?( NT下不支持)

2

HKLM/.../RunServices?(NT下不支持)

3

用戶登錄。用戶可以在所有服務(wù)開始之前登錄

4

所有服務(wù)開始和用戶登錄

5

HKLM/.../RunOnce

6

所有注冊(cè)程序完成

7

HKLM/.../Run

8

HKCU/.../Run

9

當(dāng)前用戶包含在Startup文件夾下的程序

10

HKCU/.../RunOnce

?

Winlogon鍵

?????????如果在任何用戶登錄之前,僅需要簡單地顯示信息,你可以探索下面鍵的注冊(cè)條目:

HKEY_LOCAL_MACHINE

/SOFTWARE

/Microsoft

/Windows

/CurrentVersion

/Winlogon

LegalNoticeCaption和LegalNoticeText值可以定義系統(tǒng)消息框的標(biāo)題和文字,這個(gè)消息框?qū)⒃谌魏斡脩舻卿浿俺霈F(xiàn)。

?

Windows 9x的服務(wù)

?????????在WindowsNT下你可以編寫服務(wù)來實(shí)現(xiàn)要求系統(tǒng)特權(quán)的特定任務(wù)。NT服務(wù)是具有特殊結(jié)構(gòu)和行為的Win32應(yīng)用。除了特殊的實(shí)現(xiàn)細(xì)節(jié)外,服務(wù)的主要特征可以概括為以下幾點(diǎn):

?????????服務(wù)在任何用戶登錄之前運(yùn)行

?????????服務(wù)持續(xù)運(yùn)行,即使用戶注銷也不終止

?????????服務(wù)沒有用戶界面,不是交互的

?????????服務(wù)從操作系統(tǒng)取得特殊待遇——如,它可以在包括系統(tǒng)帳號(hào)的任何用戶帳號(hào)下自動(dòng)啟動(dòng)和運(yùn)行

?????????服務(wù)運(yùn)行在單獨(dú)的虛擬桌面上,它不同于應(yīng)用使用的桌面

?????????服務(wù)可以停止或暫停

WindowsNT有一個(gè)特殊的控件稱為服務(wù)控制管理器(SCM),它管理運(yùn)行中的服務(wù)。由于這個(gè)接口非常強(qiáng),因此在NT下不需要RunServices和RunServicesOnce注冊(cè)表鍵幫忙。

????我們的目的是通過探索RunServices鍵,你可以仿真NT服務(wù),以獲得大致相同的行為。Windows9x的服務(wù)正常是Win32應(yīng)用(無論是否為GUI或控制臺(tái)應(yīng)用),僅是簡單地注冊(cè)在RunServices下在登陸之前運(yùn)行的程序。

?????????通過調(diào)用RegisterServiceProcess() API函數(shù),你可以注冊(cè)當(dāng)前進(jìn)程(或任何其它運(yùn)行中的進(jìn)程)為服務(wù)。以使它持續(xù)工作即使用戶注銷也不停止。這個(gè)函數(shù)并不在任何內(nèi)部庫中輸出,因此,需要?jiǎng)討B(tài)經(jīng)由GetProcAddress()加載,它包含在kernel32.dll中。

?????????下表列出WindowsNT與Windows9x服務(wù)的區(qū)別:

WindowsNT服務(wù)

Windows9x服務(wù)

輸出ServiceMain()函數(shù)的Win32應(yīng)用

傳統(tǒng)的Win32應(yīng)用

在登陸之前運(yùn)行

如果注冊(cè)在RunServices之下,在登陸之前運(yùn)行

在注銷后繼續(xù)運(yùn)行

如果使用RegisterServiceProcess()注冊(cè)為服務(wù),在注銷后繼續(xù)運(yùn)行

一個(gè)沒有用戶界面的GUI或控制臺(tái)應(yīng)用

一個(gè)沒有用戶界面的GUI或控制臺(tái)應(yīng)用

可在系統(tǒng)帳號(hào)下運(yùn)行

不支持

運(yùn)行在單獨(dú)桌面上

不支持

可以停止或暫停

僅可以通過調(diào)用TerminateProcess()停止

服務(wù)必須沒有用戶界面并不是系統(tǒng)要求的,但確實(shí)合理并強(qiáng)烈推薦。

?

設(shè)計(jì)Shell集成的應(yīng)用

?????????到目前為止我們已經(jīng)調(diào)查了應(yīng)用程序與Shell集成的主要技術(shù)。現(xiàn)在我們給出一個(gè)具體的例子來說明實(shí)際程序的設(shè)計(jì)原理和規(guī)則。

?????????對(duì)于面向Shell的應(yīng)用程序,第一個(gè)要求是程序應(yīng)該是基于文件的。這就是說,應(yīng)用必須是圍繞一定種類文檔操作而工作的。其菜單應(yīng)該誠實(shí)地呈現(xiàn)出對(duì)處理文檔的操作活動(dòng),所以在設(shè)計(jì)Shell集成應(yīng)用中你應(yīng)該清楚地知道哪些功能是要通過Shell展示的。

?????????其次,這些功能必須盡可能模塊化編碼,并且通過命令行、RunDLL32或Shell擴(kuò)展的方法可以訪問它們,下面我們就研究怎樣來吸納這些建議。

?

元文件觀察器

?????????我們將要開發(fā)的這個(gè)應(yīng)用是元文件觀察器。選擇這個(gè)例子有兩個(gè)原因:

???????????????????它是一個(gè)有意義的基于文件的應(yīng)用

???????????????????在Windows中沒有系統(tǒng)實(shí)用程序來查看WMF和WMF文件

要說明第二點(diǎn),當(dāng)前唯一觀察元文件的方法是打開文件夾的‘觀察?|?作為Web頁面’選項(xiàng),并且有賴于一個(gè)嵌入的小控件(當(dāng)然,不難獲得這個(gè)共享件使用程序,但是這仍然可以說明這樣的實(shí)用程序提供了足夠的Shell層支持)。

?????????我們的例子是一個(gè)簡單的基于對(duì)話框的應(yīng)用,它允許你選擇,顯示,打印和轉(zhuǎn)換任何Windows元文件。下面的截圖顯示這個(gè)樣例程序的外觀,使用AppWizard建立一個(gè)稱之為WMFView的基于對(duì)話框的應(yīng)用:

?????????????

?

?

我們首先調(diào)查怎樣使這個(gè)程序能實(shí)際顯示元文件,而后怎樣增強(qiáng)它的代碼求得關(guān)聯(lián)菜單客戶化的幫助。

?

Windows元文件和增強(qiáng)元文件

?????????元文件是一些稱作記錄的圖形指令集,它們以產(chǎn)生圖像的順序一個(gè)接一個(gè)地執(zhí)行。在Win32以前有兩種元文件:

???????????????????Windows元文件

???????????????????可定位元文件

例如,Office的剪裁文件就全部是可定位元文件,這兩種類型的元文件通常都有.wmf擴(kuò)展名。

?????????關(guān)于元文件的詳細(xì)說明超出了本書的范圍,你可以參考MSDN庫的幫助。

隨著Win32平臺(tái)的出現(xiàn),Windows元文件的格式也發(fā)生了變化。Win32提出了更新的.emf格式(增強(qiáng)元文件),但是仍然提供對(duì)老的WMF型元文件的支持。

?????????顯然,API函數(shù)更多地關(guān)注增強(qiáng)元文件,值得注意的是,在Win32公共控件中是‘圖像’控件才有能力顯示增強(qiáng)元文件,因而它打開和顯示EMF也是直觀的,然而不幸的是,對(duì)于老的WMF文件就不是那么容易的了。很幸運(yùn),我們發(fā)現(xiàn)了一個(gè)工具在微軟的Web站點(diǎn)上:

http://support.microsoft.com/download/support/mslfiles/enmeta.exe

因此我們能夠使用這個(gè)例子作為參考建立我們自己的源文件觀察器。

?

顯示元文件

?????????wmfview.exe程序識(shí)別三種類型的元文件:

????????Windows元文件

????????可定位元文件

????????增強(qiáng)元文件

?

頭兩個(gè)有.wmf擴(kuò)展名,最后一個(gè)有.emf擴(kuò)展名。無論當(dāng)前打開文件的初始格式是什么,程序總是內(nèi)部使用增強(qiáng)元文件格式。下面代碼顯示怎樣打開和顯示元文件,無論其初始格式如何:

//

//?需要處理16位可定位元文件

#pragma pack(push)

#pragma pack(2)

typedef struct{

DWORD dwKey;

WORD hmf;

SMALL_RECT bbox;

WORD wInch;

DWORD dwReserved;

WORD wCheckSum;

} APMHEADER, *LPAPMHEADER;

#pragma pack(pop)

//

//?獲取Handle和顯示指定的元文件

void DisplayMetaFile(HWND hwndMeta, LPTSTR szFile)

{

//?取得元文件的Handle

HENHMETAFILE hemf = GetMetaFileHandle(szFile);

if(hemf == NULL)

{

MessageBox(NULL, __TEXT("不能處理這個(gè)文件."),

szFile, MB_OK | MB_ICONSTOP);

return;

}

//?釋放老文件并顯示新文件

HENHMETAFILE hemfOld = reinterpret_cast<HENHMETAFILE>(

SendMessage(hwndMeta, STM_GETIMAGE, IMAGE_ENHMETAFILE, 0));

if(hemfOld)

DeleteEnhMetaFile(hemfOld);

// hwndMeta?是圖像控件

SendMessage(hwndMeta, STM_SETIMAGE, IMAGE_ENHMETAFILE,

Reinterpret_cast<LPARAM>(hemf));

lstrcpy(g_szCurFile, szFile);

}

DisplayMetaFile()函數(shù)調(diào)用GetMetaFileHandle()輔助函數(shù)來獲取傳遞來的元文件Handle,刪除任何當(dāng)前顯示,然后發(fā)送消息到控件使它顯示新的元文件。

//?對(duì)指定文件恢復(fù)它的?HENHMETAFILE Handle

HENHMETAFILE GetMetaFileHandle(LPTSTR szFile)

{

DWORD dwSize = 0;

LPBYTE pb = NULL;

//?試著作為EMF讀取文件

HENHMETAFILE hEMF = GetEnhMetaFile(szFile);

if(hEMF)

return hEMF;

//?試著作為WMF讀取文件

HMETAFILE hWMF = GetMetaFile(szFile);

if(hWMF)

{

dwSize = GetMetaFileBitsEx(hWMF, 0, NULL);

if(dwSize == 0)

{

DeleteMetaFile(hWMF);

return NULL;

}

//?分配足夠的內(nèi)存

pb = new BYTE[dwSize];

if(pb == NULL)

{

DeleteMetaFile(hWMF);

return NULL;

}

//?取得元文件的位

dwSize = GetMetaFileBitsEx(hWMF, dwSize, pb);

if(dwSize == 0)

{

delete [] pb;

DeleteMetaFile(hWMF);

return NULL;

}

//?轉(zhuǎn)換成?EMF

hEMF = SetWinMetaFileBits(dwSize, pb, NULL, NULL);

//?清理

DeleteMetaFile(hWMF);

delete [] pb;

return hEMF;

}

//?試著處理輸入為可定位元文件

HANDLE hFile = CreateFile(szFile, GENERIC_READ, 0, NULL,

OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

if(hFile == INVALID_HANDLE_VALUE)

return NULL;

//?讀文件到緩沖

dwSize = GetFileSize(hFile, NULL);

pb = new BYTE[dwSize];

ReadFile(hFile, pb, dwSize, &dwSize, NULL);

CloseHandle(hFile);

//?檢查看是否為可定位元文件

if((reinterpret_cast<LPAPMHEADER>(pb))->dwKey != 0x9ac6cdd7l)

{

//?這個(gè)文件不可知怎樣處理

delete [] pb;

return NULL;

}

//?從位信息建立增強(qiáng)元文件

hEMF = SetWinMetaFileBits(dwSize, &(pb[sizeof(APMHEADER)]), NULL, NULL);

delete [] pb;

return hEMF;

}

GetMetaFileHandle()的操作隱藏了它所涉及到的一般元文件和增強(qiáng)元文件的不同,而且上述大多數(shù)代碼都是關(guān)于格式轉(zhuǎn)換的。最后總是返回增強(qiáng)型元文件的Handle到調(diào)用它的函數(shù)。

?

打印和轉(zhuǎn)換元文件

?????????這個(gè)程序還可以打印元文件,或轉(zhuǎn)換元文件從WMF到EMF,或從EMF到WFM。打印就是簡單地取得關(guān)聯(lián)的打印設(shè)備,然后在打印機(jī)上顯示元文件。

void PrintMetaFile(LPTSTR szFile)

{

//?取得?EMF handle

HENHMETAFILE hEMF = GetMetaFileHandle(szFile);

if(hEMF == NULL)

return;

//?取得打印機(jī)的?DC

PRINTDLG pdlg;

ZeroMemory(&pdlg, sizeof(PRINTDLG));

pdlg.lStructSize = sizeof(PRINTDLG);

pdlg.Flags = PD_RETURNDC;

HDC hDC = NULL;

if(PrintDlg(&pdlg))

hDC = pdlg.hDC;

else

return;

//?準(zhǔn)備打印文檔

DOCINFO di;

ZeroMemory(&di, sizeof(DOCINFO));

di.cbSize = sizeof(DOCINFO);

di.lpszDocName = "Printing EMF";

//?啟動(dòng)打印

StartDoc(hDC, &di);

StartPage(hDC);

//?標(biāo)定符合整個(gè)打印頁

RECT rc;

SetRect(&rc, 0, 0, GetDeviceCaps(hDC, HORZRES), GetDeviceCaps(hDC, VERTRES));

PlayEnhMetaFile(hDC, hEMF, &rc);

//?清理

EndPage(hDC);

EndDoc(hDC);

DeleteDC(hDC);

DeleteEnhMetaFile(hEMF);

}

轉(zhuǎn)換元文件也并不復(fù)雜,就像下面程序清單中說明的。三個(gè)函數(shù)里的頭一個(gè)SaveMetaFile(),處理EMF存儲(chǔ)成可定位的WMF文件(或相反),具有相同的名字,和不同的擴(kuò)展名。實(shí)際上,每一個(gè)元文件都首先轉(zhuǎn)換成EMF(由GetMetaFileHandle()函數(shù)),然后再作為EMF或WMF存儲(chǔ)到磁盤。

void SaveMetaFile(LPTSTR szFile)

{

TCHAR szOutputFile[MAX_PATH] = {0};

HENHMETAFILE hEMF = GetMetaFileHandle(szFile);

if(hEMF == NULL)

return;

//?確定輸出格式

lstrcpy(szOutputFile, szFile);

strlwr(szFile);

if(strstr(szFile, ".emf"))

{

PathRenameExtension(szOutputFile, ".wmf");

SaveToWMF(hEMF, szOutputFile);

}

else if(strstr(szFile, ".wmf"))

{

PathRenameExtension(szOutputFile, ".emf");

SaveToEMF(hEMF, szOutputFile);

}

DeleteEnhMetaFile(hEMF);

}

兩個(gè)輔助函數(shù)SaveToEMF()和SaveToWMF()是非常簡單的:

void SaveToEMF(HENHMETAFILE hEMF, LPTSTR szFile)

{

//?取得存儲(chǔ)EMF位的內(nèi)存

DWORD dwSize = GetEnhMetaFileBits(hEMF, 0, NULL);

LPBYTE pb = new BYTE[dwSize];

//?取得EMF位信息

GetEnhMetaFileBits(hEMF, dwSize, pb);

//?存儲(chǔ)到文件

HANDLE hFile = CreateFile(szFile, GENERIC_WRITE,

0, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL);

if(hFile == INVALID_HANDLE_VALUE)

{

UINT rc = MessageBox(GetFocus(), "File exists. Overwrite?",

szFile, MB_ICONQUESTION | MB_YESNO);

if(rc == IDYES)

hFile = CreateFile(szFile, GENERIC_WRITE,

0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);

else

{

delete [] pb;

return;

}

}

DWORD dwBytes;

WriteFile(hFile, pb, dwSize, &dwBytes, NULL);

CloseHandle(hFile);

delete [] pb;

}

void SaveToWMF(HENHMETAFILE hEMF, LPTSTR szFile)

{

//?取得存儲(chǔ)WMF位信息的內(nèi)存

HDC hDC = GetDC(NULL);

DWORD dwSize = GetWinMetaFileBits(hEMF, 0, NULL, MM_ANISOTROPIC, hDC);

LPBYTE pb = new BYTE[dwSize];

//?從EMFHandle中取出WMF位信息

GetWinMetaFileBits(hEMF, dwSize, pb, MM_ANISOTROPIC, hDC);

ReleaseDC(NULL, hDC);

//?存儲(chǔ)到文件

HANDLE hFile = CreateFile(szFile, GENERIC_WRITE,

0, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL);

if(hFile == INVALID_HANDLE_VALUE)

{

UINT rc = MessageBox(GetFocus(), "File exists. Overwrite?",

szFile, MB_ICONQUESTION|MB_YESNO);

if(rc == IDYES)

hFile = CreateFile(szFile, GENERIC_WRITE,

0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);

else

{

delete [] pb;

return;

}

}

DWORD dwBytes;

WriteFile(hFile, pb, dwSize, &dwBytes, NULL);

CloseHandle(hFile);

delete [] pb;

}

它們看來對(duì)于我們的討論似乎是多余的,然而轉(zhuǎn)換函數(shù)不僅是用于顯示的。盡管Win32開始就支持增強(qiáng)元文件,但你總能接觸到WMF文件——大部分是可定位元文件。因此對(duì)于這類應(yīng)用一種容易的格式轉(zhuǎn)換方法是無價(jià)的。

?

組建觀察器

?????????要把前面給出的離散函數(shù)組裝到一起組建成一個(gè)應(yīng)用,我們需要在某個(gè)地方調(diào)用它們。在開發(fā)的這個(gè)一階段我們選擇對(duì)話框的相關(guān)菜單來完成這個(gè)工作(你可以使用VC++的資源編輯器的‘屬性’關(guān)聯(lián)菜單生成)。添加本節(jié)開始的截圖上顯示的菜單項(xiàng),然后修改APP_DlgProc()中的WM_COMMAND處理器,如下:

case WM_COMMAND:

switch(wParam)

{

case ID_FILE_OPEN:

OnOpen(hDlg);

return FALSE;

case ID_FILE_PRINT:

OnPrint(hDlg);

return FALSE;

case ID_FILE_SAVEAS:

OnSave(hDlg);

return FALSE;

case ID_FILE_EXIT:

case IDCANCEL:

EndDialog(hDlg, FALSE);

return FALSE;

}

break;

最后,問題歸結(jié)到實(shí)現(xiàn)三個(gè)相對(duì)容易的函數(shù)或例程上,我們?cè)谙旅娼o出它們的定義:

void OnOpen(HWND hDlg)

{

TCHAR szFile[MAX_PATH] = {0};

OPENFILENAME ofn;

ZeroMemory(&ofn, sizeof(OPENFILENAME));

ofn.lStructSize = sizeof(OPENFILENAME);

ofn.lpstrFilter =

"Metafiles/0*.?mf/0WMF/0*.wmf/0Enhanced/0*.emf/0All Files/0*.*/0";

ofn.nMaxFile = MAX_PATH;

ofn.lpstrFile = szFile;

if(!GetOpenFileName(&ofn))

return;

else

{

HWND hwndMeta = GetDlgItem(hDlg, IDC_METAFILE);

DisplayMetaFile(hwndMeta, ofn.lpstrFile);

RefreshUI(hDlg, ofn.lpstrFile);

}

}

void OnPrint(HWND hDlg)

{

if(lstrlen(g_szCurFile))

PrintMetaFile(g_szCurFile);

else

Msg("當(dāng)前沒有打開的元文件.");

}

void OnSave(HWND hDlg)

{

TCHAR s[1024] = {0};

TCHAR szOutputFile[MAX_PATH] = {0};

if(!lstrlen(g_szCurFile))

{

Msg("當(dāng)前沒有打開的元文件.");

return;

}

//?請(qǐng)求用戶確認(rèn)

lstrcpy(szOutputFile, g_szCurFile);

if(strstr(g_szCurFile, ".emf"))

PathRenameExtension(szOutputFile, ".wmf");

else if(strstr(g_szCurFile, ".wmf"))

PathRenameExtension(szOutputFile, ".emf");

wsprintf(s, "You're about to convert %s to %s./nAre you really sure?",

g_szCurFile, szOutputFile);

UINT rc = MessageBox(hDlg, s, APPTITLE, MB_ICONQUESTION | MB_YESNO);

//?處理...

if(rc == IDYES)

SaveMetaFile(g_szCurFile);

}

上面代碼顯示這些函數(shù)完成了應(yīng)用的操作,除了下述的三件事。第一,APPTITLE是一個(gè)全局串常量,它是對(duì)話框的標(biāo)題,因而應(yīng)該等于“元文件觀察器”。第二,g_szCurfile是一個(gè)全局字符數(shù)組,用于存儲(chǔ)打開的元文件名,并且在WinMain()中應(yīng)該設(shè)置成空串。第三,RefreshUI()是一個(gè)輔助函數(shù),它用于把打開的源文件名字附加到對(duì)話框標(biāo)題上:

void RefreshUI(HWND hWnd, LPTSTR szFile)

{

TCHAR szCaption[MAX_PATH] = {0};

//?刷新標(biāo)題條

wsprintf(szCaption, "%s - %s", APPTITLE, szFile);

SetWindowText(hWnd, szCaption);

}

使用這最后一個(gè)函數(shù)和通用對(duì)話框及Shell輕量級(jí)API的頭文件和庫文件,你現(xiàn)在就可以自豪的擁有這個(gè)有用的應(yīng)用程序來顯示,打印和轉(zhuǎn)換元文件了。

?

改編這個(gè)應(yīng)用

?????????通過文檔的關(guān)聯(lián)菜單打印和轉(zhuǎn)換元文件是一種更好的方法。在上面的函數(shù)中,我們已經(jīng)用模塊的方法實(shí)現(xiàn)了三個(gè)函數(shù):

???????????????????打開

???????????????????打印

???????????????????轉(zhuǎn)換到

也就是說,有三個(gè)靜態(tài)動(dòng)詞可以加到WMF和EMF文檔上,然而,在我們能夠斷言可以成功地客戶化關(guān)聯(lián)菜單之前,在應(yīng)用中有幾個(gè)問題需要解決。首先,我們需要加入對(duì)命令行的支持,其次,我們需要注冊(cè)EMF和WMF系統(tǒng)文件類——默認(rèn)情況下它們是不可知文件類。此后,第三個(gè)問題是每次點(diǎn)擊元文件都導(dǎo)出新的wmfview實(shí)例運(yùn)行。這比只有一個(gè)實(shí)例運(yùn)行要好,因?yàn)榭梢源蜷_,打印或轉(zhuǎn)換任何新文檔。下面就讓我們來著手解決這些問題。

?

命令行的重要性

這個(gè)應(yīng)用應(yīng)該支持下面的命令行:

wmfview.exe filename

wmfview.exe /p filename

wmfview.exe /s filename

頭一行打開指定的文件,其他兩行是打印和轉(zhuǎn)換文件。有命令行的支持可以使我們?nèi)菀椎靥砑有聞?dòng)詞到EMF和WMF文檔,看一下這段代碼:

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevious,LPTSTR lpsz, int iCmd)

{

//?這段代碼沒有改變,因而省略

//?運(yùn)行主對(duì)話框

BOOL b = DialogBoxParam(hInstance, "DLG_MAIN", NULL, APP_DlgProc,

reinterpret_cast<LPARAM>(lpsz));

//?退出

DestroyIcon(g_hIconLarge);

DestroyIcon(g_hIconSmall);

return b;

}

WinMain()函數(shù)傳遞對(duì)話框過程通過調(diào)用DialogBoxParam() API函數(shù)而接收的命令行串。任何命令行變量則通過WM_INITDIALOG消息的響應(yīng)來處理,如下代碼顯示:

void OnInitDialog(HWND hDlg, LPARAM lParam)

{

//?設(shè)置圖標(biāo)(T/F?作為大/小圖標(biāo))

SendMessage(hDlg, WM_SETICON, FALSE, reinterpret_cast<LPARAM>(g_hIconSmall));

SendMessage(hDlg, WM_SETICON, TRUE, reinterpret_cast<LPARAM>(g_hIconLarge));

if(lstrlen(reinterpret_cast<LPTSTR>(lParam)))

ParseCommandLine(hDlg, reinterpret_cast<LPTSTR>(lParam));

}

void ParseCommandLine(HWND hwnd, LPTSTR pszCmdLine)

{

if(!lstrlen(pszCmdLine))

return;

//?取得命令行的頭兩個(gè)字符(+ 1?它是開關(guān))

TCHAR pszSwitch[2] = {0};

lstrcpyn(pszSwitch, pszCmdLine, 3);

LPTSTR psz = pszCmdLine + lstrlen(pszSwitch) + 1;

//?解析條件并發(fā)送客戶消息

if(!lstrcmpi(pszSwitch, "/p"))

SendMessage(hwnd, WM_EX_PRINTMETA, 0, reinterpret_cast<LPARAM>(psz));

else if(!lstrcmpi(pszSwitch, "/s"))

SendMessage(hwnd, WM_EX_SAVEMETA, 0, reinterpret_cast<LPARAM>(psz));

else

SendMessage(hwnd, WM_EX_DISPLAYMETA,

?0,reinterpret_cast<LPARAM>(pszCmdLine));

}

如上所示,ParseCommandLine()函數(shù)檢查命令行,決定要做什么,然后發(fā)送客戶消息到應(yīng)用窗口過程。客戶消息如下定義:

const int WM_EX_DISPLAYMETA = WM_APP + 1;

const int WM_EX_PRINTMETA = WM_APP + 2;

const int WM_EX_SAVEMETA = WM_APP + 3;

APP_DlgProc()得到幾個(gè)調(diào)用我們已經(jīng)定義過的函數(shù)處理器,如下:

BOOL CALLBACK APP_DlgProc(HWND hDlg, UINT uiMsg, WPARAM wParam, LPARAM lParam)

{

switch(uiMsg)

{

case WM_INITDIALOG:

OnInitDialog(hDlg, lParam);

break;

case WM_EX_DISPLAYMETA:

DisplayMetaFile(GetDlgItem(hDlg, IDC_METAFILE),

reinterpret_cast<LPTSTR>(lParam));

RefreshUI(hDlg, reinterpret_cast<LPTSTR>(lParam));

break;

case WM_EX_PRINTMETA:

PrintMetaFile(reinterpret_cast<LPTSTR>(lParam));

break;

case WM_EX_SAVEMETA:

SaveMetaFile(reinterpret_cast<LPTSTR>(lParam));

break;

case WM_COMMAND:

現(xiàn)在你可以看出,用命令行運(yùn)行應(yīng)用和顯示、打印以及保存元文件就象使用對(duì)話框的菜單一樣。

?

為什么應(yīng)用要單實(shí)例化

?????????上面代碼顯示每次都運(yùn)行一個(gè)新實(shí)例。一旦我們把它添加到Shell使之支持元文件,就可以在任何WMF或EMF文件上點(diǎn)擊來導(dǎo)出wmfview,顯示指定的元文件。問題是新的程序副本被導(dǎo)出不僅是在打開文件時(shí),在打印或轉(zhuǎn)換文件時(shí)也產(chǎn)生。為了避免太多的wmfview窗口,我們需要使之成為單實(shí)例應(yīng)用。

?????????回顧Windows3.x中WinMain()的hPrevious變量,它用于表示應(yīng)用的前一個(gè)實(shí)例是否存在。在Win32平臺(tái)下,這個(gè)變量僅僅是為了維護(hù)兼容性而存在。并且總是NULL。因此很難知道是否有同一個(gè)進(jìn)程的副本當(dāng)前正在運(yùn)行,但是仍然有幾種可用的技術(shù):

技術(shù)

描述

FindWindow()

這個(gè)API函數(shù)返回屬于指定類并具有給定標(biāo)題的頭一個(gè)窗口的Handle。因而它可以通過類名或標(biāo)題鑒別窗口。

EnumWindows()

這個(gè)API函數(shù)枚舉所有存在的非子窗口的窗口。這對(duì)于調(diào)查是否有相同類或標(biāo)題的多重窗口是有用的。

進(jìn)程名

這項(xiàng)技術(shù)要求枚舉所有活動(dòng)進(jìn)程并檢查程序名。

互斥量和信號(hào)燈

如果你想限制實(shí)例的數(shù)量,也可以使用同步結(jié)構(gòu),如互斥量和信號(hào)燈。互斥量對(duì)于單實(shí)例應(yīng)用更好一些,而信號(hào)燈則允許有固定數(shù)量的副本。

?

?

?

基于對(duì)話框的單實(shí)例的應(yīng)用

?????????在wmfview情況下,要求我們采用EnumWindows()方法。首先,互斥量和進(jìn)程名的方法對(duì)于我們不是太好,因?yàn)槲覀冃枰謴?fù)前面窗口的Handle以便再重新使用它。簡單地知道它存在是沒有幫助的。

?????????再有,盡管FindWindow()更簡單,但是我們的程序是基于對(duì)話框的,所以沒有一個(gè)可以容易辨別的類名。相反,我們程序的主窗口類名是#32770,這實(shí)際與任何對(duì)話框或基于對(duì)話框應(yīng)用同名。而且FindWindow()在頭一個(gè)匹配后就停止了。我們可以使用標(biāo)題來減少匹配的機(jī)會(huì),但是我們?cè)跇?biāo)題條上附加了打開的文件名,因此標(biāo)題是經(jīng)常變化的。

?????????剩余的只有基于EnumWindows()這一種方法。我們將枚舉窗口,并每次檢查類名和標(biāo)題。對(duì)于對(duì)話框窗口,我們驗(yàn)證標(biāo)題具有我們期望的前綴——即,“源文件觀察器”。為了絕對(duì)保證我們獲得正確的窗口,我們還檢查建立它的可執(zhí)行文件。

?????????取得可執(zhí)行文件名比想象的要困難得多,因?yàn)樵赪indows9x和WindowsNT中沒有共同的方法。我們需要在Windows9x下使用ToolHelp?API函數(shù),而在NT下使用PSAPI函數(shù)。關(guān)于這兩種API的資料在MSDN庫中可以找到。

?????????只要獲得了前一個(gè)實(shí)例的HWND Handle,我們就可以把它推到前臺(tái)并調(diào)用ParseCommandLine(),傳遞我們接收的命令行。下面是對(duì)WinMain()代碼的修改:

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevious,LPTSTR lpsz, int iCmd)

{

//?是否有運(yùn)行中的實(shí)例

HWND hwnd = AnotherInstanceRunning();

if(IsWindow(hwnd))

{

//?推送前一個(gè)窗口到頂部

if(IsIconic(hwnd))

ShowWindow(hwnd, SW_RESTORE);

SetForegroundWindow(hwnd);

//?解析‘這個(gè)’命令行而不是發(fā)送消息到前一個(gè)窗口

ParseCommandLine(hwnd, lpsz);//這里使用的是獲得的?hWnd,在分析中發(fā)送消息。

//?現(xiàn)在可以退出了

return 1;

}

//?其余函數(shù)不變

}

這里的AnotherInstanceRunning()就是實(shí)際用于測(cè)試的函數(shù),它調(diào)用EnumWindows()函數(shù),而回調(diào)函數(shù)僅在找到同類窗口時(shí)停止。

HWND AnotherInstanceRunning()

{

HWND hwndFound = NULL;

EnumWindows(CheckRunningApps, reinterpret_cast<LPARAM>(&hwndFound));

// hwndFound?將取匹配窗口的Handle

return hwndFound;

}

BOOL CALLBACK CheckRunningApps(HWND hwnd, LPARAM lParam)

{

TCHAR szClass[MAX_PATH] = {0};

GetClassName(hwnd, szClass, MAX_PATH);

if(!lstrcmpi(szClass, "#32770"))

{

TCHAR s[MAX_PATH] = {0};

TCHAR szTitle[MAX_PATH] = {0};

GetWindowText(hwnd, szTitle, MAX_PATH);

lstrcpyn(s, szTitle, 1 + lstrlen(APPTITLE));

if(!lstrcmpi(s, APPTITLE))

{

//?使用緩沖指針lparam返回HWND

HWND* lphwnd = reinterpret_cast<HWND*>(lParam);

*lphwnd = hwnd;

return FALSE;

}

}

return TRUE;

}

現(xiàn)在我們最終有了一個(gè)單實(shí)例應(yīng)用,可用它來觀察,打印和轉(zhuǎn)換元文件,因此可以考慮怎樣添加某些Shell的支持了。

?

添加Shell支持

?????????典型地,Shell支持意思是:

???????????????????注冊(cè)應(yīng)用處理的每一種文件類型

???????????????????注冊(cè)文件類型的默認(rèn)圖標(biāo)

???????????????????添加關(guān)聯(lián)菜單動(dòng)詞

???????????????????添加打開的文檔到最近文檔列表

我們已經(jīng)看到了怎樣注冊(cè)新的文件類型及其圖標(biāo),并且在Wmfview.exe的資源中我們也安排了包含元文件和增強(qiáng)元文件的圖標(biāo):

???????????????????????????????????????

?

?

添加到系統(tǒng)注冊(cè)表中的條目包含在下面的腳本代碼中:

REGEDIT4

; //

; //?注冊(cè)?WMF?和?EMF?文件類型

[HKEY_CLASSES_ROOT/.wmf]

@= "WinMetafile"

[HKEY_CLASSES_ROOT/WinMetafile]

@= "Windows Metafile"

[HKEY_CLASSES_ROOT/.emf]

@= "EnhMetafile"

[HKEY_CLASSES_ROOT/EnhMetafile]

@= "Enhanced Metafile"

; //

; //?注冊(cè)WMF?和?EMF的圖標(biāo)

[HKEY_CLASSES_ROOT/WinMetafile/DefaultIcon]

@= "C://WmfView//WmfView.exe,2"

[HKEY_CLASSES_ROOT/EnhMetafile/DefaultIcon]

@= "C://WmfView//WmfView.exe,1"

; //

; //?添加?WMF?打開,打印和保存動(dòng)詞

;?打開

[HKEY_CLASSES_ROOT/WinMetafile/Shell/Open/Command]

@= "C://WmfView//WmfView.exe %1"

;?打印

[HKEY_CLASSES_ROOT/WinMetafile/Shell/Print/Command]

@= "C://WmfView//WmfView.exe /p %1"

;?保存到?EMF

[HKEY_CLASSES_ROOT/WinMetafile/Shell/Save]

@= "&Convert to EMF"

[HKEY_CLASSES_ROOT/WinMetafile/Shell/Save/Command]

@= "C://WmfView//WmfView.exe /s %1"

; //

; //?添加打開,打印和保存動(dòng)詞到?EMF

;?打開

[HKEY_CLASSES_ROOT/EnhMetafile/Shell/Open/Command]

@= "C://WmfView//WmfView.exe %1"

;?打印

[HKEY_CLASSES_ROOT/EnhMetafile/Shell/Print/Command]

@= "C://WmfView//WmfView.exe /p %1"

;?保存到?WMF

[HKEY_CLASSES_ROOT/EnhMetafile/Shell/Save]

@= "&Convert to WMF"

[HKEY_CLASSES_ROOT/EnhMetafile/Shell/Save/Command]

@= "C://WmfView//WmfView.exe /s %1"

除了假定已經(jīng)安裝了WmfView.exe到c:/wmfview文件夾下,這個(gè)清單還展示了在編寫自己的注冊(cè)腳本時(shí)所需要了解的一些事情。特別是:

?

???????????????????在REG腳本中?@符號(hào)表示默認(rèn)值

???????????????????非常重要的是在路徑名中使用兩個(gè)反斜杠,而在注冊(cè)條目中使用一個(gè)反斜杠

???????????????????在編寫REG腳本時(shí),記住不要割裂包含在括號(hào)中的注冊(cè)表路徑到兩個(gè)行或多個(gè)行上

?

默認(rèn)情況下動(dòng)詞名(此時(shí)為Shell鍵下的任何子鍵)就是實(shí)際出現(xiàn)在關(guān)聯(lián)菜單上的項(xiàng)。這對(duì)于‘打開’和‘打印’是適合的,但是并不適合‘保存’表示的從EMF轉(zhuǎn)換到WMF或相反的命令。這就是為什么我們?cè)O(shè)置‘保存’動(dòng)詞的默認(rèn)值為客戶想要出現(xiàn)在關(guān)聯(lián)菜單項(xiàng)上的串的理由。你可以通過使用注冊(cè)表編輯器的‘注冊(cè)表?|?輸入注冊(cè)文件’菜單項(xiàng),通過雙擊這個(gè)REG文件,或編程通過ShellExecute()函數(shù)執(zhí)行這個(gè)REG文件運(yùn)行這個(gè)腳本。此后,重新啟動(dòng)探測(cè)器,其效果如下圖中顯示的一樣:

??????????

?

?

改變默認(rèn)菜單項(xiàng)

?????????如圖表示的,‘打開’動(dòng)詞總是設(shè)置為默認(rèn)的關(guān)聯(lián)菜單項(xiàng)。默認(rèn)項(xiàng)是以粗體顯示的,并且雙擊左鍵自動(dòng)選擇(或單擊,依賴于桌面設(shè)置)。默認(rèn)項(xiàng)總是顯示在關(guān)聯(lián)菜單中的第一項(xiàng)。然而,通過設(shè)置下面鍵的默認(rèn)值,我們可以重排增強(qiáng)元文件關(guān)聯(lián)菜單的項(xiàng),例如:

HKEY_CLASSES_ROOT

/EnhMetafile

/Shell

正常情況下這個(gè)值包含空字符串,但是如果你設(shè)置它為用逗號(hào)分隔的串,其標(biāo)記是各個(gè)動(dòng)詞的名,則他們將以你指定的順序顯示,而第一項(xiàng)是默認(rèn)項(xiàng),這并不要求你指定所有列表中的動(dòng)詞,所以,你可以限制有多少要重排的項(xiàng)。

?????????記住這個(gè)技術(shù)僅對(duì)定義在Shell鍵下的靜態(tài)動(dòng)詞有效

?

添加所有文件的關(guān)聯(lián)菜單項(xiàng)

?????????在上面的截圖中你可能已經(jīng)注意到有兩個(gè)關(guān)系到WinZip的項(xiàng)。這兩個(gè)項(xiàng)并不是特別針對(duì)元文件的,而是針對(duì)所有文件的(但不對(duì)文件夾)。因此,它們沒有在WinMetaFile或EnhMetaFile的Shell鍵下列出。要為所有文件類添加關(guān)聯(lián)菜單項(xiàng),簡單地在下面鍵中添加動(dòng)詞即可:

HKEY_CLASSES_ROOT

/*

/Shell

?

??????????

?

有時(shí)添加新項(xiàng)將從‘打開方式’項(xiàng)中刪除默認(rèn)風(fēng)格。還要注意,所有在這里添加的項(xiàng)總是在特殊文件類型的Shell項(xiàng)之前顯示。然而這顯然不是WinZip的場合。那么WinZip是怎么做的呢。如果你想要添加新命令在HKEY_CLASSES_ROOT/*/Shell鍵下而又不刪除當(dāng)前默認(rèn)項(xiàng),一個(gè)辦法就是定義Shell擴(kuò)展。就象上圖

中的WinZip和‘公文包’那樣。Shell擴(kuò)展在shellex鍵下,并且,如果應(yīng)用于關(guān)聯(lián)菜單,還需要有進(jìn)一步的ContextMenuHandlers子鍵。我們將在下一章中討論這個(gè)問題。

如果想要添加客戶菜單項(xiàng)到任何文件夾或驅(qū)動(dòng)器,則注冊(cè)鍵應(yīng)該分別在:

HKEY_CLASSES_ROOT/Folder

HKEY_CLASSES_ROOT/Drive

下。

?

給文件夾指定客戶圖標(biāo)

?????????假設(shè)你編寫了一套應(yīng)用,并安裝在公共路徑下。如果這個(gè)文件夾有一個(gè)客戶化圖標(biāo)是否會(huì)更好一點(diǎn)。看一下下面的圖:

?????????

?

?

這個(gè)Wrox Applications應(yīng)用的文件夾比正常的要好一些,而且它的圖標(biāo)也是不同的。要獲得這個(gè)效果,只需完成下面幾步:

?

在想要客戶化的文件夾中建立一個(gè)名為desktop.ini的ASCII文件。可以使其隱藏,但并不是必要的。過一會(huì)我們將討論這個(gè)文件的內(nèi)容。

使這個(gè)目錄為只讀。這可以編程實(shí)現(xiàn)或通過屬性對(duì)話框?qū)崿F(xiàn)?。

?

desktop.ini文件是一個(gè)特殊意義的探測(cè)器——它表示在Shell與一個(gè)客戶化文件夾外觀和行為的代碼段之間的連接點(diǎn)。在第16章中我們將揭示怎樣通過命名空間擴(kuò)展改變文件夾的行為,而不要求包含多余信息的desktop.ini文件。為了改變文件夾的圖標(biāo),我們需要把下面行添加到這個(gè)文件中:

[.ShellClassInfo]

IconIndex=0

IconFile=C:/WMFVIEW.EXE

這些條目的作用是相當(dāng)清楚的:IconIndex表示圖標(biāo)在IconFile文件中的索引。因此這兩個(gè)項(xiàng)組合定義了文件夾顯示的圖標(biāo)。

????注意,當(dāng)你改變文件夾的設(shè)置時(shí)——特別是在Web觀察下——這個(gè)文件的內(nèi)容被自動(dòng)更新。

?

自由添加最近文檔

?????????Win32資料說如果想要添加文檔到系統(tǒng)文件夾作為最近使用的文檔,你需要產(chǎn)生對(duì)SHAddToRecentDocs()函數(shù)的調(diào)用。要完成這項(xiàng)工作,我們發(fā)現(xiàn)有時(shí)并不需要走這么遠(yuǎn)。當(dāng)處理元文件已經(jīng)被注冊(cè)了之后,我們發(fā)現(xiàn)wmfview自動(dòng)保存打開的文檔到這個(gè)文件夾,甚至我們并沒有顯式地調(diào)用SHAddToRecentDocs()。這顯然是Shell施加于注冊(cè)了文檔的應(yīng)用的一個(gè)特征。對(duì)于非注冊(cè)文件類型,你仍然需要這個(gè)API函數(shù)。

????事實(shí)上,你沒必要必須打開文檔,當(dāng)你打印或確切地執(zhí)行任何動(dòng)詞的時(shí)候,都可以。然而,當(dāng)你使用‘文檔’菜單時(shí),總是執(zhí)行默認(rèn)動(dòng)詞。

?

支持拖拽

????基于文件的應(yīng)用有對(duì)拖拽的支持是一個(gè)極強(qiáng)的功能,并且如果你仔細(xì)地設(shè)計(jì)軟件并很好地與Shell集成,添加這個(gè)能力就象給你的主窗口賦一個(gè)WS_EX_ACCEPTFILES風(fēng)格的值一樣容易,這就使它能夠感覺到后面的WM_DROPFILES消息。

????重要地是需要有一個(gè)可以作為模塊化過程運(yùn)行的函數(shù)來處理這個(gè)事件。如果使用命令行來解釋,則添加Shell拖拽要求附加下面的代碼,它將被喚醒響應(yīng)WM_DROPFILES消息:

case WM_DROPFILES:

HandleFileDrop(hDlg, reinterpret_cast<HDROP>(wParam));

break;

?

void HandleFileDrop(HWND hDlg, HDROP hDrop)

{

TCHAR szFileName[MAX_PATH] = {0};

//?由于是SDI應(yīng)用,它不能感覺接收超過一個(gè)文件,因此僅是第一個(gè)文件被接收

DragQueryFile(hDrop, 0, szFileName, MAX_PATH);

SendMessage(hDlg, WM_EX_DISPLAYMETA, 0, reinterpret_cast<LPARAM>(szFileName));

DragFinish(hDrop);

}

?

客戶化打開對(duì)話框

?????????面向Shell應(yīng)用可以使用的另一個(gè)值得注意的屬性是定制系統(tǒng)公共對(duì)話框。Win32 API描述了我們需要客戶化公共對(duì)話框的技術(shù),比如顏色,字體或打印,你應(yīng)該參考微軟的資料來獲得更多細(xì)節(jié)信息。

?????????就我們的目的而言,最值得關(guān)注的對(duì)話框是‘打開’/‘保存’對(duì)話框。讓我們從一個(gè)客戶化打開對(duì)話框的例子開始,在對(duì)話框上添加幾個(gè)新按鈕作為特殊路徑的標(biāo)簽。下圖顯示了這個(gè)客戶化的對(duì)話框。要特別注意‘Open As’的標(biāo)號(hào)和組合框。

??????????????????

?

?

?????????下面我們將說明怎樣建立類似于Office2000產(chǎn)生的打開對(duì)話框。在此之前我們要向你解釋一下對(duì)話框的客戶化是怎樣發(fā)生的。

?

定義新的模版

?????????這個(gè)打開對(duì)話框允許你定義非標(biāo)準(zhǔn)模板,但是你沒有象Windows3.1中那樣可用的自由度。如果你使用類探測(cè)器的外觀,則不允許隱藏不需要的控件。這是由設(shè)計(jì)規(guī)定的,我們現(xiàn)在還沒有看到可靠的工作方法,除非編寫一個(gè)新的初級(jí)對(duì)話框。

?????????要調(diào)用打開對(duì)話框,你需要GetOpenFileName()函數(shù),顯示如下:

?

OPENFILENAME ofn;

TCHAR szFile[MAX_PATH] = {0};

ZeroMemory(&ofn, sizeof(OPENFILENAME));

ofn.lStructSize = sizeof(OPENFILENAME);

ofn.lpstrFile = szFile;

ofn.nMaxFile = sizeof(szFile);

ofn.lpstrFilter = __TEXT("All files/0*.*/0");

ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY;

GetOpenFileName(&ofn);

為了允許客戶化,需要在調(diào)用之前添加下面的代碼行:

ofn.lpTemplateName = MAKEINTRESOURCE(IDD_DIALOG);

ofn.lpfnHook = OpenDlgExProc;

ofn.Flags |= OFN_EXPLORER | OFN_ENABLEHOOK | OFN_ENABLETEMPLATE;

ofn.lpTemplateName指向一個(gè)對(duì)話框模板,ofn.lpfnHook指向一個(gè)窗口過程,這個(gè)過程將處理所有客戶控件的消息。

????形成初始對(duì)話框的控件必須作為一個(gè)整體控件加以考慮,所以,你所設(shè)計(jì)的任何模板都必須包含整個(gè)初始對(duì)話框作為構(gòu)件塊。實(shí)際上,這就是說要使用你所附加控件的相對(duì)位置,看下面截圖,其中對(duì)話框模版有一個(gè)‘Child’風(fēng)格,但是沒有邊緣:

????????????

?

?

高亮部分是靜態(tài)部件,其ID為stc32,這是定義在dlg.h(VC++的一個(gè)頭文件)中的特殊常量。它表示為一個(gè)標(biāo)準(zhǔn)的‘打開’對(duì)話框。然后你可以在這個(gè)部件周圍放置你的控件,并且不必考慮stc32控件的尺寸,最后的窗口是適當(dāng)可調(diào)整的。就如同圖中顯示的。下面截圖顯示這個(gè)打開對(duì)話框在運(yùn)行時(shí)使用上面的模版形成的顯示界面,這是一個(gè)由AppWizard生成的應(yīng)用。

??????????????

?

?

標(biāo)準(zhǔn)對(duì)話框被擴(kuò)展成有三個(gè)豎排按鈕在左側(cè)的客戶化對(duì)話框,看上去不過如此,它的行為又如何呢?

?

新對(duì)話框?qū)傩?/p>

?????????除了新的外表,這個(gè)‘打開’對(duì)話框應(yīng)該有什么樣的客戶化行為呢?

??????????????????

快捷方式的常用路徑是一個(gè)好想法,如果可以由用戶定義就更好了

???????????????????新控件上的工具標(biāo)簽是另一個(gè)特征,這改進(jìn)了對(duì)話框的外觀和感覺

???????????????????這個(gè)對(duì)話框應(yīng)該能夠防止刪除和重命名文件夾中的項(xiàng)

?

常用路徑的標(biāo)簽

?????????返回前面的討論,我們選擇添加了幾個(gè)按鈕來存儲(chǔ)指向‘Favorites’,‘最近文檔’和‘Windows’目錄的標(biāo)簽。代碼的最有技巧部分是編程獲得特定的路徑,方案與手動(dòng)所作的一樣。換言之,只需設(shè)置‘文件名’編輯框到路徑名,并點(diǎn)擊‘打開’即可。

?????????所有在公共對(duì)話框中使用的控件,其ID都定義在dlgs.h頭文件中,但不幸的是這個(gè)文件并沒有給這些ID分配助記符。通過使用Spy++和dlg.h,可以對(duì)應(yīng)給出它們的標(biāo)記。文件名編輯框的ID是0x0480,其相關(guān)助記常量是edt1。從窗口過程調(diào)用函數(shù)處理按鈕點(diǎn)擊,我們需要作下面的操作:

#include <dlgs.h>

#include <shlobj.h>

void Goto(HWND hDlg, WORD wID)

{

TCHAR szDir[MAX_PATH] = {0};

LPITEMIDLIST pidl;

//?恢復(fù)轉(zhuǎn)跳路徑

if(wID == IDC_WINDOWS)

GetWindowsDirectory(szDir, MAX_PATH);

else

{

if(wID == IDC_FAVORITES)

SHGetSpecialFolderLocation(hDlg, CSIDL_FAVORITES, &pidl);

else

if(wID == IDC_RECENT)

SHGetSpecialFolderLocation(hDlg, CSIDL_RECENT, &pidl);

SHGetPathFromIDList(pidl, szDir);

}

//?在文件名編輯框中設(shè)置新路徑

HWND hdlgParent = GetParent(hDlg);

SetDlgItemText(hdlgParent, edt1, szDir);

SendMessage(hdlgParent, WM_COMMAND, IDOK, 0);

SetDlgItemText(hdlgParent, edt1, "");

}

注意這段代碼,鉤子過程所接收的窗口Handle不是實(shí)際對(duì)話框窗口的Handle。這個(gè)操作僅僅適用于‘打開’對(duì)話框,要獲得實(shí)際窗口,你必須取得傳輸窗口的父窗口Handle:

HWND hdlgParent = GetParent(hDlg);

獲得父窗口后,你才能安全地獲得‘打開’對(duì)話框的每一個(gè)子控件。

?

按鈕的圖標(biāo)和提示

?????????每一個(gè)新按鈕都有一個(gè)圖標(biāo)和提示。下面是怎樣設(shè)置按鈕的圖標(biāo)和提示:

?

void InitNewButtons(HWND hDlg)

{

SHFILEINFO sfi;

LPITEMIDLIST pidl = NULL;

//?指派圖標(biāo)到‘Favorites’按鈕

SHGetSpecialFolderLocation(hDlg, CSIDL_FAVORITES, &pidl);

SHGetFileInfo(reinterpret_cast<LPTSTR>(pidl),

0, &sfi, sizeof(SHFILEINFO), SHGFI_PIDL | SHGFI_ICON);

SendDlgItemMessage(hDlg, IDC_FAVORITES, BM_SETIMAGE, IMAGE_ICON,

reinterpret_cast<LPARAM>(sfi.hIcon));

//?指派圖標(biāo)到‘最近文檔’按鈕

SHGetSpecialFolderLocation(hDlg, CSIDL_PERSONAL, &pidl);

SHGetFileInfo(reinterpret_cast<LPTSTR>(pidl),

0, &sfi, sizeof(SHFILEINFO), SHGFI_PIDL | SHGFI_ICON);

SendDlgItemMessage(hDlg, IDC_RECENT, BM_SETIMAGE, IMAGE_ICON,

reinterpret_cast<LPARAM>(sfi.hIcon));

//?指派圖標(biāo)到‘Windows’按鈕

SendDlgItemMessage(hDlg, IDC_WINDOWS, BM_SETIMAGE, IMAGE_ICON,

reinterpret_cast<LPARAM>(LoadIcon(NULL, IDI_WINLOGO)));

//?設(shè)置每個(gè)按鈕的提示

SetTooltips(hDlg);

}

圖標(biāo)根據(jù)特定的圖標(biāo)需求從不同的地方恢復(fù),對(duì)于特殊文件夾,如‘Favorites’或‘最近文檔’,使用SHGetFileInfo(),而LoadIcon()則幫助獲得Windows的標(biāo)記。每一個(gè)按鈕也都有自己的提示:

void SetTooltips(HWND hDlg)

{

//?建立提示控件

HWND hwndTT = CreateWindow(TOOLTIPS_CLASS, NULL, TTS_ALWAYSTIP,CW_USEDEFAULT,

CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, hDlg,

NULL, GetModuleHandle(NULL), NULL);

//?為每一個(gè)按鈕定義要求的工具

//?查看‘Favorites’

TOOLINFO ti;

ZeroMemory(&ti, sizeof(TOOLINFO));

ti.cbSize = sizeof(TOOLINFO);

ti.uFlags = TTF_IDISHWND | TTF_SUBCLASS;

ti.hwnd = hDlg;

ti.uId = reinterpret_cast<UINT>(GetDlgItem(hDlg, IDC_FAVORITES));

ti.lpszText = __TEXT("查看Favorites");

SendMessage(hwndTT, TTM_ADDTOOL, 0, reinterpret_cast<LPARAM>(&ti));

//?查看最近文檔

ZeroMemory(&ti, sizeof(TOOLINFO));

ti.cbSize = sizeof(TOOLINFO);

ti.uFlags = TTF_IDISHWND | TTF_SUBCLASS;

ti.hwnd = hDlg;

ti.uId = reinterpret_cast<UINT>(GetDlgItem(hDlg, IDC_RECENT));

ti.lpszText = __TEXT("查看最近文檔");

SendMessage(hwndTT, TTM_ADDTOOL, 0, reinterpret_cast<LPARAM>(&ti));

//?查看Windows

ZeroMemory(&ti, sizeof(TOOLINFO));

ti.cbSize = sizeof(TOOLINFO);

ti.uFlags = TTF_IDISHWND | TTF_SUBCLASS;

ti.hwnd = hDlg;

ti.uId = reinterpret_cast<UINT>(GetDlgItem(hDlg, IDC_WINDOWS));

ti.lpszText = __TEXT("Look in Windows");

SendMessage(hwndTT, TTM_ADDTOOL, 0, reinterpret_cast<LPARAM>(&ti));

}

我們建立一個(gè)新的工具窗口,并定義了五個(gè)工具,每一個(gè)都有自己的顯示文字。在每一個(gè)定義新工具的塊中,uFlags字段指定uId字段表示窗口Handle(TTF_IDISHWND)。意思是這個(gè)窗口,即按鈕之一是一個(gè)顯示分配給lpszText文字的區(qū)域。

?????????對(duì)于要顯示的標(biāo)簽,本質(zhì)上是一個(gè)窗口區(qū)域,它具有發(fā)送適當(dāng)?shù)氖髽?biāo)通知的到提示窗口的能力。TTF_SUBCLASS標(biāo)志使提示窗口自動(dòng)子類為按鈕以便發(fā)送消息。

?

組裝代碼

????為了盡可能地重用代碼,下面的函數(shù)是圍繞標(biāo)準(zhǔn)GetOpenFileName()API函數(shù)構(gòu)件的一個(gè)封裝,它接收指向OPENFILENAME結(jié)構(gòu)的指針作為輸入。如果這個(gè)結(jié)構(gòu)中包含了非空的模版字段,則調(diào)用標(biāo)準(zhǔn)函數(shù),換句話說,如果用戶請(qǐng)求了定制的文件夾,則這個(gè)函數(shù)只是調(diào)用原始例程,否則,它用我們開發(fā)的對(duì)話框模版置換標(biāo)準(zhǔn)的:

#include <commdlg.h>

BOOL GetOpenFileNameEx(LPOPENFILENAME lpofn)

{

//?如果模板是定制的,回復(fù)到標(biāo)準(zhǔn)對(duì)話框

if(lpofn->lpTemplateName)

return GetOpenFileName(lpofn);

//?調(diào)整?OPENFILENAME?結(jié)構(gòu)

lpofn->hInstance = GetModuleHandle(NULL);

lpofn->lpTemplateName = MAKEINTRESOURCE(IDD_DIALOG);

lpofn->lpfnHook = OpenDlgExProc;

lpofn->Flags |= OFN_EXPLORER | OFN_ENABLEHOOK | OFN_ENABLETEMPLATE;

BOOL b = GetOpenFileName(lpofn);

return b;

}

由上函數(shù)使用的回調(diào)需要處理兩個(gè)消息。由于對(duì)話框構(gòu)造而接收到WM_NOTIFY消息時(shí),必須調(diào)用InitNewButtons()函數(shù)來設(shè)置按鈕的圖標(biāo)和提示。如果接收到WM_COMMAND消息,我們就檢查按下的是哪一個(gè)按鈕,并作出適當(dāng)?shù)捻憫?yīng):

UINT CALLBACK OpenDlgExProc(HWND hDlg, UINT uiMsg, WPARAM wParam, LPARAM lParam)

{

LPOFNOTIFY pN = NULL;

switch(uiMsg)

{

case WM_NOTIFY:

pN = reinterpret_cast<LPOFNOTIFY>(lParam);

if(pN->hdr.code == CDN_INITDONE)

InitNewButtons(hDlg);

break;

case WM_COMMAND:

switch(LOWORD(wParam))

{

case IDC_FAVORITES:

case IDC_RECENT:

case IDC_WINDOWS:

Goto(hDlg, LOWORD(wParam));

break;

}

break;

}

return 0;

}

現(xiàn)在所有剩下的是使用來自主應(yīng)用對(duì)話框OnOK()函數(shù)的適當(dāng)變量調(diào)用GetOpenFileNameEx()函數(shù)。下面是它的代碼:

void OnOK(HWND hDlg)

{

//?局部數(shù)據(jù)

OPENFILENAME ofn;

TCHAR szFile[MAX_PATH] = {0};

ZeroMemory(&ofn, sizeof(OPENFILENAME));

ofn.lStructSize = sizeof(OPENFILENAME);

ofn.hwndOwner = hDlg;

ofn.lpstrFile = szFile;

ofn.nMaxFile = sizeof(szFile);

ofn.lpstrFilter = "All files/0*.*/0";

ofn.nFilterIndex = 1;

ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY;

if(GetOpenFileNameEx(&ofn))

MessageBox(hDlg, ofn.lpstrFile, "Open", MB_OK | MB_ICONINFORMATION);

}

?

防止重命名項(xiàng)

????默認(rèn)情況下‘打開’對(duì)話框允許刪除和重命名它顯示的文件。這個(gè)特征是由系統(tǒng)提供的,而且沒有標(biāo)志來抑制它。然而,在很多情況下,并不需要這個(gè)功能,有時(shí)它會(huì)變得非常討厭。現(xiàn)在讓我們來看一下怎樣關(guān)閉這個(gè)功能。

????允許列出文件的控件是列表觀察控件,它具有LBS_EDITLABELS風(fēng)格。為了得到我們想要的行為,只需要把這個(gè)位關(guān)閉掉即可。最困難的是怎樣取得這個(gè)列表觀察控件的Handle。

void ModifyStyle(HWND hDlg)

{

//?取得文件的列表觀察Handle

HWND hwndDefView = GetDlgItem(GetParent(hDlg), lst2);

HWND hwndListView = GetDlgItem(hwndDefView, 1);

//?關(guān)閉這個(gè)位

DWORD dwStyle = GetWindowLong(hwndListView, GWL_STYLE);

dwStyle &= ~LVS_EDITLABELS;

SetWindowLong(hwndListView, GWL_STYLE, dwStyle);

}

列表觀察是窗口容器的子控件,其ID為lst2——這是另一個(gè)來自dlg.h頭文件的值。這個(gè)列表觀察的ID是1,這主要是由Spy++獲得。必須在每次用戶改變目錄后調(diào)用上面函數(shù),因?yàn)槊看文夸洶l(fā)生改變時(shí)都銷毀和重建這個(gè)觀察。一個(gè)好方法是響應(yīng)CDN_FOLDERCHANGE通知:

case WM_NOTIFY:

pN = reinterpret_cast<LPOFNOTIFY>(lParam);

if(pN->hdr.code == CDN_INITDONE)

InitNewButtons(hDlg);

if(pN->hdr.code == CDN_FOLDERCHANGE)

ModifyStyle(hDlg);

break;

?

文件刪除提示

?????????即使你子類化了列表觀察,你也不能捕捉到對(duì)應(yīng)的‘刪除’鍵。顯然探測(cè)器通過處理消息的鍵盤鉤子陷落了文件刪除的請(qǐng)求,然后吃掉了它。

?????????如此對(duì)于我們最好的方法也是安裝鍵盤鉤子,用以陷落‘刪除’鍵,陷落消息,然后中斷鉤子鏈,就象探測(cè)器那樣。此時(shí)探測(cè)器不能得到消息,因此將不能刪除文件。鉤子應(yīng)該在調(diào)用GetOpenFileName()之前安裝,而后立即刪除。代碼有一點(diǎn)小小的改動(dòng):

BOOL GetOpenFileNameEx(LPOPENFILENAME lpofn)

{

//?如果模版是定制的,回復(fù)到標(biāo)準(zhǔn)對(duì)話框

if(lpofn->lpTemplateName)

return GetOpenFileName(lpofn);

//?調(diào)整?OPENFILENAME?結(jié)構(gòu)

lpofn->hInstance = GetModuleHandle(NULL);

lpofn->lpTemplateName = MAKEINTRESOURCE(IDD_DIALOG);

lpofn->lpfnHook = OpenDlgExProc;

lpofn->Flags |= OFN_EXPLORER | OFN_ENABLEHOOK | OFN_ENABLETEMPLATE;

//?設(shè)置鍵盤鉤子到當(dāng)前線程

g_hHook = SetWindowsHookEx(WH_KEYBOARD,HookProc,NULL,GetCurrentThreadId());

BOOL b = GetOpenFileName(lpofn);

//?刪除鉤子

UnhookWindowsHookEx(g_hHook);

return b;

}

每當(dāng)在打開對(duì)話框中按鍵時(shí)鉤子過程都被調(diào)用,這個(gè)鉤子過程是非常簡單的,下面是它的代碼:

LRESULT CALLBACK HookProc(int iCode, WPARAM wParam, LPARAM lParam)

{

//?吃掉?DELETE?鍵

if(wParam == VK_DELETE)

return 1;

//?否則自由處理

return CallNextHookEx(g_hHook, iCode, wParam, lParam);

}

?

什么是Shell集成的應(yīng)用

我們已經(jīng)花費(fèi)了一整章的內(nèi)容來討論把應(yīng)用與Shell集成的各個(gè)方面。我們還發(fā)現(xiàn),有一定數(shù)量的特征是可以編進(jìn)可執(zhí)行程序的。包括解析命令行參數(shù),確保應(yīng)用單一運(yùn)行實(shí)例等。還有一些其它的特征,它們是不必貼附到只應(yīng)用上的,如文件類型的注冊(cè)和關(guān)聯(lián)菜單的改進(jìn)等。基本上有兩個(gè)層次的Shell集成。第一個(gè)是美化和瞄準(zhǔn)用戶界面的:完好的文件類型名,完好的圖標(biāo)和新的關(guān)聯(lián)菜單項(xiàng)。其次是編寫代碼,有一定的全程應(yīng)用設(shè)計(jì)規(guī)則,還有其它許多小技巧,比如處理WM_ENDSESSION消息導(dǎo)致應(yīng)用自動(dòng)在下一次登錄時(shí)運(yùn)行,以及添加快捷方式到‘Favorites’和‘最近文檔’文件夾等。

?

小結(jié)

?????????在這一章中,我們論證了Win32應(yīng)用與系統(tǒng)Shell集成的各方面技術(shù)。我們調(diào)查了Shell集成的方法,要考慮的各個(gè)方面,以及怎樣設(shè)計(jì)和工程化存在的代碼以獲得Shell對(duì)應(yīng)用處理文檔的支持。我們還討論了客戶化和擴(kuò)展‘打開’對(duì)話框的能力。

?????????這一章所設(shè)計(jì)的主要科目是:

???????????????????文件類和存儲(chǔ)在注冊(cè)表中的信息

???????????????????關(guān)聯(lián)菜單的客戶化

???????????????????經(jīng)由Shell建立新文檔

???????????????????應(yīng)用的命令行

???????????????????怎樣編寫基于對(duì)話框的單實(shí)例應(yīng)用

???????????????????客戶化打開文件公共對(duì)話框

???????????????????使應(yīng)用為Shell感知的原理

然而我們說Shell擴(kuò)展是擴(kuò)展WindowsShell能力和行為的最靈活有力的方法。下一章我們將探討這個(gè)課題。

轉(zhuǎn)載于:https://www.cnblogs.com/songtzu/p/3239866.html

總結(jié)

以上是生活随笔為你收集整理的[转]Windows Shell 编程 第十四章【来源:http://blog.csdn.net/wangqiulin123456/article/details/7988010】...的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。