程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> VC >> 關於VC++ >> 改變窗口中的光標形狀

改變窗口中的光標形狀

編輯:關於VC++

如何在注冊表中查找默認浏覽器信息?

如何改變窗口中的光標形狀?

如何避免資源ID沖突?

如何在注冊表中查找默認浏覽器位置的定義?我需要知道EXE文件的路徑和名稱以便啟動一個應用程序會話。我的目的很簡單,就是打開默認的浏覽器,這樣用戶能夠象普通程序一樣使用它,而不是在我設計的程序窗口內浏覽因特網。

Rolf Wenger

據我所知,在Window中沒有專門指定默認浏覽器的注冊表鍵值或設定值。即使專家也很難弄清楚整個注冊表,更何況常人。我知道可能存在這樣一個鍵值,

HKCU\System\Mumble\Bletch\Blah\Gak\DefaultBrowser

如果你知道這樣的鍵值,請寫信告訴我。不過,我知道一個簡單的解決辦法,那就是查找哪個程序和HTML文件相關聯。在Window操作系統中HTML文件的後綴通常為.htm和.html,所以你要做的就是查找HKCR/.htm的鍵值。如果你查找了會找到下面的鍵值:

HKEY_CLASSES_ROOT\.htm="htmlfile"

再根據這個鍵值查找HKCR/htmlfilm的條目,你會找到下面的鍵值:

[HKEY_CLASSES_ROOT\htmlfile\shell\open\command]
@="\"C:\\PROGRA~1\\INTERN~1\\iexplore.exe\" -nohome"

這個鍵值表明Microsoft Internet Explorer (iexplore.exe)是用來打開.htm文件的程序。(-nohome開關標志告訴IE浏覽器不要打開主頁)如果默認的浏覽器是Netscape,這個條目會是這樣:

[HKEY_CLASSES_ROOT\htmlfile\shell\open\command]
@="\"C:\\PROGRA~1\\NETSCAPE\\netscape.exe\".

我的回答滿意嗎?

我想為對話框的一個按鈕設置不同的光標,應該如何進行設置?

Rolf Wenger

有兩種方式可以改變窗口中的光標:你可以在注冊窗口類時聲明一個全局光標(HCURSOR)作為WNDCLASS結構的一部分,或者通過處理WM_SETCURSOR消息來手工設置光標。標准的MFC程序采用第一種方式設置光標,它自動為主窗口注冊一個箭頭光標。你可以在主窗口或子窗口中通過處理WM_SETCURSOR消息來改寫這個行為。

// 在按鈕類中處理WM_SETCURSOR消息
BOOL CMyButton::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT msg)
{
  ::SetCursor(m_hMyCursor);
  return TRUE;
   }

不管什麼時候,當用戶將鼠標移動到按鈕上並且鼠標沒有被捕獲時,Windows 會發送一條WM_SETCURSOR消息給按鈕。它傳遞一個窗口句柄——即鼠標指針指向的窗口, 此時就是按鈕本身;擊中測試碼——即在WM_NCHITTEST消息中使用的HTXXX碼(見 Figure 1);和一個觸發事件的消息ID,比如說它觸發了WM_MOUSEMOVE事件。設置鼠標 光標的最佳機會就是在處理WM_SETCURSOR消息的時候。如果要這麼做,你必須返回TRUE以阻止窗口默認的處理過程。

此時處理會如何進行呢?首先窗口默認的處理過程向父窗口(如果有的話)發送WM_SETCURSOR消息到父窗口。如果父窗口處理了WM_SETCURSOR消息(就是說它返回了TURE),Windows就不做什麼了, 該消息就算處理完了。如果父窗口沒有處理WM_SETCURSOR消息(返回FALSE),Windows就給子窗口一個處理這條消息的機會。假如子窗口也沒有處理該消息(返回FALSE),Windows就使用全局光標,要是連全局光標也沒有,則使用箭頭光標。

這些意味著什麼?這意味著在需要動態設置光標時,你要決定是在子窗口還是在父窗口處理WM_SETCURSOR消息。兩個選擇都可行,這取決於實際情況。一般來說,最好讓對象決定自己的屬性,這就是說最好在子窗口處理消息。本例中子窗口是指按鈕。但這需要從CButton類繼承一個新的按鈕類,讓它有自己的消息映射和其 它一些必要的屬性,如果你是樂於使用 Class Wizard 的人(有沒有人用它啊?),這意味著需要多敲幾下鍵盤或多點幾次鼠標。如果你已經具備了自己的按鈕類,那我明確的告訴你在這個子類中處理WM_SETCURSOR消息。要是你沒有自己的按鈕類而且你 又是個懶人,那就在對話框裡處理WM_SETCURSOR消息也行。不過千萬不要告訴別的面向對象專家,是我要你這麼干的!

Figure 2 按鈕上設置的光標

我寫了一個簡單的基於對話框的應用程序,NoCursor,來舉例說明這兩種方法。如果你把鼠標移動到一個按鈕上(OK或Cancel),光標變成藍色的指示手指(見 Figure 2);該功能通過處理對話框類的OnSetCursor函數實現(見 Figure 3)。另外,當你把鼠標移動到帶下劃線的超級鏈接上時,光標變成另一種不同的指示手指。該功能是在子類CStaticLink裡實現的(見 Figure 4)。CStaticLink是在我的專欄裡經常出現的一個多用途超鏈接類(見 Figure 5)。CStaticLink::OnSetCursor函數中的大部分代碼是處理如何從winhlp32.exe獲得適合的手形光標資源,就光標設置而言這些代碼無關 緊要,故省略。如果你對這些細節很感興趣,可以象往常一樣從本文頂部的鏈接下載全部代碼。

Figure 4 超級鏈接上設置的光標

我獲得一個含有對話框的庫(當然也就包括一些資源ID)。當我在主程序中使用這個庫時,庫中的資源ID和主程序的資源ID發生了沖突。結果是,要顯示庫中的對話框時卻彈出了主程序中的對話框。要怎樣做才能避免沖突?難道要手工對庫中的資源ID進行設置嗎?

Hans Zwahlen

唉,這個問題沒有特別令人滿意的答案,只有一些曲線救國的辦法。該問題的實質在於Windows中的每個資源必須從屬於某個模塊(EXE或DLL),而在每一個模塊中,特定資源不能有相同的名稱或ID。就DLL(動態鏈接庫)而言不存在這樣的問題,因為DLL本身就有和主程序不同的HINSTANCE句柄。但對靜態鏈接庫來說,所有資源在同一個EXE文件內共存,就像一個大家庭的成員共同生活在一起一樣。當然,像家庭成員可能發生沖突一樣,資源 也存在發生沖突的可能性。

MFC是如何解決這個問題的呢?它采用800磅重的大猩猩的方法,“我是800磅重的大猩猩,身強體壯,你們打不贏我,最好乖乖聽我的。”據我所知MFC沒有任何內置的對話框,但有一些通用的菜單項ID,像ID_FILE_OPEN。這些ID也被用作菜單項提示字符串的資源ID。所有MFC的ID值都比0xE000大。最好不要使用大於該值的ID,否則產生沖突就是你的問題。

這種方法對編寫操作系統的人來說可能是適合的,但如果你只是一個努力賺錢養家糊口的人,當顧客抱怨你的ID和他的程序有沖突的時候,最好不要“嘴硬”, 你必須要想方設法解決這個問題,要不然怎麼辦?

我曾經用過的一個辦法是:把庫中所有資源ID規定為一個已初始化基本值的偏移量,在發生沖突時程序員能夠改變這個基本值。可以在一個特殊的頭文件中規定這個值,然後程序員必須同時在主程序文件和資源文件中包含它。

// 在主應用程序rc文件
#include "libres.h" // ID 標識符
#include "libres.rc" // 資源

這裡libres.rc是包含庫中所有對話框、光標和其他資源的資源文件。頭文件libres.h以基本值偏移量的方式規定了libres.res中使用的資源ID。

// 在libres.h文件中
#ifndef LIBBASEID
#define LIBBASEID 2000 // 或其他值
#endif
#define IDR_FOO (LIBBASEID+1)
#define IDR_BAR (LIBBASEID+2)
// 等等

只要改變LIBBASEID的值,使用這個庫的程序員可以方便地重新映射所有資源ID,例如:

// 在主程序rc文件
#define LIBBASEID 3000
#include "libres.h"
#include "libres.rc"

現在所有的ID從3000開始而不是2000,在實際當中這個辦法很有效,但它有一個麻煩的問題,那就是它需要用戶重新編譯庫以改變ID。如果重新編譯庫是不可行的(也許你不想提供源代碼),還有一些其 它的辦法。要是程序員改變了LIBBASEID,只有當他直接使用IDR_FOO或其他資源ID時,才需要重新編譯庫。也就是說,在庫中直接引用了這些資源ID:

BOOL SomeLibFn(...)
{
  DialogBox(..., IDR_FOO, ...);
}

可以不直接引用這些資源ID,方式之一就是把它們作為調用函數的參數傳遞。

BOOL SomeLibFn(..., UINT nDlgID)
{
  DialogBox(..., nDlgID, ...);
}

現在你不用擔心庫中的資源ID會發生沖突,因為主程序一定支持它們。改變LIBBASEID值的用戶不用也不能重新編譯庫,因為所有ID是作為函數值傳入的。當然這個方法要求編寫主程序的程序員必須編輯庫中的rc文件以設置ID,還是有點麻煩。更好的辦法是以全局靜態變量而不是固定的ID值作為偏移量的基本值。

// 在頭文件中
extern UINT LibBaseId;
#define IDR_FOO (LibBaseId+1);
#define IDR_BAR (LibBaseId+2);
// 等等
// 在庫中主模塊
UINT LibBaseId = 2000;

假如現在發生沖突,應用程序可以在啟動時改變LibBaseId值(也許是通過一個函數),然後重新編譯主程序即可。唉,但這對RC文件不起作用。我曾經說過RC文件的語法並不是純C/C++語法,而只是其一個子集,所以RC文件不知道extern和UINT是什麼。要使資源編譯器理解這些符號,你需要使用另外的頭文件,或者采用更簡便的方法,使用宏RC_INVOKED,這樣這些符號就可以放在同一個頭文件當中。

// 在libres.h頭文件中
#ifdef RC_INVOKED
#ifndef LibBaseId
#define LibBaseId 2000
#endif
#else
extern UINT LibBaseId;
#endif
#define IDR_FOO (LibBaseId+1);
#define IDR_BAR (LibBaseId+2);
// 等等

一般來說,包含在RC文件中的頭文件不能包含除#define以外的C代碼。采用這種方法,使用你的庫的程序員必須做兩件事來重新映射庫中的資源ID。第一步,在把libres.h包含到主RC文件之前,他們必須重新#define LibBaseId一個新值。第二步,他們必須在主應用程序的啟動代碼中(比如在CWinApp::InitInstance函數中)將LibBaseId初始化為同樣的值。最後,有另外一種更簡單的解決資源ID沖突的辦法:使用字符串而不是數值來映射資源ID。

// 在libres.h頭文件中
#define IDR_FOO "MyApp_IDR_FOO"
#define IDR_BAR "MyApp_IDR_BAR"
 
//等等

使用字符串映射資源ID的效率並不高,因為不僅它們占用空間多,而且查找字符串映射的資源比查找整數映射的資源慢。如果你庫中有幾百個用於字符串或其他使用頻率高的資源ID,我建議你使用整數映射ID並且采用前面提出的一種解決方法。但假如你庫中的資源就是幾個對話框,那使用字符串映射資源ID是再好不過了。沒有人會注意多用了5毫秒顯示對話框,並且資源ID沖突的可能性幾乎為零。

作者簡介

Paul DiLascia,《Windows++: Writing Reusable Windows Code in C++》(Addison-Wesley出版公司1992出版)的作者,是一名自由作家和顧問。你可以發郵件到[email protected]或登錄http://www.dilascia.com/和他取得聯系。

本文配套源碼

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved