unit Unit1; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, ActnList, ComCtrls, ExtCtrls; type TForm1 = class(TForm) btn1: TButton; grp1: TGroupBox; btn2: TButton; grp2: TGroupBox; lbl1: TLabel; actlst1: TActionList; lbl2: TLabel; lbl3: TLabel; act1: TAction; act2: TAction; act3: TAction; grp3: TGroupBox; btn3: TButton; btn4: TButton; grp4: TGroupBox; hk1: THotKey; btn5: TButton; grp5: TGroupBox; lst1: TListBox; btn6: TButton; grp6: TGroupBox; lbl4: TLabel; btn7: TButton; grp7: TGroupBox; lbl5: TLabel; btn8: TButton; tmr1: TTimer; procedure btn1Click(Sender: TObject); procedure btn2Click(Sender: TObject); procedure act1Execute(Sender: TObject); procedure act2Execute(Sender: TObject); procedure act3Execute(Sender: TObject); procedure FormCreate(Sender: TObject); procedure btn3Click(Sender: TObject); procedure btn4Click(Sender: TObject); procedure btn5Click(Sender: TObject); procedure FormDestroy(Sender: TObject); procedure btn6Click(Sender: TObject); procedure btn7Click(Sender: TObject); procedure btn8Click(Sender: TObject); procedure tmr1Timer(Sender: TObject); private procedure WmHotKey(var msg:TMessage); message WM_HOTKEY;//自定义快捷键响应函数 procedure GetCombination(Modifiers: Word); //获取所有系统热键 procedure ScanHotKeys; //获取所有系统热键 public { Public declarations } end; var Form1: TForm1; implementation {$R *.dfm} var Hook :HHook;//钩子句柄 {键盘回调函数 LRESULT CALLBACK KeyboardProc( int code, WPARAM wParam, LPARAM lParam );} function LowLevelKeyboardProc(vNode:Integer;wParam:WPARAM;lParam:LPARAM):LRESULT;stdcall; (*自定义消息结构体 typedef struct tagMSG { HWND hwnd; 消息将要发送到的那个窗口的句柄,用这个参数可以决定让哪个窗口接收消息 UINT message; 消息号,它唯一标识了一种消息类型。每种消息类型都在Windows文件进行了预定义。 WPARAM wParam; 一个32位的消息参数,这个值的确切意义取决于消息本身。 LPARAM lParam;同上。 DWORD time;消息放入消息队列中的时间,在这个域中写入的并非当时日期,而是从Windows启动后所测量的时间值。 POINT pt;消息放入消息队列时的鼠标坐标。 } MSG, *PMSG; *) type KbDllHookStruct = record vkCode:DWord; ScanCode:DWord; Flags:DWord; Time:DWord; dwExtraInfo:DWord; end; var P:^KbDllHookStruct;//定义消息指针 begin Result := 0;//如果为0则表示失败 P := Pointer(LPARAM);//消息结构图转换为指针 if vNode = HC_ACTION then //如果这个消息wParam和lParam包含了按键消息 begin case wParam of //判断这个消息为按下键或者按下放开系统键 WM_KEYUP,WM_SYSKEYUP: if (p.vkCode = VK_HOME) {OR (p.vkCode = VK_RWIN)}//判断按下的是不是左右Win键盘 then begin Form1.Text := '您按下了Home键!'; //ShowMessage('您按下了Home键!'); Result := 1; //返回1 end; end; {如果 code不为按键消息,则 必须让KeyboardProc()函数返回CallNextHookEx()} if vNode <> 0 then //如果这个消息 Result := CallNextHookEx(0,vNode,wParam,lParam); end; end; {注册钩子} procedure TForm1.btn1Click(Sender: TObject); begin (*HHOOK SetWindowsHookEx( int idHook,钩子的类型,即它处理的消息类型 HOOKPROC lpfn, 钩子子程的地址指针。如果dwThreadId参数为0 或是一个由别的进程创建的线程的标识, lpfn必须指向DLL中的钩子子程。除此以外,lpfn可以指向当前进程的一段钩子子程代码。 钩子函数的入口地址,当钩子钩到任何消息后便调用这个函数。 HINSTANCE hMod, 应用程序实例的句柄。标识包含lpfn所指的子程的DLL。 如果dwThreadId 标识当前进程创建的一个线程,而且子程代码位于当前进程,hMod必须为NULL。 可以很简单的设定其为本应用程序的实例句柄。 DWORD dwThreadId 与安装的钩子子程相关联的线程的标识符。如果为0,钩子子程 与所有的线程关联,即为全局钩子。);*) {WH_KEYBOARD_LL为13是键盘钩子} Hook := SetWindowsHookEx(WH_KEYBOARD_LL,LowLevelKeyboardProc,hInstance,0); end; {卸载钩子} procedure TForm1.btn2Click(Sender: TObject); begin UnHookWindowsHookEx(Hook);//卸载钩子 end; {锁定大小写} procedure ToggleCapsLock; var KeyState: TKeyboardState;//键盘状态 begin GetKeyboardState(KeyState);//获取键盘状态 if (KeyState[VK_CAPITAL] = 0) then //如果键盘大小写没锁定 begin //按下 {VOID keybd_event(BYTE bVk,BYTE bScan,DWORD dwFlags,DWORD dwExtralnfo); bVk:定义一个虚拟键码。键码值必须在1~254之间。 bScan:定义该键的硬件扫描码。 dwFlags:定义函数操作的各个方面的一个标志位集。应用程序可使用如下一些预定义常数的组合设置标志位。 KEYEVENTF_EXTENDEDKEY:若指定该值,则扫描码前一个值为OXEO(224)的前缀字节。 KEYEVENTF_KEYUP:若指定该值,该键将被释放;若未指定该值,该键将被按下。 dwExtralnfo:定义与击键相关的附加的32位值。} Keybd_Event(VK_CAPITAL, 1, KEYEVENTF_EXTENDEDKEY or 0, 0);//按下 (KEYEVENTF_EXTENDEDKEY为扩展键) Keybd_Event(VK_CAPITAL, 1, KEYEVENTF_EXTENDEDKEY or KEYEVENTF_KEYUP, 0);//松开 end else begin //松开 Keybd_Event(VK_CAPITAL, 0, KEYEVENTF_EXTENDEDKEY or 0, 0); Keybd_Event(VK_CAPITAL, 0, KEYEVENTF_EXTENDEDKEY or KEYEVENTF_KEYUP, 0); end; end; {锁定数字键} procedure ToggleNumLock; var KeyState: TKeyboardState;//键盘状态 begin GetKeyboardState(KeyState);//获取键盘状态 if (KeyState[VK_NUMLOCK] = 0) then //如果小键盘没锁定 begin //按下 {VOID keybd_event(BYTE bVk,BYTE bScan,DWORD dwFlags,DWORD dwExtralnfo); bVk:定义一个虚拟键码。键码值必须在1~254之间。 bScan:定义该键的硬件扫描码。键的OEM扫描码 dwFlags:定义函数操作的各个方面的一个标志位集。应用程序可使用如下一些预定义常数的组合设置标志位。 KEYEVENTF_EXTENDEDKEY:若指定该值,则扫描码前一个值为OXEO(224)的前缀字节。 KEYEVENTF_KEYUP:若指定该值,该键将被释放;若未指定该值,该键将被按下。 dwExtralnfo:定义与击键相关的附加的32位值。} Keybd_Event(VK_NUMLOCK, 1, KEYEVENTF_EXTENDEDKEY or 0, 0);//按下 (KEYEVENTF_EXTENDEDKEY为扩展键) Keybd_Event(VK_NUMLOCK, 1, KEYEVENTF_EXTENDEDKEY or KEYEVENTF_KEYUP, 0);//松开 end else begin //松开 Keybd_Event(VK_NUMLOCK, 0, KEYEVENTF_EXTENDEDKEY or 0, 0); Keybd_Event(VK_NUMLOCK, 0, KEYEVENTF_EXTENDEDKEY or KEYEVENTF_KEYUP, 0); end; end; {锁定大小写} procedure TForm1.btn3Click(Sender: TObject); var KeyState: TKeyboardState; //键盘状态 begin ToggleCapsLock;//改变函数 GetKeyboardState(KeyState);//获取按键状态 btn3.Caption := '大小写状态:' + IntToStr(KeyState[VK_CAPITAL]);//改变按钮文本 end; {锁定数字键盘} procedure TForm1.btn4Click(Sender: TObject); var KeyState: TKeyboardState; begin ToggleNumLock; GetKeyboardState(KeyState);//获取按键状态 btn4.Caption := '小键盘状态:' + IntToStr(KeyState[VK_NUMLOCK]);//改变按钮文本 end; {注册快捷键} var Id: Integer; Key,Shift: Word;//定义2个按键(用于快捷键的) procedure TForm1.btn5Click(Sender: TObject); var {shift 是一个集合变量。type TShiftState = set of (ssShift, ssAlt, ssCtrl, ssLeft, ssRight, ssMiddle, ssDouble);} TShift: TShiftState; begin {使用前必须先注册热键.HotKeyId的合法取之范围是0x0000到0xBFFF之间, GlobalAddAtom函数得到的值 在0xC000到0xFFFF之间,所以减掉0xC000来满足调用要求。} Id := GlobalAddAtom('MyHotKey') - $C000; {判断用了哪些系统键} Key := hk1.HotKey and not (scShift + scCtrl + scAlt); TShift := []; if hk1.HotKey and scShift <> 0 then Include(TShift, ssShift); if hk1.HotKey and scCtrl <> 0 then Include(TShift, ssCtrl); if hk1.HotKey and scAlt <> 0 then Include(TShift, ssAlt); {判断是哪个系统键} Shift:= 0; if ssShift in TShift then Shift := MOD_SHIFT; if ssCtrl in TShift then Shift := Shift or MOD_CONTROL; if ssAlt in TShift then Shift:= Shift or MOD_ALT; {BOOL RegisterHotKey( HWND hWnd,响应该热键的窗口句柄 Int id,该热键的唯一标识 UINT fsModifiers,该热键的辅助按键 UINT vk该热键的键值);} if not RegisterHotKey(Handle, Id, Shift, Key) then //如果注册失败 MessageDlg('注册热键失败,与系统中已注册的热键冲突,请尝试其它热键',mtError,[mbOk],0); end; {获取所有系统热键} const // Windows 2000/XP multimedia keys (adapted from winuser.h and renamed to avoid potential conflicts) // See also: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/winui/winui/WindowsUserInterface/UserInput/VirtualKeyCodes.asp _VK_BROWSER_BACK = $A6; // Browser Back key _VK_BROWSER_FORWARD = $A7; // Browser Forward key _VK_BROWSER_REFRESH = $A8; // Browser Refresh key _VK_BROWSER_STOP = $A9; // Browser Stop key _VK_BROWSER_SEARCH = $AA; // Browser Search key _VK_BROWSER_FAVORITES = $AB; // Browser Favorites key _VK_BROWSER_HOME = $AC; // Browser Start and Home key _VK_VOLUME_MUTE = $AD; // Volume Mute key _VK_VOLUME_DOWN = $AE; // Volume Down key _VK_VOLUME_UP = $AF; // Volume Up key _VK_MEDIA_NEXT_TRACK = $B0; // Next Track key _VK_MEDIA_PREV_TRACK = $B1; // Previous Track key _VK_MEDIA_STOP = $B2; // Stop Media key _VK_MEDIA_PLAY_PAUSE = $B3; // Play/Pause Media key _VK_LAUNCH_MAIL = $B4; // Start Mail key _VK_LAUNCH_MEDIA_SELECT = $B5; // Select Media key _VK_LAUNCH_APP1 = $B6; // Start Application 1 key _VK_LAUNCH_APP2 = $B7; // Start Application 2 key // Self-invented names for the extended keys NAME_VK_BROWSER_BACK = 'Browser Back'; NAME_VK_BROWSER_FORWARD = 'Browser Forward'; NAME_VK_BROWSER_REFRESH = 'Browser Refresh'; NAME_VK_BROWSER_STOP = 'Browser Stop'; NAME_VK_BROWSER_SEARCH = 'Browser Search'; NAME_VK_BROWSER_FAVORITES = 'Browser Favorites'; NAME_VK_BROWSER_HOME = 'Browser Start/Home'; NAME_VK_VOLUME_MUTE = 'Volume Mute'; NAME_VK_VOLUME_DOWN = 'Volume Down'; NAME_VK_VOLUME_UP = 'Volume Up'; NAME_VK_MEDIA_NEXT_TRACK = 'Next Track'; NAME_VK_MEDIA_PREV_TRACK = 'Previous Track'; NAME_VK_MEDIA_STOP = 'Stop Media'; NAME_VK_MEDIA_PLAY_PAUSE = 'Play/Pause Media'; NAME_VK_LAUNCH_MAIL = 'Start Mail'; NAME_VK_LAUNCH_MEDIA_SELECT = 'Select Media'; NAME_VK_LAUNCH_APP1 = 'Start Application 1'; NAME_VK_LAUNCH_APP2 = 'Start Application 2'; const LOCALIZED_KEYNAMES = True; HotKeyAtomPrefix = 'HotKeyManagerHotKey'; // Non-localized (!) modifier names ModName_Shift = 'Shift'; ModName_Ctrl = 'Ctrl'; ModName_Alt = 'Alt'; ModName_Win = 'Win'; var EnglishKeyboardLayout: HKL; ShouldUnloadEnglishKeyboardLayout: Boolean; // Localized (!) modifier names; initialized to English names LocalModName_Shift: string = ModName_Shift; LocalModName_Ctrl: string = ModName_Ctrl; LocalModName_Alt: string = ModName_Alt; LocalModName_Win: string = ModName_Win; {是否为扩展键} function IsExtendedKey(Key: Word): Boolean; begin Result := ((Key >= _VK_BROWSER_BACK) and (Key <= _VK_LAUNCH_APP2)); end; {分割热键} procedure SeparateHotKey(HotKey: Cardinal; var Modifiers, Key: Word); // Separate key and modifiers, so they can be used with RegisterHotKey const VK2_SHIFT = 32; VK2_CONTROL = 64; VK2_ALT = 128; VK2_WIN = 256; var Virtuals: Integer; V: Word; // x: Byte; x: Word; begin Key := Byte(HotKey); x := HotKey shr 8;//右移8位 Virtuals := x; V := 0; if (Virtuals and VK2_WIN) <> 0 then Inc(V, MOD_WIN); if (Virtuals and VK2_ALT) <> 0 then Inc(V, MOD_ALT); if (Virtuals and VK2_CONTROL) <> 0 then Inc(V, MOD_CONTROL); if (Virtuals and VK2_SHIFT) <> 0 then Inc(V, MOD_SHIFT); Modifiers := V; end; {把热键显示在列表框里面} function HotKeyToText(HotKey: Cardinal; Localized: Boolean): string; {获取扩展键} function GetExtendedVKName(Key: Word): string; begin case Key of _VK_BROWSER_BACK: Result := NAME_VK_BROWSER_BACK; _VK_BROWSER_FORWARD: Result := NAME_VK_BROWSER_FORWARD; _VK_BROWSER_REFRESH: Result := NAME_VK_BROWSER_REFRESH; _VK_BROWSER_STOP: Result := NAME_VK_BROWSER_STOP; _VK_BROWSER_SEARCH: Result := NAME_VK_BROWSER_SEARCH; _VK_BROWSER_FAVORITES: Result := NAME_VK_BROWSER_FAVORITES; _VK_BROWSER_HOME: Result := NAME_VK_BROWSER_HOME; _VK_VOLUME_MUTE: Result := NAME_VK_VOLUME_MUTE; _VK_VOLUME_DOWN: Result := NAME_VK_VOLUME_DOWN; _VK_VOLUME_UP: Result := NAME_VK_VOLUME_UP; _VK_MEDIA_NEXT_TRACK: Result := NAME_VK_MEDIA_NEXT_TRACK; _VK_MEDIA_PREV_TRACK: Result := NAME_VK_MEDIA_PREV_TRACK; _VK_MEDIA_STOP: Result := NAME_VK_MEDIA_STOP; _VK_MEDIA_PLAY_PAUSE: Result := NAME_VK_MEDIA_PLAY_PAUSE; _VK_LAUNCH_MAIL: Result := NAME_VK_LAUNCH_MAIL; _VK_LAUNCH_MEDIA_SELECT: Result := NAME_VK_LAUNCH_MEDIA_SELECT; _VK_LAUNCH_APP1: Result := NAME_VK_LAUNCH_APP1; _VK_LAUNCH_APP2: Result := NAME_VK_LAUNCH_APP2; else Result := ''; end; end; {获取左右的ctrl或者alt或者shift键} function GetModifierNames: string; var S: string; begin S := ''; if Localized then begin if (HotKey and $4000) <> 0 then // scCtrl S := S + LocalModName_Ctrl + '+'; if (HotKey and $2000) <> 0 then // scShift S := S + LocalModName_Shift + '+'; if (HotKey and $8000) <> 0 then // scAlt S := S + LocalModName_Alt + '+'; if (HotKey and $10000) <> 0 then S := S + LocalModName_Win + '+'; end else begin if (HotKey and $4000) <> 0 then // scCtrl S := S + ModName_Ctrl + '+'; if (HotKey and $2000) <> 0 then // scShift S := S + ModName_Shift + '+'; if (HotKey and $8000) <> 0 then // scAlt S := S + ModName_Alt + '+'; if (HotKey and $10000) <> 0 then S := S + ModName_Win + '+'; end; Result := S; end; {获取快捷键名字} function GetVKName(Special: Boolean): string; var ScanCode: Cardinal; KeyName: array[0..255] of Char; oldkl: HKL; Modifiers, Key: Word; begin Result := ''; if Localized then {本地特有键} begin if Special then ScanCode := (MapVirtualKey(Byte(HotKey), 0) shl 16) or (1 shl 24) else ScanCode := (MapVirtualKey(Byte(HotKey), 0) shl 16); if ScanCode <> 0 then begin GetKeyNameText(ScanCode, KeyName, SizeOf(KeyName)); Result := KeyName; end; end else {英文键} begin if Special then ScanCode := (MapVirtualKeyEx(Byte(HotKey), 0, EnglishKeyboardLayout) shl 16) or (1 shl 24) else ScanCode := (MapVirtualKeyEx(Byte(HotKey), 0, EnglishKeyboardLayout) shl 16); if ScanCode <> 0 then begin oldkl := GetKeyboardLayout(0); if oldkl <> EnglishKeyboardLayout then ActivateKeyboardLayout(EnglishKeyboardLayout, 0); // Set English kbd. layout GetKeyNameText(ScanCode, KeyName, SizeOf(KeyName)); Result := KeyName; if oldkl <> EnglishKeyboardLayout then begin if ShouldUnloadEnglishKeyboardLayout then UnloadKeyboardLayout(EnglishKeyboardLayout); // Restore prev. kbd. layout ActivateKeyboardLayout(oldkl, 0); end; end; end; if Length(Result) <= 1 then begin // Try the internally defined names SeparateHotKey(HotKey, Modifiers, Key); if IsExtendedKey(Key) then Result := GetExtendedVKName(Key); end; end; var KeyName: string; begin case Byte(HotKey) of // PgUp, PgDn, End, Home, Left, Up, Right, Down, Ins, Del $21..$28, $2D, $2E: KeyName := GetVKName(True); else KeyName := GetVKName(False); end; Result := GetModifierNames + KeyName; end; {测试热键是否可以被注册} function HotKeyAvailable(HotKey: Cardinal): Boolean; var M, K: Word; Atom: Word; begin Atom := GlobalAddAtom(PChar('HotKeyManagerHotKeyTest')); SeparateHotKey(HotKey, M, K); Result := RegisterHotKey(Application.Handle, Atom, M, K); if Result then UnregisterHotKey(Application.Handle, Atom); GlobalDeleteAtom(Atom); end; {获取系统热键} function GetHotKey(Modifiers, Key: Word): Cardinal; const VK2_SHIFT = 32; VK2_CONTROL = 64; VK2_ALT = 128; VK2_WIN = 256; var hk: Cardinal; begin hk := 0; if (Modifiers and MOD_ALT) <> 0 then Inc(hk, VK2_ALT); if (Modifiers and MOD_CONTROL) <> 0 then Inc(hk, VK2_CONTROL); if (Modifiers and MOD_SHIFT) <> 0 then Inc(hk, VK2_SHIFT); if (Modifiers and MOD_WIN) <> 0 then Inc(hk, VK2_WIN); hk := hk shl 8; Inc(hk, Key); Result := hk; end; {系统热键输出到列表框} procedure TForm1.GetCombination(Modifiers: Word); var I: Integer; HotKey: Cardinal; begin for I := $08 to $E6 do begin HotKey := GetHotKey(Modifiers, I); if not HotKeyAvailable(HotKey) then lst1.Items.Add(HotKeyToText(HotKey, LOCALIZED_KEYNAMES)); end; end; {取系统热键(主函数)} procedure TForm1.ScanHotKeys; begin Screen.Cursor := crHourGlass; lst1.Clear; GetCombination(MOD_WIN); GetCombination(MOD_WIN + MOD_SHIFT); GetCombination(MOD_WIN + MOD_CONTROL); GetCombination(MOD_WIN + MOD_ALT); GetCombination(MOD_WIN + MOD_SHIFT + MOD_CONTROL); GetCombination(MOD_WIN + MOD_SHIFT + MOD_ALT); GetCombination(MOD_WIN + MOD_CONTROL + MOD_ALT); GetCombination(MOD_WIN + MOD_SHIFT + MOD_CONTROL + MOD_ALT); GetCombination(MOD_SHIFT); GetCombination(MOD_SHIFT + MOD_CONTROL); GetCombination(MOD_SHIFT + MOD_ALT); GetCombination(MOD_SHIFT + MOD_CONTROL + MOD_ALT); GetCombination(MOD_CONTROL); GetCombination(MOD_CONTROL + MOD_ALT); GetCombination(MOD_ALT); GetCombination(0); Screen.Cursor := crDefault; end; {获取所有系统热键} procedure TForm1.btn6Click(Sender: TObject); begin ScanHotKeys; end; {注册个性化热键} var TmpAtom:atom; //键盘标识符 KeyUse:Boolean; procedure TForm1.btn7Click(Sender: TObject); begin {向全局原子表添加一个字符串,并返回这个字符串的唯一标识符(原子ATOM)} TmpAtom:=globaladdatom('hot key'); {注册快捷键} {BOOL RegisterHotKey( HWND hWnd,响应该热键的窗口句柄 Int id,该热键的唯一标识 UINT fsModifiers,该热键的辅助按键 UINT vk该热键的键值);} KeyUse:=registerhotkey(handle,TmpAtom,0,vk_f11); end; {个性化热键2} procedure TForm1.tmr1Timer(Sender: TObject); begin tmr1.Interval:=1; if (GetKeyState(Ord('P'))<0) and (GetKeyState(Ord('J'))<0) then Begin tmr1.Interval := 200; //延时,防止事件触发过快; ShowMessage('你按下了个性化快捷键j+p'); End; end; {个性化热键2} procedure TForm1.btn8Click(Sender: TObject); begin If KeyUse Then Begin UnregisterHotKey(Handle,TmpAtom);//注册热键 GlobalDeleteAtom(TmpAtom); //如果热键被使用,就撤销热键pjjp End; tmr1.Interval:=1; tmr1.Enabled:=True; end; {热键相应函数} procedure TForm1.WmHotKey(var msg:TMessage); begin {热键对应的函数} if (Msg.LparamLo = Shift) AND (Msg.LParamHi = Key) then //如果按下了自定义快捷键 ShowMessage('自定义快捷键!'); {个性化热键} If (Msg.LParamHi=Vk_F11) Then //判断按键是否为F11 Begin if GetKeyState(Ord('T'))<0 then //判断T键是否被按下 ShowMessage('你按下了个性化快捷键'); //输出消息 End; end; procedure TForm1.FormCreate(Sender: TObject); begin end; {退出时候取消热键} procedure TForm1.FormDestroy(Sender: TObject); begin {退出时撤销已经注册的热键} if not UnRegisterHotKey(Self.Handle,id) then MessageDlg('撤销热键失败!',mtError,[mbOk],0); {撤销热键} GlobalDeleteAtom(Id); {退出时撤销已经注册的个性化热键} If KeyUse Then Begin UnregisterHotKey(Handle,TmpAtom); GlobalDeleteAtom(TmpAtom); End; end; {响应TActionList函数} procedure TForm1.act1Execute(Sender: TObject); begin ShowMessage('您按下了F5!'); end; {响应TActionList函数} procedure TForm1.act2Execute(Sender: TObject); begin ShowMessage('您按下了F6!'); end; {响应TActionList函数} procedure TForm1.act3Execute(Sender: TObject); begin ShowMessage('您按下了F7!'); end; end.