[转]Windows Shell 编程 第十四章【来源:http://blog.csdn.net/wangqiulin123456/article/details/7988010】...
第十四章?設(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
?????????MDI和SDI是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)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 《分拣机械臂测试》- 端拾器最大吸力测试
- 下一篇: windows 文件名太长无法删除的解决