實現一個熱鍵注冊編輯的類

實現一個熱鍵注冊編輯的類

CST(http://blog.csdn.net/mrtechno) 2005-8-19


1 文檔目的... 1
2 熱鍵編程基礎... 1
2.1 API函數... 1
2.2 編程方法... 2
3 實現概述... 4
4 實現細節... 4
4.1 XML配置文檔結構... 4
4.2 熱鍵編輯控件 ThotKeyEdit 5
4.3 熱鍵自定義窗體類TformHotKeyconfig. 6
4.4 主類ThotKeyConfig. 6
4.4.1 解析XML文檔... 7
4.4.2 注冊注銷系統熱鍵... 8
4.4.3 熱鍵編輯窗體... 8
4.4.4 對象唯一性... 8
5 程序源代碼... 9
6 小結... 25
6.1 沒有解決的一些問題... 25
6.2 程序心得... 26

1 文檔目的本文檔介紹了在Delphi 7中注冊、注銷、使用熱鍵(Hot Key)的基本函數和方法,並在此基礎上介紹了一個熱鍵控制的類THotKeyConfig。該類可以從指定的xml配置文檔中讀入熱鍵配置信息,並在程序指定的位置注冊、注銷、修改熱鍵。

本文將不涉及XML文檔的讀寫方法,也不會詳述控件開發的方法。如果您對這些內容不了解,推薦您先略讀一下相關的文章。關于xml文檔的解析,可以到我的blog找到文章。

2 熱鍵編程基礎2.1 API函數在Delphi7中使用熱鍵要用到如下幾個函數:

//注冊熱鍵

BOOL RegisterHotKey(

HWND hWnd, // 接受熱鍵消息的窗口句柄

int id, // 熱鍵ID

UINT fsModifiers, // 按鍵組合整數

UINT vk // 案件虛擬鍵碼VK

);

其中fsModifiers 值如下:

1: M_strDisplay:='Alt + ';

2: M_strDisplay:='Ctrl + ';

3: M_strDisplay:='Ctrl + Alt + ';

4: M_strDisplay:='Shift + ';

5: M_strDisplay:='Shift + Alt + ';

6: M_strDisplay:='Ctrl + Shift + ';

7: M_strDisplay:='Ctrl + Shift + Alt + ';

//注銷熱鍵

BOOL UnregisterHotKey(

HWND hWnd, //接受熱鍵消息的窗口句柄

int id //熱鍵ID

);

注銷時,根據注冊時賦予的熱鍵ID進行注銷,因此在注冊時必須保證ID唯一。

關于這兩個WIN32 API函數的具體說明可以參考WIN32 SDK文檔。

2.2 編程方法基本的熱鍵編程方法是定義2個窗體過程,和一個消息響應函數:

Procedure HotKeyOn;

Procedure HotKeyOff;

procedure HotKeyDown(var Msg: Tmessage); message WM_HOTKEY;

在HotKeyOn過程中調用API函數注冊熱鍵,代碼可以是這樣的:

procedure TfrmMain.HotKeyOn;

begin

HKStep := 1;

HKScreen := 2;

HKComponent := 3;

HKShowMain := 4;

HKShowOI := 5;

if not RegisterHotKey(Handle, HKStep, MOD_CONTROL, Ord('C')) then

showmessage('can not register the hotkey "Ctrl-C"');

if not RegisterHotKey(Handle, HKScreen, MOD_CONTROL, Ord('V')) then

showmessage('can not register the hotkey "Ctrl-V"');

if not RegisterHotKey(Handle, HKComponent, MOD_CONTROL, Ord('B')) then

showmessage('can not register the hotkey "Ctrl-B"');

if not RegisterHotKey(Handle, HKShowMain, MOD_CONTROL, VK_F11) then

showmessage('can not register the hotkey "Ctrl-F11"');

if not RegisterHotKey(Handle, HKShowOI, MOD_CONTROL, VK_F12) then

showmessage('can not register the hotkey "Ctrl-F12"');

end;

在HotKeyOff過程中調用API函數注銷熱鍵,代碼可以是這樣的:

procedure TfrmMain.HotKeyOff;

begin

UnRegisterHotKey(handle, HKStep);

UnRegisterHotKey(handle, HKScreen);

UnRegisterHotKey(handle, HKComponent);

UnRegisterHotKey(handle, HKShowMain);

UnRegisterHotKey(handle, HKShowOI);

end;

在HotKeyDown消息處理函數中判斷系統消息,根據不同的熱鍵組合執行響應的語句,代碼可以是這樣的:

procedure TfrmMain.HotKeyDown(var Msg: Tmessage);

begin

if (Msg.LParamHi = Ord('C')) and (Msg.LParamLo = MOD_CONTROL) then

ShowMessage('"Ctrl-C"')

else if (Msg.LParamHi = Ord('V')) and (Msg.LParamLo = MOD_CONTROL) then

ShowMessage('"Ctrl-V"')

else if (Msg.LParamHi = Ord('B')) and (Msg.LParamLo = MOD_CONTROL) then

ShowMessage('"Ctrl-B"')

else if (Msg.LParamHi = VK_F11) and (Msg.LParamLo = MOD_CONTROL) then

ShowMessage('"Ctrl-11"')

else if (Msg.LParamHi = VK_F12) and (Msg.LParamLo = MOD_CONTROL) then

ShowMessage('"Ctrl-12"')

end;

如果系統熱鍵數量少、穩定不變,則適合使用這種方法。如果系統熱鍵較多,而軟件需求又要求熱鍵可以由用戶設置修改,則需要有一個自動化管理的模塊來實現。因此我在學習了如上的方法後實現了一個熱鍵自動管理的類。

3 實現概述實現這個熱鍵管理類我定義了1個記錄體和3個類。可以將熱鍵配置信息保存在一個獨立的xml文檔中,也作爲子樹加入到應用程序的配置文檔中。

記錄體ThotKeyStatus保存從XML配置樹中讀入的熱鍵記錄,該記錄體的數組變量將被整個單元文件內的對象所共享。

類ThotKeyEdit是一個自定義的控件,用于接受用戶輸入的熱鍵組合,一方面轉化爲系統可以接受的形式,另一方面也給用戶一個即時反饋。

類TformHotkeyConfig是一個窗體類,該窗體類將根據從XML中讀入的熱鍵配置動態創建ThotKeyEdit控件,提供用戶查看和修改熱鍵。

類ThotKeyConfig是我們要使用和直接訪問的類,它提供一個後台操作的功能,用戶在引入該類後可以選擇在程序指定位置讀入XML配置文件、生效熱鍵配置、打開TformHotKeyConfig提供的編輯窗體、保存配置到XML文件。另外該類在編程上爲了控制對象的唯一性,采用了類方法MgetInstance來獲得唯一對象,關于用類方法控制對象唯一性的方法可以參考我blog中的文章。

4 實現細節4.1 XML配置文檔結構XML配置文檔的結構可以如下:

<?xml version="1.0" encoding="UTF-8"?>

<configure>

<hotkeys>

<hotkey>

<caption>添加SCREEN</caption>

<hkid>101</hkid>

<mod>2</mod>

<vk>49</vk>

</hotkey>

<hotkey>

<caption>新建STEP</caption>

<hkid>102</hkid>

<mod>2</mod>

<vk>50</vk>

</hotkey>

</hotkeys>

</configure>

其中,<hotkeys>爲保存熱鍵配置的節點的子樹樹根。每個<hotkey>子樹記錄一個熱鍵配置。

caption爲熱鍵名稱,將顯示在TformHotKeyConfig實例中的ThotKeyEdit對象的標題位置。

Hkid爲熱鍵唯一標識,對應上文API函數中的ID值,該值必須局部唯一。

Mod爲熱鍵模式,對應上文API函數中的fsModifier值。

Vk爲熱鍵虛擬鍵碼,對應上文API函數中的VK值。

4.2 熱鍵編輯控件 ThotKeyEdit該控件的聲明如下:

//---------------------------------------

// 熱鍵編輯控件

//---------------------------------------

THotKeyEdit = class(TLabeledEdit)

private

//當前控件接收到的熱鍵組合是否合法

FKeySetValid:Boolean;

//組合鍵

FModValue:Integer;

//虛擬鍵碼

FVirtualKeyValue:Integer;

//修改合法後顯示的顔色

FValidateColor:TColor;

//用來覆蓋OnExit事件的函數

procedure LostFocusEvent(Sender:TObject);

//用來覆蓋OnKeyDown事件的函數

procedure GetHotKeyDownEvent(Sender: TObject; var Key: Word; Shift: TShiftState);

//將熱鍵數據轉換爲直觀文字

function GetDisplayText:string;

//熱鍵組合合法執行的代碼

procedure ActionOnHotKeyValid;

//熱鍵組合非法執行的代碼

procedure ActionOnHotKeyInvalid;

public

//覆蓋構造函數

constructor Create(AOwner: TComponent); override;

//外部請求將內部數據表現爲直觀文字

procedure DisplayHotKey;

published

property HasValidKeySet:boolean read FKeySetValid;

property VirtualKeyValue:integer read FVirtualKeyValue write FVirtualKeyValue;

property KeyModValue:integer read FModValue write FModValue;

end;

ThotKeyEdit控件繼承自TlabeledEdit控件,包含一個顯示熱鍵名稱的LABEL和一個只讀的EDIT區,在該區將顯示用戶輸入的熱鍵組合。控件的OnKeyDown事件被GetHotKeyDownEvent過程重寫,在該過程中捕捉用戶按下的按鍵組合,先將捕捉到的鍵位信息保存到私有字段中,然後調用GetDisplayText函數判斷私有字段中的鍵位是否合法,並且返回由這些信息轉換得到的熱鍵字串。合法性將保存在一個私有布爾字段FKeySetValid中。

對于用戶提供的熱鍵布局,如果可以接受則控件edit區會變色,如果不能接受則會在失去焦點時清除內容,並恢複默認顔色。

4.3 熱鍵自定義窗體類TformHotKeyconfig該窗體類繼承自Tform類,並需要UnitHotkeyConfigClass.dfm資源文件的支持。

類的聲明代碼如下:

//---------------------------------------

// 熱鍵設定窗體

//---------------------------------------

TFormHotkeyConfig = class(TForm)

GroupBoxLeft: TGroupBox;

PanelRight: TPanel;

ButtonYes: TButton;

ButtonNo: TButton;

procedure ButtonYesClick(Sender: TObject);

procedure ButtonNoClick(Sender: TObject);

private

//用來保存動態創建的THotKeyEdit控件的對象列表

FEditList:TObjectList;

public

constructor Create(AOwner: TComponent); override;

end;

該類重寫了構造方法,並在構造方法中根據從xml中讀入的熱鍵個數動態創建和初始化響應數量的ThotKeyEdit控件,因此需要一個私有成員FeditList來維護控件數組。

4.4 主類ThotKeyConfigThotKeyConfig的聲明如下:

//---------------------------------------

// 主類:提供熱鍵注冊和編輯功能

//---------------------------------------

THotkeyConfig = class (TComponent)

private

//如果用戶自定義配置文件路徑則記錄它

FAssociatedXML : String;

//配置窗體對象

FConfigureForm : TFormHotkeyConfig;

//熱鍵響應窗體引用

FWindow : TWinControl;

//定位到保存熱鍵記錄的XML子樹樹根

function XMLGetKeysetFather(AXML:TXMLDocument):IXMLNode;

//隱藏的構造函數

constructor Create(AOwner: TComponent);override;

public

//獲得對象

class function MGetInstance(AOwner:TWinControl):THotKeyConfig;

//讀入XML配置文件

function LoadConfigFromXML(const AXMLFile:string='hotkey.xml'):boolean;

//保存配置

function SaveConfigToXML(const AXMLFile:string='hotkey.xml'):boolean;

//注冊所有熱鍵設置

function EnableAllHotkeys:Boolean;

//注銷熱鍵

procedure DisableAllHotkeys;

//打開配置窗口

procedure OpenConfigWindow;

published

property WindowHandlesHotkey : TWinControl write FWindow;

end;

在該類中主要完成如下幾個功能:

1. 讀寫解析XML配置文件

2. 注冊注銷系統熱鍵

3. 提供熱鍵修改窗體的入口

4. 通過類方法和類變量,管理類的對象在應用程序中的唯一性

4.4.1 解析XML文檔因爲XML文檔結構相對簡單因此使用TXMLDocument類來實現,在LoadConfigFromXML方法中通過XMLGetKeysetFather函數定位到Hotkey節點,這樣如果XML結構位置改變,只需要修改XMLGetKeysetFather函數就可以。讀出的熱鍵記錄將保存到靜態變量FkeyInfo和FkeyInfoCount中,他們是ThotKeyStatus的數組和計數器。

4.4.2 注冊注銷系統熱鍵兩個對象方法負責自動完成熱鍵的注冊和注銷:EnableAllHotkeys和DisableAllHotkeys。

熱鍵注冊時從FkeyInfo數組中讀入鍵位信息並調用WIN32 API函數RegisterHotKey注冊熱鍵,如果注冊成功則返回true。

在RegisterHotKey中需要一個窗體句柄來接受系統消息WM_HOTKEY,因此在調用EnableAllHotkeys之前需要爲屬性WindowHandlesHotkey賦一個窗體的引用值。或者在MgetInstance方法的參數中傳遞該窗體的引用。如果沒有定義WindowHandlesHotkey會使熱鍵無法注冊。

4.4.3 熱鍵編輯窗體執行OpenConfigWindow方法將彈出模式窗體,提供用戶編輯熱鍵,該窗體就是TformHotKeyConfig的實例。

在窗體打開之前,爲了不在編輯時觸發熱鍵消息,需要調用DisableAllHotkeys取消所有熱鍵。

在窗體別關閉後檢查靜態變量isXMLNeedSave判斷用戶按下的是確認還是取消。如果是確認,則要保存熱鍵配置到靜態變量FkeyInfo和XML文檔。最後根據FkeyInfo重新注冊熱鍵。

4.4.4 對象唯一性因爲一個應用程序中,對于ThotkeyConfig對象通常只需要一個就夠了,如果在每個需要用到的地方都重新創建會影響程序執行效率。所以,我使用一個靜態變量保存唯一對象的引用,然後公開一個方法MgetInstance讓程序員得到ThotKeyConfig的實例對象。具體概念請訪問我的blog。

因此類的編程模式如下:

with THotKeyConfig.MGetInstance(Form1) do begin

LoadConfigFromXML;

EnableAllHotkeys ;

//……

end;

5 程序源代碼爲了便于使用,我將3個類定義和1個記錄體聲明寫在一個單獨的UNIT中,這樣會帶來一些訪問上的安全隱患,但是作爲學習只用,程序員在調用時“自覺”一點就可以了 :-P

//--------------------------------------------------------------------------

//UNIT: UnitHotkeyConfigClass.pas

//SUMM: 熱鍵控制類

//AUTH: CST

//DATE: 2005-8-15

//DESC: 本單元文件定義了一個保存熱鍵項目的記錄體、後台控制類、一個設定窗口類

// 以及一個需要用到的自定義控件THotKeyEdit。

//REFE: HotKeyConfig類使用到了自定義控件HotKeyEdit

//BUGS: No checking for duplicated hotkey sets

// No checking for duplicated hotkey_id in xml

//

//USES: 用戶只需使用THotKeyConfig類,該類不能創建實例。

// 請使用THotKeyConfig.MGetInstance(Owner)方法來訪問對象。

//--------------------------------------------------------------------------

unit UnitHotkeyConfigClass;

interface

uses

Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,

Dialogs, StdCtrls, ExtCtrls, Buttons, StrUtils, Contnrs, xmldom, XMLIntf,

msxmldom, XMLDoc;

type

//---------------------------------------

// 熱鍵組合

//---------------------------------------

THotKeyStatus = record

FCaption:String[20]; //熱鍵標題

FHKID :Integer; //唯一ID

FMod :Integer; //組合鍵

FVK :Integer; //虛擬鍵碼

end;

//---------------------------------------

// 熱鍵編輯控件

//---------------------------------------

THotKeyEdit = class(TLabeledEdit)

private

//當前控件接收到的熱鍵組合是否合法

FKeySetValid:Boolean;

//組合鍵

FModValue:Integer;

//虛擬鍵碼

FVirtualKeyValue:Integer;

//修改合法後顯示的顔色

FValidateColor:TColor;

//用來覆蓋OnExit事件的函數

procedure LostFocusEvent(Sender:TObject);

//用來覆蓋OnKeyDown事件的函數

procedure GetHotKeyDownEvent(Sender: TObject; var Key: Word; Shift: TShiftState);

//將熱鍵數據轉換爲直觀文字

function GetDisplayText:string;

//熱鍵組合合法執行的代碼

procedure ActionOnHotKeyValid;

//熱鍵組合非法執行的代碼

procedure ActionOnHotKeyInvalid;

public

//覆蓋構造函數

constructor Create(AOwner: TComponent); override;

//外部請求將內部數據表現爲直觀文字

procedure DisplayHotKey;

published

property HasValidKeySet:boolean read FKeySetValid;

property VirtualKeyValue:integer read FVirtualKeyValue write FVirtualKeyValue;

property KeyModValue:integer read FModValue write FModValue;

end;

//---------------------------------------

// 熱鍵設定窗體

//---------------------------------------

TFormHotkeyConfig = class(TForm)

GroupBoxLeft: TGroupBox;

PanelRight: TPanel;

ButtonYes: TButton;

ButtonNo: TButton;

procedure ButtonYesClick(Sender: TObject);

procedure ButtonNoClick(Sender: TObject);

private

//用來保存動態創建的THotKeyEdit控件的對象列表

FEditList:TObjectList;

public

constructor Create(AOwner: TComponent); override;

end;

//---------------------------------------

// 主類:提供熱鍵注冊和編輯功能

//---------------------------------------

THotkeyConfig = class (TComponent)

private

//如果用戶自定義配置文件路徑則記錄它

FAssociatedXML : String;

//配置窗體對象

FConfigureForm : TFormHotkeyConfig;

//熱鍵響應窗體引用

FWindow : TWinControl;

//定位到保存熱鍵記錄的XML子樹樹根

function XMLGetKeysetFather(AXML:TXMLDocument):IXMLNode;

//隱藏的構造函數

constructor Create(AOwner: TComponent);override;

public

//獲得對象

class function MGetInstance(AOwner:TWinControl):THotKeyConfig;

//讀入XML配置文件

function LoadConfigFromXML(const AXMLFile:string='hotkey.xml'):boolean;

//保存配置

function SaveConfigToXML(const AXMLFile:string='hotkey.xml'):boolean;

//注冊所有熱鍵設置

function EnableAllHotkeys:Boolean;

//注銷熱鍵

procedure DisableAllHotkeys;

//打開配置窗口

procedure OpenConfigWindow;

published

property WindowHandlesHotkey : TWinControl write FWindow;

end;

procedure Register;

implementation

{$R *.dfm}

//-----------------------------------------------------------------------

// 單元內全局變量

//-----------------------------------------------------------------------

var

//共享從XML中讀入的熱鍵配置信息

FKeyInfo : array of THotKeyStatus;

//讀入的熱鍵記錄個數

FKeyInfoCount: Integer;

//是否需要保存到XML中

isXMLNeedSave:Boolean;

//實體

HK_Instance:THotkeyConfig;

//-----------------------------------------------------------------------

// 自定義控件可以被注冊

//-----------------------------------------------------------------------

procedure Register;

begin

RegisterComponents('CST', [THotKeyEdit]);

end;

{********************************************************

*********************************************************

******************* THotkeyConfig *********************

*********************************************************

********************************************************}

//-----------------------------------------------------------------------

// 構造函數

//-----------------------------------------------------------------------

constructor THotKeyConfig.Create (AOwner: TComponent);

begin

inherited;

end;

//-----------------------------------------------------------------------

//NAME: MGetInstance

//SUMM: 獲得類的唯一實例

//PARA: AOwner

//RETN: 唯一實例

//AUTH: CST

//DATE: 2005-8-18

//DESC: 類方法實現對象唯一性控制

//-----------------------------------------------------------------------

class function THotKeyConfig.MGetInstance(AOwner:TWinControl):THotKeyConfig;

begin

if HK_Instance = nil then begin

HK_Instance:=Create(AOwner);

HK_Instance.WindowHandlesHotkey := AOwner;

end;

Result:=HK_Instance;

end;

//-----------------------------------------------------------------------

//NAME: EnableAllHotkeys

//SUMM: 注冊所有熱鍵

//PARA: N/A

//RETN: TRUE-成功

//AUTH: CST

//DATE: 2005-8-15

//DESC: 在應用程序加載時由用戶顯式調用。熱鍵配置修改後被類自動調用重新注冊

// 先判斷熱鍵Listener窗體是否賦值,然後調用WIN32 API注冊

//-----------------------------------------------------------------------

function THotkeyConfig.EnableAllHotkeys:Boolean;

var

M_Index:integer;

M_ErrText:string;

begin

Result:=True;

//CHECK HOTKEY HANDLE WINDOW DEFINED

if FWindow=nil then begin

ShowMessage('熱鍵處理窗體未定義。'+#13+'請使用WindowHandlesHotkey方法。');

result:=false;

exit;

end;

//REGISTER BY LOOP

for M_Index := 0 to FKeyInfoCount - 1 do begin

//SKIP UNDEFINED HOTKEY EVENTS

if FKeyInfo[M_Index].FMod < 1 then continue;

if FKeyInfo[M_Index].FVK < 1 then continue;

//START TO REGISTER HOTKEY

if not RegisterHotKey( FWindow.Handle,

FKeyInfo[M_Index].FHKID,

FKeyInfo[M_Index].FMod ,

FKeyInfo[M_Index].FVK ) then begin

//REGISTER FAILURE PROCEDURE

Result:=False;

M_ErrText:=Format('無法注冊名爲%s的熱鍵。',[FKeyInfo[M_Index].FCaption]);

//ShowMessage(M_ErrText);

end; //end of if

end; //end of for

end;

//-----------------------------------------------------------------------

//NAME: DisableAllHotkeys

//SUMM: 注銷所有熱鍵

//PARA: N/A

//RETN: TRUE-成功

//AUTH: CST

//DATE: 2005-8-15

//DESC: 在進入熱鍵編輯之前要調用此方法注銷熱鍵。

//-----------------------------------------------------------------------

procedure THotkeyConfig.DisableAllHotkeys;

var

M_Index:integer;

begin

for M_Index := 0 to FKeyInfoCount - 1 do

UnRegisterHotKey( FWindow.Handle, FKeyInfo[M_Index].FHKID);

end;

//-----------------------------------------------------------------------

//NAME: XMLGetKeysetFather

//SUMM: 定位到保存熱鍵記錄的XML子樹樹根

//PARA: AXML-XML文檔

//RETN: 樹根節點

//AUTH: CST

//DATE: 2005-8-18

//DESC: 定位到保存熱鍵記錄的XML子樹樹根。

// 如果要改變XML結構,則只要修改這裏的定位語句。

//-----------------------------------------------------------------------

function THotkeyConfig.XMLGetKeysetFather(AXML:TXMLDocument):IXMLNode;

var

M_SearchNode:IXMLNode;

begin

//NAVIGATE THROUGH XML CONFIGURE FILE

M_SearchNode:=AXML.Node;

M_SearchNode:=M_SearchNode.ChildNodes.Nodes['configure'];

M_SearchNode:=M_SearchNode.ChildNodes.Nodes['hotkeys'];

Result:= M_SearchNode;

end;

//-----------------------------------------------------------------------

//NAME: LoadConfigFromXML

//SUMM: 從XML文檔中讀取熱鍵設置,並注冊生效

//PARA: AXMLFile-XML文檔路徑,默認爲EXE同根的hotkey.xml

//RETN: TRUE-成功

//AUTH: CST

//DATE: 2005-8-15

//DESC: 使用TXMLDocuement對象解析配置文檔,將讀取的記錄保存到類變量中

// FKeyInfo數組記錄讀入的熱鍵組合,FKeyInfoCount記錄動態數組大小

//-----------------------------------------------------------------------

function THotkeyConfig.LoadConfigFromXML(const AXMLFile:string='hotkey.xml'):boolean;

var

M_ConfigXML:TXMLDocument;

M_SearchNode, M_PropNode:IXMLNODE;

M_Index :integer;

begin

result:=False;

//Q:爲何構造方法參數爲nil就會無法解析節點?

M_ConfigXML:=TXMLDocument.Create(Self);

try

//OPEN XML CONFIGURE FILE

with M_ConfigXML do begin

LoadFromFile(AXMLFile);

Options := [];

Active := True;

end;

//RECORD ASSOCIATED XML CONFIGURATION FILE

FAssociatedXML := AXMLFile;

//GET THE ROOT WE WANT

M_SearchNode:=XMLGetKeysetFather(M_ConfigXML);

//GET COUNT FOR HOTKEY SETS

FKeyInfoCount:=M_SearchNode.ChildNodes.Count ;

SetLength(FKeyInfo, FKeyInfoCount);

//LOOP TO READ EVERY KEYSET

for M_Index := 0 to FKeyInfoCount - 1 do begin

M_PropNode:=M_SearchNode.ChildNodes.Nodes[M_Index];

with FKeyInfo[M_Index] do begin

FCaption := M_PropNode.ChildValues['caption'];

FHKID := M_PropNode.ChildValues['hkid'];

FMod := M_PropNode.ChildValues['mod'];

FVK := M_PropNode.ChildValues['vk'];

end; //end of with

end; //end of for

finally

M_ConfigXML.Active := False;

FreeAndNil(M_ConfigXML);

end;

end;

//-----------------------------------------------------------------------

//NAME: SaveConfigToXML

//SUMM: 保存修改的熱鍵配置到XML文檔

//PARA: AXMLFile-XML文檔路徑

//RETN: TRUE-成功

//AUTH: CST

//DATE: 2005-8-15

//DESC: 配置窗口確認關閉後調用

//-----------------------------------------------------------------------

function THotkeyConfig.SaveConfigToXML(const AXMLFile:string='hotkey.xml'):boolean;

var

M_ConfigXML:TXMLDocument;

M_SearchNode, M_PropNode:IXMLNODE;

M_Index :integer;

begin

result:=False;

M_ConfigXML:=TXMLDocument.Create(Self );

try

//OPEN XML CONFIGURE FILE

with M_ConfigXML do begin

LoadFromFile(AXMLFile);

Options := [];

Active := True;

end;

//GET THE ROOT WE WANT

M_SearchNode:=XMLGetKeysetFather(M_ConfigXML);

//LOOP TO READ EVERY KEYSET

for M_Index := 0 to FKeyInfoCount - 1 do begin

M_PropNode:=M_SearchNode.ChildNodes.Nodes[M_Index];

with FKeyInfo[M_Index] do begin

M_PropNode.ChildValues['caption']:=FCaption;

M_PropNode.ChildValues['hkid']:=FHKID;

M_PropNode.ChildValues['mod']:=FMod;

M_PropNode.ChildValues['vk']:=FVK;

end; //end of with

end; //end of for

//SAVE CHANGES

M_ConfigXML.SaveToFile(AXMLFile);

finally

M_ConfigXML.Active := False;

FreeAndNil(M_ConfigXML);

end;

end;

//-----------------------------------------------------------------------

//NAME: OpenConfigWindow

//SUMM: 打開配置窗口

//PARA: N/A

//RETN: N/A

//AUTH: CST

//DATE: 2005-8-15

//DESC: 窗體對象爲 FConfigureForm 成員

//-----------------------------------------------------------------------

procedure THotKeyConfig.OpenConfigWindow ;

var

M_ErrMsg:String;

begin

if FConfigureForm = nil then FConfigureForm:=TFormHotkeyConfig.Create(nil);

try

//默認是不要保存修改

isXMLNeedSave:=False;

//設置之前先注銷所有熱鍵

DisableAllHotkeys ;

//打開設置窗口

FConfigureForm.ShowModal;

if isXMLNeedSave then begin

//修改後按下『確認』生效並保存

if EnableAllHotkeys then

begin

//新設置熱鍵注冊成功

MessageBox(Application.Handle, '所有熱鍵都成功注冊。'+#13+'點擊確認保存所有熱鍵設置。', '提示', MB_OK + MB_ICONINFORMATION);

SaveConfigToXML(FAssociatedXML);

end //end of if

else

begin

//新設置熱鍵有沖突

M_ErrMsg:='您設置的熱鍵組合中有一項或多項沒有注冊成功。' + #13 +

'也許是和其他應用程序産生了沖突,您可以嘗試更換其他按鍵組合。' + #13 +

'請問是否仍然要保存這次的設置,如果保存請按“是”,我們將在下次軟件啓動的時候'+

'再次嘗試注冊您的熱鍵配置,您可以在這之前注銷或修改其他應用程序的沖突設置。';

if MessageBox(Application.Handle, PChar(M_ErrMsg), '提示',MB_YESNO+MB_ICONQUESTION)=IDYES then SaveConfigToXML(FAssociatedXML);

end;

end

else begin

//按下『取消』按鈕,但是還是要恢複原先的熱鍵

EnableAllHotkeys;

end;

finally

FreeAndNil(FConfigureForm);

end;

end;

{********************************************************

*********************************************************

**************** TFormHotkeyConfig ********************

*********************************************************

********************************************************}

//-----------------------------------------------------------------------

//NAME: Create

//SUMM: TFormHotkeyConfig的構造函數

//AUTH: CST

//DATE: 2005-8-15

//DESC: 繼承TForm的構造函數,動態創建THotKeyEdit控件。

// 將窗體上的熱鍵接受控件的OnKeyDown事件改寫。

//-----------------------------------------------------------------------

constructor TFormHotkeyConfig.Create(AOwner: TComponent);

var

M_Index, M_Top:integer;

HKEdit:THotkeyEdit;

const

MLEFT = 10;

MWIDTH = 200;

MHEIGHT = 21;

MMARGIN = 30;

begin

inherited;

//HOTKEYEDITORS

FEditList := TObjectList.Create ;

M_Top := 0;

for M_Index := 0 to FKeyInfoCount - 1 do begin

//計算控件位置,縱向排列

M_Top:= MMARGIN + M_Index * (MHEIGHT+MMARGIN);

//根據讀入的XML節點動態創建熱鍵編輯控件

HKEdit:=THotKeyEdit.Create(Self);

with HKEdit do begin

//定義樣式

Parent:=Self.GroupBoxLeft;

SetBounds(MLEFT,M_Top,MWIDTH,MHEIGHT);

LabelPosition := lpAbove ;

EditLabel.Caption := FKeyInfo[M_Index].FCaption;

EditLabel.Width := MWIDTH;

//定義初始數據

VirtualKeyValue := FKeyInfo[M_Index].FVK;

KeyModValue := FKeyInfo[M_Index].FMod;

//按照定義的數據顯示熱鍵組合

DisplayHotKey;

end; //end of with

//保存組件到對象列表

FEditList.Add(HKEdit);

end; //end of for

Height:= M_Top + MHEIGHT + MMARGIN;

end;

//------------------------------------------------------------

// 確定

//------------------------------------------------------------

procedure TFormHotkeyConfig.ButtonYesClick(Sender: TObject);

var

M_Index:integer;

begin

//CONVERT HK_EDITOR DATA TO HOTKEY INFO ARRAY

for M_Index := 0 to FKeyInfoCount - 1 do begin

FKeyInfo[M_Index].FMod := (FEditList.Items[M_Index] as THotKeyEdit).KeyModValue ;

FKeyInfo[M_Index].FVK := (FEditList.Items[M_Index] as THotKeyEdit).VirtualKeyValue;

end;

//END OF CONVERT

Close;

isXMLNeedSave:=True;

end;

//------------------------------------------------------------

// 取消

//------------------------------------------------------------

procedure TFormHotkeyConfig.ButtonNoClick(Sender: TObject);

begin

if MessageBox(Self.Handle,'是否要放棄修改並關閉窗口?','提示',MB_YESNO+mb_iconinformation) = IDYES then

begin

Close;

isXMLNeedSave:=False;

end;

end;

{********************************************************

*********************************************************

********************* THotKeyEdit *********************

*********************************************************

********************************************************}

//-----------------------------------------------------------------------

// HotKeyEdit控件構造函數

//-----------------------------------------------------------------------

constructor THotKeyEdit.Create(AOwner: TComponent);

begin

inherited;

ReadOnly := True;

OnKeyDown := GetHotKeyDownEvent;

OnExit := LostFocusEvent;

FValidateColor := clSkyBlue;

end;

//-----------------------------------------------------------------------

//NAME: GetDisplayText

//SUMM: 將熱鍵信息轉換爲顯示字串

//PARA: N/A

//RETN: 熱鍵轉換的顯示結果

//AUTH: CST

//DATE: 2005-8-15

//DESC: 型如:"Ctrl + Alt + Shift + A "爲正確

// 數據來源 FVirtualKeyValue, FModValue

// 判斷組合是否合法,記錄在FKeySetValid中

//-----------------------------------------------------------------------

function THotKeyEdit.GetDisplayText:string;

var

M_strDisplay:String;

const

SPLUS = ' + ';

begin

FKeySetValid := True;

//處理按鍵組合

case FModValue of

1: M_strDisplay:='Alt + ';

2: M_strDisplay:='Ctrl + ';

3: M_strDisplay:='Ctrl + Alt + ';

4: M_strDisplay:='Shift + ';

5: M_strDisplay:='Shift + Alt + ';

6: M_strDisplay:='Ctrl + Shift + ';

7: M_strDisplay:='Ctrl + Shift + Alt + ';

else

begin

M_strDisplay := '';

FKeySetValid := False;

end;

end;

//處理鍵碼

case FVirtualKeyValue of

VK_F1..VK_F12:

M_strDisplay := M_strDisplay + 'F'+IntToStr(FVirtualKeyValue - VK_F1 + 1);

Ord('A')..Ord('Z'), Ord('0')..Ord('9'):

M_strDisplay := M_strDisplay + Chr(FVirtualKeyValue);

else

begin

M_strDisplay := M_strDisplay ;

FKeySetValid := False;

end;

end;

result:=M_strDisplay;

end;

//-----------------------------------------------------------------------

//NAME: LostFocusEvent

//SUMM: 控件失去焦點時檢查熱鍵合法性

//PARA: Sender-控件

//RETN: N/A

//AUTH: CST

//DATE: 2005-8-15

//DESC: 此函數將用來覆蓋4個TLabelEdit的OnExit事件

//-----------------------------------------------------------------------

procedure THotKeyEdit.LostFocusEvent(Sender:TObject);

begin

if not FKeySetValid then begin

Text:='';

FModValue := 0;

FVirtualKeyValue := 0;

end;

end;

//-----------------------------------------------------------------------

//NAME: GetHotKeyDownEvent

//SUMM: 接受用戶輸入的熱鍵並判斷是否合法的時間函數

//PARA: Sender-控件 Key-虛擬鍵碼 Shift-輔助鍵信息

//RETN: N/A

//AUTH: CST

//DATE: 2005-8-15

//DESC: 此函數將用來覆蓋OnKeyDown事件

//

//-----------------------------------------------------------------------

procedure THotKeyEdit.GetHotKeyDownEvent(Sender: TObject; var Key: Word; Shift: TShiftState);

var

M_StrDisplay:String;

begin

//READ HOTKEY SET MODE

FModValue := 0;

if (ssCtrl in Shift) then FModValue := FModValue + 2;

if (ssAlt in Shift) then FModValue := FModValue + 1;

if (ssShift in Shift) then FModValue := FModValue + 4;

//READ HOTKEY SET VIRTUAL KEY

FVirtualKeyValue := Key;

//GET DISPLAY TEXT AND JUDGE WHETHER KEYSET IS VALIDATE

M_StrDisplay := GetDisplayText;

//REFLECTION

if FKeySetValid then

ActionOnHotKeyValid

else

ActionOnHotKeyInvalid ;

Text := M_StrDisplay;

end;

//---------------------------------------

// 在動態創建時顯示組合鍵

//---------------------------------------

procedure THotKeyEdit.DisplayHotKey;

begin

Text := GetDisplayText ;

end;

//---------------------------------------

// 熱鍵組合合法執行的代碼

//---------------------------------------

procedure THotKeyEdit.ActionOnHotKeyValid;

begin

Color:=FValidateColor;

end;

//---------------------------------------

// 熱鍵組合非法執行的代碼

//---------------------------------------

procedure THotKeyEdit.ActionOnHotKeyInvalid;

begin

Color := clWhite;

end;

end.

6 小結6.1 沒有解決的一些問題TXMLDocument的對象在創建時如果Owner參數爲nil則無法解析到節點,如果使用帶文檔路徑參數的重載的構造函數也會如此,因爲在TXMLDocument的源碼中重載的版本Owner也是nil。爲了規避這個問題,我犧牲了效率而將Owner置爲Application並手動釋放了文檔對象。考慮到如果使用self可能會因爲釋放兩次而産生錯誤,而Application的釋放影響不會很大。

沒有實現對于XML文檔合法性的檢驗,僅過濾了超出範圍的MOD和VK值,對于HKID是否唯一沒有做檢查。

沒有實現對于用戶定義的熱鍵之間的沖突,在TformHotKeyConfig中沒有判斷是否設置的了相同的熱鍵。

熱鍵編輯控件可以注冊到Pallete中,ThotKeyConfig類尚未控件化,如果控件化可能需要改變對象調用方式,公開構造函數允許創建多個實例。取消MgetInstance方法。

6.2 程序心得雖然Delphi中對于熱鍵的使用也不繁瑣,但是使用本方法可以利用流行的xml記錄熱鍵是挺誘人的,只要稍加修改就可以繼承到應用程序中。而且這樣自由度比較高,熱鍵數量、名稱、布局都是可以自定義的。

在組件化上,我只封裝了ThotKeyEdit控件,而沒有將ThotKeyConfig類嚴格封裝。因此只能通過代碼手動創建和調用。熱鍵編輯窗口是一個挺方便的設計,可以讓使用該類的用戶不必關心熱鍵編輯的實現。

在不斷的OO開發中,我也在摸索,程序中難免會有一些不如意之處,我誠心希望各位給我提出意見,我也很高興能在相關的問題上和大家一起討論討論。

本程序的相關代碼和測試示例可以在我的YAHOO公文包上下載。

 
用ASP實現一個真正的注冊頁面
沙灘小子 (一),設定注冊頁面的外觀: 在這個例子中涉及了五個頁面,其中有三幅是一般的htm文件,另外的兩幅是asp文件,在這裏制作利用的工具是frontpage98,但是大部分的asp代碼還是要...查看完整版>>用ASP實現一個真正的注冊頁面
 
用ASP實現一個真正的注冊頁面
     (一),設定注冊頁面的外觀:  在這個例子中涉及了五個頁面,其中有三幅是一般的htm文件,另外的兩幅是asp文件,在這裏制作利用的工具是frontpage98,但是大部分的asp代碼還是要自己輸入的:  1,設定...查看完整版>>用ASP實現一個真正的注冊頁面
 
用ASP實現一個真正的注冊頁面
  1,設定原來已經注冊的用戶進入的外觀:在這裏設置了兩個文本框,一個超級鏈接和兩個按鈕。兩個文本框分別用來輸入帳號(txtNum)和密碼(txtPasswd),超級鏈接(New)鏈接到新用戶進行注冊的頁面,兩個按鈕是用...查看完整版>>用ASP實現一個真正的注冊頁面
 
一個實現FTP斷點續傳的類
本文建立在你對socket知識有一點點的基礎之上(有一點點就足夠了:)) FTP客戶端實現要建立兩個通道,一個控制命令通道,讓FTP服務器知道客戶端要幹什麽,一個數據傳輸通道。所謂的兩個通道只不過是兩個調用...查看完整版>>一個實現FTP斷點續傳的類
 
【原創】輕松實現一個操作ini文件的類
作者:lixiaosan(CSDN) 前言: 相信很多朋友在編寫自己的程序中,都需要把一些數據先期導入到程序中進行初始化。那麽這個時候,比較好的做法就是把你所有的數據寫入一個ini文件,然後在程序去讀ini文件中的數據...查看完整版>>【原創】輕松實現一個操作ini文件的類