实现一个热键注册编辑的类
CST(http://blog.csdn.net/mrtechno) 2005-8-19
4.3 热键自定义窗体类TformHotKeyconfig. 6
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公文包上下载。