views:

386

answers:

6

I need to set a hot key at the operating system level, that once set will call whatever I tell it to call. The hot key set must be done from inside my preference option, but the application must not have to be open so that the hot key works.

+1  A: 

This will do it (uses ComObj, ActiveX, ShlObj):

procedure TForm1.Button1Click(Sender: TObject);
var
  IObject: IUnknown;
  ISLink: IShellLink;
  IPFile: IPersistFile;
const
  HOTKEY_CONTROL = 512;
  HOTKEY_ALT = 1024;
begin
  // Creates a shell link (*.lnk) to C:\WINDOWS\notepad.exe in the directory of this exe;
  // the link has keyboard shortcut CTRL+ALT+N.
  IObject := CreateComObject(CLSID_ShellLink);
  ISLink := IObject as IShellLink;
  IPFile := IObject as IPersistFile;
  ISLink.SetPath('C:\WINDOWS\notepad.exe');
  ISLink.SetHotkey(Ord('N') + HOTKEY_CONTROL + HOTKEY_ALT);
  IPFile.Save(PChar(ExtractFilePath(Application.ExeName) + 'Shortcut to Notepad.lnk'), false);
end;

But, if you only wish to start another application from your application, simply use ShellExecute (uses ShellAPI):

procedure TForm1.Button2Click(Sender: TObject);
begin
  ShellExecute(Handle, nil, 'C:\WINDOWS\notepad.exe', nil, nil, SW_SHOWNORMAL);
end;

Of course, using a TActionList or a TMainMenu, you can assign any keyboard shortcut to any Delphi function or procedure.

Andreas Rejbrand
+4  A: 

I think that you're looking for RegisterHotKey function.

Alexander
+4  A: 

You need an application instance which is listening to the hotkey or rather to the message which will be send by system if the hotkey is pressed (after registering with RegisterHotKey). So either your application is listening by itself and gets activated/comes to front or you have a hotkey listener which launches a second application.

While the Windows shell (explorer.exe) allows you to define shortcut keys to start a program, it is not possible to use the Win key (not under Windows 7 either), because the ISHellLink interface supports only Alt, Ctrl and Shift via the SetHotKey method. Probably that's why applications like WinKey exists.

splash
+3  A: 

A shortcut (.LNK file) that is on the Desktop or in the Start Menu hierarchy can have a "shortcut key" (hotkey) assigned to it. [1,2]

The Properties dialog, at least under XP, doesn't allow Win key combinations but you might be able to create them programmatically. The JCL has a function ShellLinkCreate() to create shortcuts. Note that after creating a shortcut programmatically, Explorer will not automatically pick up the hotkey you have set. Obviously you could restart Explorer (which can be done cleanly without logging off [3,4]) or you might get away with just calling SHChangeNotify for the .lnk file, or maybe broadcasting a WM_SETTINGCHANGE message.

[1] Start a program using a keyboard shortcut
[2] How to create a keyboard shortcut for a program in Windows XP
[3] Hidden Trick to Exit Windows Explorer Using Shutdown Dialog Box in Windows XP
[4] Hidden Trick to Exit Windows Explorer Using Start Menu in Windows Vista and Server 2008

Hugh Allen
+3  A: 

Using the Windows Key is against Microsoft's User Experience Guidelines. See mghie's answer to "THotkey with win-key support" for a good comment about this.

And if you look at MSDN's writeup for RegisterHotKey, you'll see that even though they allow a MOD_WIN value, they specifically state: "Keyboard shortcuts that involve the WINDOWS key are reserved for use by the operating system."

Windows allows shortcut keys for programs. You can add a shortcut on the Property Page of the executable. This is exactly what you want to do in your program. On the Property Page, they let you set Ctrl key, Shift key and Alt key and combinations of up to three of those keys with another key as your shortcut. But when you try to use the Windows key, it will not let you use it, and will suggest Crtl+Alt instead.

Your can use RegisterHotKey as Alexander first answered to do this. Here's a Delphi example of how to do it.

But I strongly suggest you not use the Windows Key for your shortcuts.

lkessler
+4  A: 

This does what you want.

First, you need a program that runs in the background and listens to, and responds to, keystrokes. Like this:

program Project1;

uses
  Windows, Messages, ShellAPI;

var
  W: HWND;
  M: MSG;

const
  WM_SETHOTKEY = WM_APP + 1;
  WM_UNSETHOTKEY = WM_APP + 2;
  AppName = 'Rejbrand Hot Key Listener';

const
  FileNames: array[0..1] of string = ('notepad.exe', 'pbrush.exe');

begin

  if FindWindow('STATIC', PChar(AppName)) <> 0 then
    Exit;


  W := CreateWindow('STATIC', PChar(AppName), 0, 0, 0, 100, 100, HWND_MESSAGE, 0, HInstance, nil);

  while GetMessage(M, W, 0, 0) do
    case M.message of
      WM_HOTKEY:
        ShellExecute(0, nil, PChar(FileNames[M.wParam]), nil, nil, SW_SHOWNORMAL);
      WM_SETHOTKEY:
        RegisterHotKey(W, M.wParam, M.lParam shr 16, M.lParam and $FFFF);
      WM_UNSETHOTKEY:
        UnregisterHotKey(W, M.wParam);
    end;

end.

(To create this program, select New/VCL Forms Application, and then remove the main form from the project. Then select Project/View Source and remove the Application.Initialize nonsense. The program should look like the above.)

The above program listens to the messages WM_SETHOTKEY that registers a new Windows hotkey, WM_UNSETHOTKEY that removes a previously registered hotkey, and WM_HOTKEY that is sent by Windows when a registered hotkey is activated by the end-user. The first two messages are defined by me, in this application.

To register a hotkey, send the message WM_SETHOTKEY to the window W. The wParam of the message should be the index (in the FileNames array) of the program to start. The lParam should be of the form $MMMMKKKK where $MMMM are the modifiers (Ctrl, Alt, Shift) and $KKKK the virtual-key code of the hotkey. To remove a hotkey, send a WM_UNSETHOTKEY message with the program index as wParam to W.

Sample usage

From any application, you can do (assuming that Project1.exe is running in the background)

const
  WM_SETHOTKEY = WM_APP + 1;
  WM_UNSETHOTKEY = WM_APP + 2;

const
  MODIFIER_ALT = MOD_ALT shl 16;
  MODIFIER_CTRL = MOD_CONTROL shl 16;
  MODIFIER_SHIFT = MOD_SHIFT shl 16;

procedure TForm1.RegisterHotkeys;
var
  w: HWND;
begin
  w := FindWindow('STATIC', 'Rejbrand Hot Key Listener');
  PostMessage(w, WM_UNSETHOTKEY, 0, MODIFIER_CTRL + MODIFIER_ALT + ord('N'));
  PostMessage(w, WM_SETHOTKEY, 1, MODIFIER_CTRL + MODIFIER_ALT + ord('P'));
end;

Now, even if you close this new program, notepad.exe and pbrush.exe will start on Ctrl+Alt+N and Ctrl+Alt+P, respectively.

Some more discussion

Notice that, when compiled, Project1.exe is only 20 kB small! This is tiny for an application made in Delphi!

To unregister a previously registered hotkey, do

PostMessage(w, WM_UNSETHOTKEY, N, 0);

where N, in this example, is = 0 for notepad and = 1 for pbrush.

To quit project1.exe, do

PostMessage(w, WM_QUIT, 0, 0);

But, of course, if you quit project1.exe, all hotkeys are unregistered by Windows.

You might want to do

procedure TForm1.RegisterHotkeys;
var
  w: HWND;
begin
  w := FindWindow('STATIC', 'Rejbrand Hot Key Listener');
  if w = 0 then
    MessageBox('Error: Rejbrand Hot Key Listener not running!');
  PostMessage(w, WM_UNSETHOTKEY, 0, MODIFIER_CTRL + MODIFIER_ALT + ord('N'));
  PostMessage(w, WM_SETHOTKEY, 1, MODIFIER_CTRL + MODIFIER_ALT + ord('P'));
end;

or even start project1.exe if you cannot find the window.

Andreas Rejbrand
I'm not sure: Do I need to call `DispatchMessage` in this case? (Yes, Jon Skeet, I am talking to you.)
Andreas Rejbrand