PDF全文下載地址:http://download.csdn.net/source/2320280
http://bbs.driverdevelop.com/read.php?tid-120461.html
《USB 軟件結(jié)構(gòu) 》
軟件結(jié)構(gòu)比硬件來(lái)的復(fù)雜很多。因?yàn)樗嗽S多從表面上看不到的層次。比如總線驅(qū)動(dòng)、功能驅(qū)動(dòng)、過(guò)濾驅(qū)動(dòng)等。套用社會(huì)學(xué)的話,這體現(xiàn)了功能應(yīng)用中的分工和統(tǒng)籌。下面我們逐層來(lái)看它們。
總線驅(qū)動(dòng)
總線驅(qū)動(dòng)位于驅(qū)動(dòng)棧的最低層,處理復(fù)雜的任務(wù),必須資源分配,子設(shè)備管理。作為下層驅(qū)動(dòng),負(fù)責(zé)處理上層驅(qū)動(dòng)發(fā)下來(lái)的請(qǐng)求。 USB 設(shè)備中的總線驅(qū)動(dòng)主要有兩類:控制器驅(qū)動(dòng)、 HUB 驅(qū)動(dòng);另外還有一個(gè)端口驅(qū)動(dòng)。
1 ) 控制器驅(qū)動(dòng): Ushohci.sys 、 Usbuhci.sys 、 Usbehci.sys 。
首先解釋一下 HCI ,它是主機(jī)控制接口( Host Control Interface )的縮寫。前后一共有三種 HCI 協(xié)議出現(xiàn): USB 1.1 時(shí)代,有 OHCI (開發(fā) HCI )協(xié)議和 UHCI (通用 HCI )協(xié)議; USB2.0 時(shí)代,有 EHCI (擴(kuò)展 HCI )協(xié)議。三個(gè)協(xié)議分別對(duì)應(yīng)了上面的三個(gè)驅(qū)動(dòng)程序。因?yàn)?USB 是向后兼容的,所以 UsbEHC.sys 中也包含了 UsbOhci 和 UsbUhci 的功能。請(qǐng)看下圖。
圖 1 總線設(shè)備
上圖中設(shè)備 1 、 3 是控制器設(shè),從名稱上就可以區(qū)別它們的不同: Universal Host Controller 和 Enhanced Host Controller 。所以設(shè)備 1 的驅(qū)動(dòng)程序是 USBUhci.sys ,設(shè)備 3 的驅(qū)動(dòng)程序是 USBEhci.sys 。
這說(shuō)明同一臺(tái)主機(jī)特別是筆記本電腦中, 1.1 和 2.0 的控制器并存,像筆記本電腦中的鍵盤通過(guò)內(nèi)置 USB 接口與系統(tǒng)連接,其數(shù)據(jù)吞吐量小,只需要 1.1 的控制器就能滿足;而暴露在外供用戶使用的接口則需要 2.0 。
2) Hub 驅(qū)動(dòng): UsbHub.sys 。 Hub 驅(qū)動(dòng)是所有 USB 設(shè)備的父驅(qū)動(dòng)。下圖描繪了這一景況:
圖2
上圖中看到, Hub 驅(qū)動(dòng)的子設(shè)備要不是獨(dú)立設(shè)備,如左側(cè)圖;要么是一個(gè)含有多個(gè)子設(shè)備的父設(shè)備,如右側(cè)圖。 Hub 驅(qū)動(dòng)只為直系子設(shè)備創(chuàng)建唯一的物理設(shè)備對(duì)象。上圖中, Hub 驅(qū)動(dòng)為設(shè)備 1 和設(shè)備 2 創(chuàng)建物理設(shè)備對(duì)象,但并不為設(shè)備 2 的子設(shè)備創(chuàng)建物理設(shè)備對(duì)象。
3 ) Port 驅(qū)動(dòng): UsbPort.sys 。這是個(gè)框架驅(qū)動(dòng),比較復(fù)雜,很少人會(huì)用到。而上面的 Ushohci.sys 、 Usbuhci.sys 、 Usbehci.sys 其實(shí)都是他的微端口驅(qū)動(dòng)。對(duì)于這么偏門的框架,不提也罷。
系統(tǒng)類驅(qū)動(dòng)
所以出現(xiàn)類驅(qū)動(dòng),體現(xiàn)了 USB 總線在應(yīng)用上的繁榮景象。只有用得多了,才有被歸類的可能。就像人類社會(huì)中有幾百個(gè)國(guó)家,幾千個(gè)民族,正是體現(xiàn)了人類這個(gè)團(tuán)體的多樣性與繁榮。只有在“多”的基礎(chǔ)上,分類才是有必要的;少數(shù)人的小群體,再怎么獨(dú)立特行,也都不足以被分類,甚至定義為“ XX 民族”。
USB 設(shè)備包含很多的通用的功能類,比如: USB 集線器設(shè)備, USB HID 設(shè)備, USB 音頻設(shè)備, USB MIDI 設(shè)備, USB 存儲(chǔ)設(shè)備。為了讓開發(fā)工作變得更加簡(jiǎn)單, Windows 操作系統(tǒng)為他們提供了系統(tǒng)驅(qū)動(dòng)程序。
大部分時(shí)候,系統(tǒng)類驅(qū)動(dòng)就是功能驅(qū)動(dòng)。下表是系統(tǒng)提供的 USB 類驅(qū)動(dòng)。
USB 類名稱
類代碼
驅(qū)動(dòng)名稱
系統(tǒng)支持
藍(lán)牙設(shè)備
0xE0
Bthusb.sys
Vista 、 XP
USB 芯片智能卡接口設(shè)備
(CCID)
0x0B
Usbccid.sys
2K 、 XP 、 2K3 、 Vista 、 2K8
Hub 設(shè)備
0x09
Usbhub.sys
2K 、 XP 、 2K3 、 Vista 、 2K8
HID 設(shè)備
0x03
Hidusb.sys
2K 、 XP 、 2K3 、 Vista 、 2K8
USB 大容量存儲(chǔ)設(shè)備
0x08
Usbstor.sys
2K 、 XP 、 2K3 、 Vista 、 2K8
打印設(shè)備
0x07
Usbprint.sys
2K 、 XP 、 2K3 、 Vista 、 2K8
掃描設(shè)備
0x06
WpdUsb.sys Usbscan.sys
2K 、 XP 、 2K3 、 Vista 、 2K8
媒體傳輸設(shè)備 (MTP)
0x06
WpdUsb.sys
XP 、 2K3 、 Vista 、 2K8
音頻設(shè)備
0x01
Usbaudio.sys
2K 、 XP 、 2K3 、 Vista 、 2K8
Modem 設(shè)備 (CDC)
0x02
Usbser.sys
2K 、 XP 、 2K3 、 Vista 、 2K8
視頻設(shè)備 (UVC)
0x0E
Usbvideo.sys
XP 、 Vista
表 1 系統(tǒng)提供的 USB 類驅(qū)動(dòng)
功能驅(qū)動(dòng)
不是所有的 USB 設(shè)備都有類驅(qū)動(dòng),但功能驅(qū)動(dòng)卻是它們唯一的身份證。沒(méi)有功能驅(qū)動(dòng),設(shè)備就不足以在系統(tǒng)中存在。它的作用是為設(shè)備創(chuàng)造一個(gè)獨(dú)一無(wú)二的內(nèi)核設(shè)備對(duì)象( DEVICE_OBJCET ),并因此而在需要的時(shí)候,系統(tǒng)能夠通過(guò)此內(nèi)核設(shè)備對(duì)象找到它。
如果要讓用戶層也能夠知道并使用 USB 設(shè)備,功能驅(qū)動(dòng)更加不可少。它為設(shè)備在用戶程序可見(jiàn)的名字空間中,為它起一個(gè)別名,這個(gè)別名可以是一個(gè)符號(hào)鏈接,也可以是一個(gè)由 GUID 定義的設(shè)備 Interface 。通過(guò)對(duì)這個(gè)別名進(jìn)行操作,也就是對(duì)設(shè)備本身進(jìn)行操作。
OK ,上面的話說(shuō)得太滿了,有例外的!唯一的例外是以 RAW 模式驅(qū)動(dòng)的設(shè)備。這種設(shè)備直接由總線驅(qū)動(dòng)來(lái)驅(qū)動(dòng)其工作,不需要功能驅(qū)動(dòng)。這種例子真的不多見(jiàn),也許只有很很底層的控制器設(shè)備、 Hub 設(shè)備之類,才會(huì)這樣做。對(duì)于 RAW 模式驅(qū)動(dòng)的設(shè)備,當(dāng)收到 IRP_MN_QUERY_CAPABILITIES 查詢請(qǐng)求的時(shí)候,在返回的 DEVICE_CAPABILITIES 結(jié)構(gòu)體中,必須將 RawDeviceOK 位設(shè)置為 TRUE 。
建議讀者在需要的時(shí)候使用 WinOBJ.exe 工具查看系統(tǒng)空間中的設(shè)備與別名。
父驅(qū)動(dòng)與混合設(shè)備
通過(guò)一定的設(shè)置, USB 設(shè)備中的每個(gè)接口可以擁有不同的 Class 和 Protocol 定義,從而實(shí)現(xiàn):一個(gè)設(shè)備,多個(gè)功能。這種設(shè)備被稱為混合設(shè)備。混合設(shè)備的前提是擁有多個(gè)接口,對(duì)單接口設(shè)備談 “ 混合 ” 是沒(méi)有意義的。
滿足了如下兩個(gè)條件的多接口 USB 設(shè)備,被系統(tǒng)認(rèn)為是混合設(shè)備:
1. 設(shè)備描述符中,設(shè)備類的值為 0 : (bDeviceClass , bDeviceSubClass, bDeviceProtocol ) = (0, 0, 0);
2. 只有唯一的配置描述符,即設(shè)備描述符中: (bNumConfigurations ) = (1)
從 WinXP SP2 以后,還支持另外一種混合設(shè)備的判別方式,稱作: Interface Association Descriptor ( IAD )。其實(shí) IAD 描述符是用來(lái)組織 “ 接口組( Interfaces Group ) ” 的。配置描述符中可以有多個(gè) IAD 存在,如果將某兩個(gè)接口將組成接口組,那么首先這兩個(gè)接口必須是緊挨著的,其次,必須有一個(gè) IAD 描述符位于這兩個(gè)接口描述符的前面,也必須是緊挨著的, IAD 描述符中的 bFirstInterface 用來(lái)描述接口組中的第一個(gè)接口 ID , bInterfaceCount 用來(lái)描述接口組中包含多少個(gè)接口。這樣接口集合 : [bFirstInterface, bFirstInterface+bInterfaceCount) 為一個(gè)接口組。
當(dāng)然,能夠用上 IAD 的設(shè)備,一定是有多接口存在了,否則就是多此一舉了。 IAD 描述符的識(shí)別和實(shí)現(xiàn)是通用父驅(qū)動(dòng)完成的,所以有 IAD 支持的設(shè)備,都被認(rèn)作混合設(shè)備。而識(shí)別設(shè)備是否有 IAD 支持,是通過(guò)設(shè)備描述符中的如下值判斷的:
(bDeviceClass, bDeviceSubClass, bDeviceProtocol) = (0xEF, 0x02, 0x01)
IAD 普及率不是很廣,一個(gè)原因就是 XP sp2 以后的操作系統(tǒng)版本才對(duì)它支持,這樣如果把設(shè)備插入到 Win 2000 甚至 XP SP1 上,都不能被正確識(shí)別。這樣,廠商可能必須為不同的系統(tǒng)寫兩套驅(qū)動(dòng)程序。
我們下面來(lái)說(shuō)說(shuō)當(dāng)一個(gè)多接口混合設(shè)備插入電腦后,系統(tǒng)是如何識(shí)別它,并為它加載驅(qū)動(dòng)的。這里大家要注意到一點(diǎn),就是系統(tǒng)是如何把一個(gè)設(shè)備,通過(guò)多接口,識(shí)別為多個(gè)物理設(shè)備的。
一開始,系統(tǒng) PNP 管理器安裝常規(guī),讀取并分析 USB 設(shè)備描述符,然后為它分配如下設(shè)備 ID :
USB\VID_vvvv&PID_pppp
USB\VID_vvvv&PID_pppp&REV_rrrr
(vvvv, pppp, rrrr: 4 位 16 進(jìn)制數(shù),分別代表了廠商 ID ,產(chǎn)品 ID , USB 版本。對(duì)應(yīng)于設(shè)備描述符中的這些值: idVendor/ idProduct/ bcdDevice)
和兼容 ID :
USB\COMPOSITE
PNP 管理器首先按照常規(guī),根據(jù)上述的設(shè)備 ID 為設(shè)備尋找合適的驅(qū)動(dòng)程序:根據(jù)設(shè)備 ID 到注冊(cè)表的設(shè)備安裝信息庫(kù)(對(duì)應(yīng)于 Enum 和 Class 兩個(gè)鍵)中進(jìn)行搜索,如果找到了安裝記錄,就根據(jù)記錄中的信息加載驅(qū)動(dòng)程序。
問(wèn)題是如果找不到合法的記錄怎么辦?這時(shí)候就用的著兼容 ID 了,兼容 ID “ USB\COMPOSITE ”是在系統(tǒng)中有注冊(cè)記錄的,并且就對(duì)應(yīng)著通用父設(shè)備驅(qū)動(dòng)( USBCCGP.sys) 。于是,系統(tǒng)為混合設(shè)備加載通用父設(shè)備驅(qū)動(dòng)。讀者可到目錄 Windows\Inf 下查看 usb.inf 文件,此文件中包含了通用父設(shè)備驅(qū)動(dòng)的安裝信息。
通用父設(shè)備驅(qū)動(dòng)通過(guò)分析配置描述符,完成兩個(gè)動(dòng)作:首先為每個(gè) USB 接口分配一個(gè)的設(shè)備 ID 和兼容 ID ;然后為每個(gè) USB 接口創(chuàng)建一個(gè)物理設(shè)備對(duì)象( Physical Device Object )。設(shè)備 ID 形式如下:
USB\VID_vvvv&PID_pppp&MI_mm
USB\VID_vvvv&PID_pppp&REV_rrrr&MI_mm
(mm: 兩個(gè) 16 位數(shù)字表示的接口號(hào) )
根據(jù)接口描述符中的 clsss 類型,分配兼容 ID ,其形式如下:
USB\CLASS_cc
USB\CLASS_cc&SUBCLASS_ss
USB\CLASS_cc&SUBCLASS_ss&PROT_pp
(cc/ ss / pp: 兩位 16 進(jìn)制數(shù)。分別對(duì)應(yīng)于接口描述符中的: bInterfaceClass/ bInterfaceSubClass/ bInterfaceProtocol)
通用父設(shè)備驅(qū)動(dòng)為每個(gè)接口創(chuàng)建了物理設(shè)備對(duì)象后,將接口的設(shè)備 ID 、兼容 ID 信息提交給 PNP 管理器, PNP 管理器就有責(zé)任為這些“虛假的”物理設(shè)備安裝驅(qū)動(dòng)。仍然重復(fù)上面的過(guò)程:根據(jù)每個(gè)接口的設(shè)備 ID 和兼容 ID 在注冊(cè)表的設(shè)備安裝信息庫(kù)中搜索,試圖找到相關(guān)的安裝記錄,如果找到了,就為接口加載相應(yīng)的驅(qū)動(dòng)程序。如果找不到,系統(tǒng)就會(huì)彈出“發(fā)現(xiàn)新設(shè)備”的對(duì)話框,啟動(dòng)驅(qū)動(dòng)安裝向?qū)?。此時(shí)用戶需為這些接口手動(dòng)安裝驅(qū)動(dòng)。
所以,對(duì)于多接口的混合設(shè)備( composite device ),其設(shè)備驅(qū)動(dòng)的安裝分為兩個(gè)過(guò)程,先嘗試安裝混合驅(qū)動(dòng),如果找不到,就默認(rèn)安裝通用父設(shè)備驅(qū)動(dòng);接下來(lái)通用父設(shè)備驅(qū)動(dòng)為每個(gè)接口分配設(shè)備 ID ,并要求系統(tǒng)為每個(gè)接口啟動(dòng) PNP 過(guò)程。
最后,那么來(lái)說(shuō),在 USB 混合設(shè)備中,一定是 X 個(gè)接口對(duì)應(yīng)于 X 個(gè)功能設(shè)備(有一個(gè)物理設(shè)備對(duì)象)嗎?一般情況下是這樣的,但也有例外,這就是接口組:在符合一定條件的情況下,多個(gè)接口中的某些接口可以被組合為一個(gè)接口組,一個(gè)接口組代表一個(gè)功能,父設(shè)備驅(qū)動(dòng)只為之創(chuàng)建一個(gè)物理設(shè)備對(duì)象。
接口組的詳情,大家看后面的章節(jié)內(nèi)容。
過(guò)濾驅(qū)動(dòng)
過(guò)濾驅(qū)動(dòng)無(wú)處不在。在很多時(shí)候,它被稱作 Hook ,是一種 Hack 手段;很多時(shí)候它又是必不可少的,這種技術(shù)甚至被操作系統(tǒng)自己使用。沒(méi)有哪一個(gè)殺防毒軟件不使用過(guò)濾驅(qū)動(dòng),我們?cè)谑褂镁W(wǎng)上銀行的時(shí)候,會(huì)用到一些安全控件,基本上都借助了過(guò)濾驅(qū)動(dòng)的技術(shù)。
過(guò)濾驅(qū)動(dòng)可以位于任何一層驅(qū)動(dòng)的上面,或下面。過(guò)濾的對(duì)象也包括已經(jīng)存在于系統(tǒng)中的其他的過(guò)濾驅(qū)動(dòng)。當(dāng)它位于某層驅(qū)動(dòng)( D 驅(qū)動(dòng))上面的時(shí)候,所有目標(biāo)發(fā)往 D 驅(qū)動(dòng)的請(qǐng)求,都首先被它截取;當(dāng)它位于某層驅(qū)動(dòng)下面的時(shí)候,所有和 D 驅(qū)動(dòng)相關(guān)的從更底層驅(qū)動(dòng)反饋回來(lái)的的“完成消息”都預(yù)先被過(guò)濾驅(qū)動(dòng)截取。這正是它威力強(qiáng)大的原因所在。對(duì)于被過(guò)濾的驅(qū)動(dòng)來(lái)說(shuō),過(guò)濾驅(qū)動(dòng)簡(jiǎn)直就是它的先知了。
但使用過(guò)濾驅(qū)動(dòng),要很慎重。很容易把系統(tǒng)搞得很不穩(wěn)定。讀者在寫過(guò)濾驅(qū)動(dòng)的時(shí)候,要明白這樣一件事:你想過(guò)濾誰(shuí),得先了解誰(shuí);好像你追求一個(gè)人,要先認(rèn)識(shí)這個(gè)人。否則死機(jī)藍(lán)屏都會(huì)與你不期而遇。
USB 驅(qū)動(dòng)棧、設(shè)備棧
請(qǐng)大家不要把這里的“棧”理解成“程序堆棧”的那個(gè)棧,朋友們要回到這個(gè)字最簡(jiǎn)單的本意來(lái)理解它,而不是想象成一個(gè)數(shù)據(jù)結(jié)構(gòu)。就看成草垛柴堆一樣。
驅(qū)動(dòng)棧、設(shè)備棧本質(zhì)上是并行概念。驅(qū)動(dòng)之間的聯(lián)系是通過(guò)設(shè)備對(duì)象進(jìn)行的,所以驅(qū)動(dòng)之間是間接聯(lián)系的,驅(qū)動(dòng)棧多少也就只是概念上的,他用來(lái)表示一個(gè)設(shè)備能夠在系統(tǒng)中識(shí)別、運(yùn)行,從上到下中共需要哪些驅(qū)動(dòng)程序支持。
設(shè)備棧則是由據(jù)可查的。系統(tǒng)中每個(gè) DevNode 就表現(xiàn)一個(gè)設(shè)備棧。可以這樣理解,多個(gè)設(shè)備棧,串聯(lián)成了驅(qū)動(dòng)棧。
使用 WinDBG 的 !devnode 命令,可以列舉系統(tǒng)中的設(shè)備樹。下圖截取了 CY001 相關(guān)片段。
圖 3 CY001 DevNode 片段
上圖中紅色框標(biāo)出的三個(gè) DevNode ,正好對(duì)應(yīng)于三個(gè)內(nèi)核驅(qū)動(dòng),建立了 CY001 的驅(qū)動(dòng)棧。從上到下分別是控制器驅(qū)動(dòng) (usbEHCI) ,集線器驅(qū)動(dòng) (usbHUB) ,和功能驅(qū)動(dòng) (CY001) 。下圖以 CY001 為例,更加清晰詳細(xì)地描繪了驅(qū)動(dòng)棧、設(shè)備棧的面貌。
圖 4 CY001 的驅(qū)動(dòng)棧和設(shè)備棧
上圖是單接口 CY001 設(shè)備的驅(qū)動(dòng)棧、設(shè)備棧全圖。也適用于所有其他的單接口 USB 設(shè)備。如果是多接口混合設(shè)備,就要多一個(gè)通用父驅(qū)動(dòng),稍微復(fù)雜一點(diǎn)。
最上面是可能存在的過(guò)濾驅(qū)動(dòng),因?yàn)橹皇?#8220;可能存在”,所以都用虛框表示。其實(shí)過(guò)濾驅(qū)動(dòng)可以存在于設(shè)備棧的任何一個(gè)位置,而不僅僅是最上層。筆者不可能盡皆畫全,以上層過(guò)濾為例,能說(shuō)明問(wèn)題也就可以了。
過(guò)濾驅(qū)動(dòng)生成的過(guò)濾設(shè)備對(duì)象,掛載到 CY001 驅(qū)動(dòng)生成的功能設(shè)備對(duì)象上;這樣所有發(fā)送給 CY001 功能設(shè)備對(duì)象的請(qǐng)求,過(guò)濾設(shè)備對(duì)象總是先得到。根集線器生成了 CY001 驅(qū)動(dòng)的物理設(shè)備對(duì)象。三個(gè)設(shè)備連在一起,就是 CY001 驅(qū)動(dòng)程序的設(shè)備棧。
但還沒(méi)有完,根集線器驅(qū)動(dòng)并不是最底層驅(qū)動(dòng),他必須得到控制器驅(qū)動(dòng)的支持,于是加上可能存在的過(guò)濾驅(qū)動(dòng),由此形成中間的那條設(shè)備棧。
仍舊未結(jié)束,控制器驅(qū)動(dòng)也不是直接和系統(tǒng)交互的,而是通過(guò)更底層的系統(tǒng)總線如 PCI 、 ACPI 進(jìn)行的。這樣,右側(cè)的第三條設(shè)備棧也形成了。
由此也可以看出設(shè)備棧、驅(qū)動(dòng)棧之間的關(guān)系了。驅(qū)動(dòng)棧是形而上的,設(shè)備棧是形而下的。
本文來(lái)自CSDN博客,轉(zhuǎn)載請(qǐng)標(biāo)明出處:http://blog.csdn.net/changpei/archive/2010/05/06/5562542.aspx
在MFC類庫(kù)提供了CWnd::OnCtlColor函數(shù),在工作框架的子窗口被重畫時(shí)將調(diào)用該成員函數(shù).因此可以重載WM_CTLCOLOR消息的響應(yīng)函數(shù).此函數(shù)的原型:
afx_msg HBRUSH OnCtlColor(CDC *pDC,CWnd *pWnd,UINT nCtlColor);
參數(shù)nCtlColor用于指定控件的類型,可以是:
.CTLCOLOR_BTN 按鈕控件
.CTLCOLOR_DLG 對(duì)話框
.CTLCOLOR_EDIT 編輯框
.CTLCOLOR_LISTBOX 列表控件
.CTLCOLOR_MSGBOX 消息控件
.CTLCOLOR_SCROLLBAR 滾動(dòng)條控件
.CTLCOLOR_STATIC 靜態(tài)控件
[程序?qū)崿F(xiàn)]
假設(shè)你已有了名為My的對(duì)話框工程.你有了一個(gè)STATIC的控件,ID為IDC_STATIC1.
HBRUSH CMyDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
{
HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);
// TODO: Change any attributes of the DC here
if (nCtlColor==CTLCOLOR_STATIC)
{
pDC->SetTextColor(RGB(255,0,0)); //字體顏色
pDC->SetBkColor(RGB(0, 0, 255)); //字體背景色
}
// TODO: Return a different brush if the default is not desired
return hbr;
}
如果要指定某個(gè)特定控件可以這樣寫:ID為IDC_STATIC1
if (pWnd->GetDlgCtrlID()==IDC_STATIC1)
{
pDC->SetTextColor(RGB(255,0,0)); //設(shè)置字體顏色
pDC->SetBkMode(TRANSPARENT); //設(shè)置字體背景為透明
// TODO: Return a different brush if the default is not desired
return (HBRUSH)::GetStockObject(BLACK_BRUSH); // 設(shè)置背景色
}
else
return hbr;
【注】
BLACK_BRUSH:黑色
WHITE_BRUSH:白色
GRAY_BRUSH:灰色
NULL_BRUSH:透明
HOLLOW_BRUSH :透明
1.為對(duì)話框類添加WM_CTLCOLOR的響應(yīng)函數(shù)afx_msg HBRUSH OnCtlColor(CDC*pDC,CWnd*pWnd,UINT nCtlColor){...}
2.定義一個(gè)m_brush(CBrush類型)的成員變量和一個(gè)m_font(CFont類型)成員變量,在構(gòu)造函數(shù)中初始化,例如:m_brush.CreateSolidBrush(RGB(0,0,255));m_font.CreatePointFont(200,"華文行楷");
3.改變背景顏色和文本顏色和字體:在OnCtlColor()添加代碼:
if(pWnd->GetDlgCtrlID()==IDC_LINE_STYLE/*控件ID*/)
{
pDC->SetTextColor(RGB(255,0,0));
pDC->SetBkMode(TRANSPARENT);//設(shè)置文本背景色為透明
pDC->SelectObject(&m_font);//設(shè)置字體
return m_brush;//設(shè)置控件背景顏色
}
//對(duì)于按鈕來(lái)說(shuō)上面的方法無(wú)效
此為我程序中的一個(gè)類,本用于WinCE,但在桌面系統(tǒng)上也同樣適用!
使用方法(在WM_INITDIALOG或WM_CREATE消息中加入):
CWindowAnchor::BeginControlBound(hwnd)
手動(dòng)調(diào)整控件位置:
CWindowAnchor::AddControl(hwnd,IDC_STATIC1,&WindowAnchorInfo(WAT_LEFT|WAT_TOP,2,8,4,10));
CWindowAnchor::AddControl(hwnd,IDC_STATIC1,&WindowAnchorInfo(WAT_LEFT|WAT_TOP|WAT_RIGHT,2,20,4,10));
CWindowAnchor::AddControl(hwnd,IDC_STATIC1,&WindowAnchorInfo(WAT_LEFT|WAT_TOP,2,8,40,10));
自動(dòng)調(diào)整控件位置(跟據(jù)設(shè)計(jì)時(shí)資源文件中控件的大小及位置):
CWindowAnchor::AddControl(hwnd,IDC_STATIC1,&WindowAnchorInfo(WAT_LEFT|WAT_TOP));
CWindowAnchor::AddControl(hwnd,IDC_STATIC1,&WindowAnchorInfo(WAT_LEFT|WAT_TOP|WAT_RIGHT));
響應(yīng)WM_SIZE消息:
case WM_SIZE:
return HANDLE_WM_SIZE(hwndDlg,wParam,lParam,CWindowAnchor::OnSize);
響應(yīng)WM_DESTROY消息:
CWindowAnchor::EndControlBound(hwnd);
代碼:
#pragma once
#include <map>
#if defined (_MSC_VER)
#pragma warning(disable: 4786)
#endif
/*用于WindowAnchorInfo結(jié)構(gòu)的停靠類型*/
typedef enum WindowAnchorType
{
WAT_TOP=0x0001,
WAT_LEFT=0x0002,
WAT_RIGHT=0x0004,
WAT_BOTTOM=0x0008
};
/*控件定位描述信息*/
typedef struct WindowAnchorInfo{
DWORD dwAnchor; //WAT_*
RECT rcOriginalRect; //控件的原始邊距,如果為空則自動(dòng)獲?。▋H適用于WM_INIT中)
WindowAnchorInfo(DWORD pAnchor=WAT_TOP|WAT_LEFT,LONG pLeft=0,LONG pTop=0,LONG pRight=0,LONG pBottom=0)
{
dwAnchor=pAnchor;
rcOriginalRect.left=pLeft;
rcOriginalRect.top=pTop;
rcOriginalRect.right=pRight;
rcOriginalRect.bottom=pBottom;
};
};
typedef std::map<HWND,WindowAnchorInfo> ControlHashtable;
typedef struct{
INT nWidth; //對(duì)話框?qū)挾?/span>
INT nHeight; //對(duì)話框高度
INT nMinHeight; //對(duì)話框最小高度
ControlHashtable mapControls; //對(duì)話框所有子控件
}WindowAnchorDialog;
/*
* 對(duì)話框子控件定位
* 2009.03.29 By Frank
*/
static class CWindowAnchor
{
private:
static BOOL _ReSize(HWND hwndDlg, const WindowAnchorDialog *wad, HWND hwndCtrl, const WindowAnchorInfo *wai);
public:
/*
* 開始調(diào)整(此調(diào)用中會(huì)獲取當(dāng)前對(duì)話框的大小,如果在設(shè)計(jì)后要調(diào)整對(duì)話框大小,請(qǐng)先調(diào)用此方法)
* hwndDlg:對(duì)話框句柄
*/
static BOOL BeginControlBound(HWND hwndDlg);
/*
* 結(jié)束調(diào)整
* hwndDlg:對(duì)話框句柄
*/
static BOOL EndControlBound(HWND hwndDlg);
/*
* 添加一個(gè)控件到調(diào)整列表
* hWndInsertAfter:HWND_BOTTOM |HWND_NOTOPMOST | HWND_TOP | HWND_TOPMOST |-2不改變 | Is Hwnd
*/
static BOOL AddControl(HWND hwndDlg, INT nCtrlID, WindowAnchorInfo *wai, HWND hWndInsertAfter=(HWND)-2);
/*
* 調(diào)整一個(gè)指定控件的大小
*/
static BOOL ReSize(HWND hwndDlg, HWND hwndCtrl);
/*
* 響應(yīng)WM_SIZE消息
*/
static BOOL OnSize(HWND hwndDlg, UINT state, int cx, int cy);
/*相應(yīng)WM_VSCROLL消息*/
static BOOL OnVScroll(HWND hwnd, HWND hwndCtl, UINT code, int pos);
};
下載地址:單擊下載
1.首先在初始化函數(shù)中,F(xiàn)ormView在OnInitialUpdate(),Dialog在OnInitDialog()中初始化控件的大小。
view plaincopy to clipboardprint?
01.//開始初始化控件大小
02. m_IsInitialed = false;
03.
04. CRect m_ClientRect;
05. this->GetClientRect(&m_ClientRect);
06. CSize m_Forsize;
07. m_Forsize = GetTotalSize();//在資源編輯器中定好大小后,程序運(yùn)行時(shí)大小(不管最大化和最小化,該大小均為同一個(gè)值),客戶區(qū)大于或等于顯示的大小
08. double m_x = (double)m_ClientRect.Width() / m_Forsize.cx;//寬度方向發(fā)大倍數(shù)
09. double m_y = (double)m_ClientRect.Height() / m_Forsize.cy;//高度方向發(fā)大倍數(shù)
10.
11. //調(diào)整控件的大小
12. CWnd *pWnd = NULL;
13. pWnd = GetWindow(GW_CHILD);
14. while(pWnd)//判斷是否為空,因?yàn)閷?duì)話框創(chuàng)建時(shí)會(huì)調(diào)用此函數(shù),而當(dāng)時(shí)控件還未創(chuàng)建
15. {
16. CRect rect; //獲取控件變化前大小
17. pWnd->GetWindowRect(&rect);
18. ScreenToClient(&rect);//將控件大小轉(zhuǎn)換為在對(duì)話框中的區(qū)域坐標(biāo)
19. m_ControlRect.insert(pair<int, CRect>(pWnd->GetDlgCtrlID(), rect));//保存控件的初始大小,以便在OnSize函數(shù)中繼續(xù)使用
20. int width = rect.Width();
21. int height = rect.Height();
22.
23. WCHAR szBuf[256];
24. GetClassName(pWnd->m_hWnd,szBuf,256);
25. if( _tcsicmp(szBuf,_T("Edit")) == 0)
26. {
27. //Edit只是位置變化,大小沒(méi)有變
28. rect.top = m_y * rect.top;
29. rect.left = m_x * rect.left;
30. rect.bottom = rect.top + height;
31. rect.right = rect.left + width;
32. }
33. else
34. {
35. //其它控件位置和大小均變化
36. rect.top = m_y * rect.top;
37. rect.left = m_x * rect.left;
38. rect.bottom = m_y * rect.bottom;
39. rect.right = m_x * rect.right;
40. }
41.
42. pWnd->MoveWindow(&rect);//設(shè)置控件大小
43. pWnd = pWnd->GetWindow(GW_HWNDNEXT);
44. }
45.
46. //控件初始化結(jié)束
47. m_IsInitialed = true;
//開始初始化控件大小
m_IsInitialed = false;
CRect m_ClientRect;
this->GetClientRect(&m_ClientRect);
CSize m_Forsize;
m_Forsize = GetTotalSize();//在資源編輯器中定好大小后,程序運(yùn)行時(shí)大小(不管最大化和最小化,該大小均為同一個(gè)值),客戶區(qū)大于或等于顯示的大小
double m_x = (double)m_ClientRect.Width() / m_Forsize.cx;//寬度方向發(fā)大倍數(shù)
double m_y = (double)m_ClientRect.Height() / m_Forsize.cy;//高度方向發(fā)大倍數(shù)
//調(diào)整控件的大小
CWnd *pWnd = NULL;
pWnd = GetWindow(GW_CHILD);
while(pWnd)//判斷是否為空,因?yàn)閷?duì)話框創(chuàng)建時(shí)會(huì)調(diào)用此函數(shù),而當(dāng)時(shí)控件還未創(chuàng)建
{
CRect rect; //獲取控件變化前大小
pWnd->GetWindowRect(&rect);
ScreenToClient(&rect);//將控件大小轉(zhuǎn)換為在對(duì)話框中的區(qū)域坐標(biāo)
m_ControlRect.insert(pair<int, CRect>(pWnd->GetDlgCtrlID(), rect));//保存控件的初始大小,以便在OnSize函數(shù)中繼續(xù)使用
int width = rect.Width();
int height = rect.Height();
WCHAR szBuf[256];
GetClassName(pWnd->m_hWnd,szBuf,256);
if( _tcsicmp(szBuf,_T("Edit")) == 0)
{
//Edit只是位置變化,大小沒(méi)有變
rect.top = m_y * rect.top;
rect.left = m_x * rect.left;
rect.bottom = rect.top + height;
rect.right = rect.left + width;
}
else
{
//其它控件位置和大小均變化
rect.top = m_y * rect.top;
rect.left = m_x * rect.left;
rect.bottom = m_y * rect.bottom;
rect.right = m_x * rect.right;
}
pWnd->MoveWindow(&rect);//設(shè)置控件大小
pWnd = pWnd->GetWindow(GW_HWNDNEXT);
}
//控件初始化結(jié)束
m_IsInitialed = true;
2.如果界面在運(yùn)行時(shí)大小可以改變,則在OnSize函數(shù)中加入如下代碼
view plaincopy to clipboardprint?
01.// TODO: 在此處添加消息處理程序代碼
02. CFormView::ShowScrollBar(SB_BOTH, false);//設(shè)置沒(méi)有滾動(dòng)條,視情況而定。
03. //在界面不是最小化并且已經(jīng)初始化完畢
04. if (!IsIconic() && m_IsInitialed)
05. {
06. CSize m_Forsize;
07. m_Forsize = GetTotalSize();
08. double m_x = (double)cx / m_Forsize.cx;
09. double m_y = (double)cy / m_Forsize.cy;
10.
11. //讀取控件的初始大小
12. map<int, CRect>::iterator pos = m_ControlRect.begin();
13. for (; pos != m_ControlRect.end(); ++pos)
14. {
15. CRect rect = pos->second;
16. int width = rect.Width();
17. int height = rect.Height();
18.
19. WCHAR szBuf[256];
20. GetClassName(GetDlgItem(pos->first)->m_hWnd,szBuf,256);
21. if( _tcsicmp(szBuf,_T("Edit")) == 0)
22. {
23. rect.top = m_y * rect.top;
24. rect.left = m_x * rect.left;
25. rect.bottom = rect.top + height;
26. rect.right = rect.left + width;
27. }
28. else
29. {
30. rect.top = m_y * rect.top;
31. rect.left = m_x * rect.left;
32. rect.bottom = m_y * rect.bottom;
33. rect.right = m_x * rect.right;
34. }
35. GetDlgItem(pos->first)->MoveWindow(rect);
36. }
37. }
// TODO: 在此處添加消息處理程序代碼
CFormView::ShowScrollBar(SB_BOTH, false);//設(shè)置沒(méi)有滾動(dòng)條,視情況而定。
//在界面不是最小化并且已經(jīng)初始化完畢
if (!IsIconic() && m_IsInitialed)
{
CSize m_Forsize;
m_Forsize = GetTotalSize();
double m_x = (double)cx / m_Forsize.cx;
double m_y = (double)cy / m_Forsize.cy;
//讀取控件的初始大小
map<int, CRect>::iterator pos = m_ControlRect.begin();
for (; pos != m_ControlRect.end(); ++pos)
{
CRect rect = pos->second;
int width = rect.Width();
int height = rect.Height();
WCHAR szBuf[256];
GetClassName(GetDlgItem(pos->first)->m_hWnd,szBuf,256);
if( _tcsicmp(szBuf,_T("Edit")) == 0)
{
rect.top = m_y * rect.top;
rect.left = m_x * rect.left;
rect.bottom = rect.top + height;
rect.right = rect.left + width;
}
else
{
rect.top = m_y * rect.top;
rect.left = m_x * rect.left;
rect.bottom = m_y * rect.bottom;
rect.right = m_x * rect.right;
}
GetDlgItem(pos->first)->MoveWindow(rect);
}
}
或在OnShowWindow()函數(shù)中加入也可以(特別是在對(duì)話框作為tabpage時(shí))
本文來(lái)自CSDN博客,轉(zhuǎn)載請(qǐng)標(biāo)明出處:http://blog.csdn.net/ybw20041910/archive/2010/06/19/5679730.aspx
1.
重載OnCtlColor (CDC* pDC, CWnd* pWnd, UINT nCtlColor),即WM_CTLCOLOR消息。
---- ①在CExampleDlgDlg的頭文件中,添加一CBrush的成員變量:
class CExampleDlgDlg : public CDialog
{...
protected:
CBrush m_brush;
...
};
---- ②在OnInitDialog()函數(shù)中添加如下代碼:
BOOL CExampleDlgDlg::OnInitDialog()
{
...
// TODO: Add extra initialization here
m_brush.CreateSolidBrush(RGB(0, 255, 0)); // 生成一綠色刷子
...
}
---- ③利用ClassWizard重載OnCtlColor(…),即WM_CTLCOLOR消息:
HBRUSH CExampleDlgDlg::OnCtlColor
(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
{
/*
** 這里不必編寫任何代碼!
**下行代碼要注釋掉
** HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);
*/
return m_brush; //返加綠色刷子
}
2.
修改對(duì)話框的OnPaint,在else中添加如下代碼
CPaintDC dc(this);
CRect rect;
GetClientRect(rect);
dc.FillSolidRect(rect, RGB(0,0,0));
CDialog::OnPaint();
3.
在對(duì)話框的應(yīng)用類(App)的.cpp的Initinstance()中加入代碼:
//加在int nResponse=dlg.DoModal();
前一個(gè)RGB設(shè)置背景色,第二個(gè)設(shè)置字體顏色
SetDialogBkColor(RGB(0,0,255),RGB(0,255,0));
4.
1.在對(duì)話框類中添加成員變量:
public:
CBrush m_brushBlue;
2.在對(duì)話框類的OnInitDialog()中添加代碼:
m_brushBlue.CreateSolidBrush(RGB(0,0,255));
3.用ClassWizard在對(duì)話框類中添加成員函數(shù)OnCtlCollor(),并在其中添加代碼:
if(nCtlColor==CTLCOLOR_DLG)
return m_brushBlue;
本文來(lái)自CSDN博客,轉(zhuǎn)載請(qǐng)標(biāo)明出處:http://blog.csdn.net/mfreesky/archive/2007/08/27/1760222.aspx