Author: Bill Todd
A Component for Creating and Modifying Shortcuts
Answer:
Windows shortcuts provide a way to have as many links to a file as you need - in as
many folders as you want. Shortcuts are also the tool for adding a file to the
Windows Start menu.
In Windows 3.x, creating shortcuts was easy. You had to learn a couple of simple
DDE calls, and that was it. In 32-bit Windows, working with shortcuts is more
complex, and requires the use of COM and interfaces. This article will look at
working with shortcuts in detail, and show you how to build a custom component you
can use to create and modify shortcuts in any folder.
The Interfaces
Shortcuts, or links as they are sometimes called, are actually binary files stored
on your hard disk with the .lnk extension. The Windows shell includes a COM object
named ShellLink for working with shortcuts. The ShellLink object implements two
interfaces, IShellLink and IPersistFile, that define the methods for working with
shortcuts. Figure 1 shows the declaration of IShellLink from SHLOBJ.PAS, and Figure
2 shows the declaration of IPersistFile from ACTIVEX.PAS.
1 IShellLinkA = interface(IUnknown) { sl. }
2 [SID_IShellLinkA]
3 function GetPath(pszFile: PAnsiChar; cchMaxPath: Integer;
4 var pfd: TWin32FindData; fFlags: DWORD): HResult;
5 stdcall;
6 function GetIDList(var ppidl: PItemIDList): HResult;
7 stdcall;
8 function SetIDList(pidl: PItemIDList): HResult; stdcall;
9 function GetDescription(pszName: PAnsiChar;
10 cchMaxName: Integer): HResult; stdcall;
11 function SetDescription(pszName: PAnsiChar): HResult;
12 stdcall;
13 function GetWorkingDirectory(pszDir: PAnsiChar;
14 cchMaxPath: Integer): HResult; stdcall;
15 function SetWorkingDirectory(pszDir: PAnsiChar): HResult;
16 stdcall;
17 function GetArguments(pszArgs: PAnsiChar;
18 cchMaxPath: Integer): HResult; stdcall;
19 function SetArguments(pszArgs: PAnsiChar): HResult;
20 stdcall;
21 function GetHotkey(var pwHotkey: Word): HResult; stdcall;
22 function SetHotkey(wHotkey: Word): HResult; stdcall;
23 function GetShowCmd(out piShowCmd: Integer): HResult;
24 stdcall;
25 function SetShowCmd(iShowCmd: Integer): HResult; stdcall;
26 function GetIconLocation(pszIconPath: PAnsiChar;
27 cchIconPath: Integer; out piIcon: Integer): HResult;
28 stdcall;
29 function SetIconLocation(pszIconPath: PAnsiChar;
30 iIcon: Integer): HResult; stdcall;
31 function SetRelativePath(pszPathRel: PAnsiChar;
32 dwReserved: DWORD): HResult; stdcall;
33 function Resolve(Wnd: HWND; fFlags: DWORD): HResult;
34 stdcall;
35 function SetPath(pszFile: PAnsiChar): HResult; stdcall;
36 end;
37 //Figure 1: The IShellLink interface.
38
39 IPersistFile = interface(IPersist)
40 ['{ 0000010B-0000-0000-C000-000000000046 }']
41 function IsDirty: HResult; stdcall;
42 function Load(pszFileName: POleStr; dwMode: Longint):
43 HResult; stdcall;
44 function Save(pszFileName: POleStr; fRemember: BOOL):
45 HResult; stdcall;
46 function SaveCompleted(pszFileName: POleStr): HResult;
47 stdcall;
48 function GetCurFile(out pszFileName: POleStr): HResult;
49 stdcall;
50 end;
51 Figure 2: The IPersistFile interface.
Into the TWinShortcut Component
The shell of the TWinShortcut custom component was created with the Component
Wizard in the Object Repository, using TComponent as its ancestor Listing One shows
the finished component. To make things easier to find, the properties and their
private member variables are in alphabetical order. In the implementation section,
the methods are divided into three groups: constructor and destructor, property
getter and setter, and custom methods. Within each of these groups, the methods are
in alphabetical order. The constructor is overridden to automatically create an
instance of the ShellLink object using the following code:
FShellLink := CreateComObject(CLSID_ShellLink) as IShellLink;
FPersistFile := FShellLink as IPersistFile;
The first statement creates the ShellLink object by calling CreateCOMObject and
passing the ShellLink object's class ID as the parameter. The return value is cast
to type IShellLink to provide a reference to the IShellLink interface and its
methods. FShellLink is a protected member variable of type IShellLink. FPersistFile
is also a protected member variable and is of type IPersistFile. Casting FShellLink
to IPersistFile provides an interface reference to the IPersistFile methods
implemented by the ShellLink object. TWinShortcut's destructor is overridden, and
both FShellLink and FPersistFile are set to nil to destroy the ShellLink object.
Because COM objects are reference counted, both variables must be set to nil before
the ShellLink object will be destroyed.
You must be able to specify the name and location of the shortcut file you want to
work with, and that capability is provided by three properties: ShortcutFileName,
ShortcutPath, and SpecialFolderLocation. One big problem in working with shortcuts
is figuring out where to create them. For example, if you want to create a shortcut
on the user's desktop, you have to know the path to the desktop folder, and that is
different for different versions of Windows.
The solution is a Windows API function named SHGetSpecialFolderLocation, which
takes three parameters. The first is a window handle, which can be set to zero. The
second is a constant that identifies the folder you want. To find a partial list of
constants, click Start | Programs | Borland Delphi 5 | Help | MS SDK Help Files |
Win32 Programmers Reference and search for SHGetSpecialFolderLocation. If you have
the MSDN Library CD, search for SHGetSpecialFolderLocation and you'll find a list
of over 40 folder constants. The Win32 Programmers Reference Help file also
contains detailed information about IShellLink and IPersistFile and their methods.
The third parameter is a variable of type PItemIdList.
After calling SHGetSpecialFolderLocation, you will call SHGetPathFromIdList to
extract the actual path from the PItemIdList parameter. The SpecialFolderLocation
property of TWinShortcut is of type Word and corresponds to the second parameter,
the folder number, of SHGetSpecialFolderLocation. This lets you specify the
location of the shortcut by setting the value of the SpecialFolderLocation
property, or by providing a path in the ShortcutPath property.
TWinShortcut has a public OpenShortcut method that's used to open an existing
shortcut. This method is only three statements long. The first statement is a call
to the protected method GetFullShortcutPath. GetFullShortcutPath returns the full
path and filename of the shortcut. The second statement, shown here, actually opens
the file by calling the IPersistFile interface's Load method:
OleCheck(FPersistFile.Load(PWideChar(WideString(FullPath)),
STGM_READWRITE));
Load's two parameters are the name of the file and the mode. Because this function
is Unicode-compatible, the path must be a null-terminated string of wide chars.
Because the call to GetFullShortcutPath returns a Pascal ANSI string, the path
variable FullPath is first cast to type WideString, and then cast to type PWideChar
to match the type of the parameter. Note that Load is called as a parameter to the
OleCheck procedure. OleCheck examines the value returned by
SHGetSpecialFolderLocation, and if that value indicates an error, OleCheck raises
an EOleSysError exception. This technique is used for all of the interface method
calls in this example, so normal Delphi exception handling can be used to trap
errors that occur when using this component. The last line of the LoadShortcut
method calls the custom method GetPropertiesFromShortcut, which calls each of the
get methods of the IShellLink interface and assigns the returned value to the
corresponding property of TWinShortcut.
Before continuing, let's look at the GetFullShortcutPath and
GetPropertiesFromShortcut methods used by OpenShortcut. If the ShortcutPath
property is null, GetFullShortcutPath calls GetSpecialFolderPath. Otherwise, it
assigns the value of the ShortcutPath property to Result. It then adds the
ShortcutFileName property to the end of the string. This is safe because
GetSpecialFolderPath always returns a path that ends with a backslash, and the
write method for the ShortcutPath property ensures that the property value always
ends with a backslash. The write method for the ShortcutFileName property ensures
that the filename always includes the .lnk extension.
GetSpecialFolderPath calls SHGetSpecialFolderLocation and passes the value of the
SpecialFolderLocation property as the second parameter. This call loads the
ItemIdList variable passed as the third parameter. Next, GetSpecialFolderPath calls
SHGetPathFromIdList, passing two parameters. The first is the ItemIdList variable
initialized by the call to SHGetSpecialFolderLocation, and the second is a char
array, CharStr, into which the path will be placed. Finally, CharStr is assigned to
the Result variable and a backslash is appended to the path.
The final step in the OpenShortcut method is the call to GetPropertiesFromShortcut.
This method calls each of the get methods in the IShellLink interface and assigns
the returned value to the corresponding property of TWinShortcut. For example, the
first call is to the IShellLink GetPath method, which returns the path to the
target file, i.e. the file to which the shortcut points. These calls are
straightforward with two exceptions. If you create a shortcut manually in Windows,
and the shortcut is to a program that requires command-line arguments, you type
them in the Target edit box following the path to the EXE file. However, the
command-line arguments are stored separately in the shortcut file and are retrieved
with a separate call, GetArguments.
The call to GetHotkey returns the hotkey information in a single parameter of type
Word. The virtual key code is stored in the low byte, and the modifier flags that
indicate which shift keys were pressed are stored in the high-order byte. If you
want to display the hotkey as text, or give users the ability to enter a hotkey,
the easy way is to use the THotkey component from the Win32 page of the Component
palette. The problem is that the THotkey component stores the virtual key code in
its HotKey property, and the modifier flags in its Modifiers property. To make
things worse, the values used to represent the [Ctrl], [Alt], [Shift], and extended
keys in the high byte of the value returned by GetHotkey, aren't the same as the
values used to represent the same keys in the Modifiers property of THotkey.
(Note: The extended-key flag indicates whether the keystroke message originated
from one of the additional keys on the enhanced keyboard. The extended keys consist
of the [Alt] and [Ctrl] keys on the right-hand side of the keyboard; the [Ins],
[Del], [Home], [End], [PageUp], [PageDown], and the arrow keys to the left of the
numeric keypad; the [NumLock] key; the k key; the [PrintScreen] key; and the divide
(/) and [Enter] keys in the numeric keypad. The extended-key flag is set if the key
is an extended key.)
To make life easier for anyone using TWinShortcut, it has two properties, Hotkey
and HotkeyModifiers, that are assignment-compatible with the properties of THotkey.
The code following the call to GetHotkey converts the modifier flags from the form
used by GetHotkey to the form used by the HotkeyModifiers property and by the
THotkey component. The modifier constants used with GetHotkey and SetHotkey
(HOTKEYF_ALT, HOTKEYF_CONTROL, HOTKEYF_SHIFT and HOTKEYF_EXT) are declared in the
CommCtrl.pas unit. The constants used with the THotkey component's Modifiers
property (hkAlt, hkCtrl, hkShift, and hkExt) are declared in the ComCtrls.pas unit.
Creating or saving a modified shortcut is handled by the TWinShortcut's public
SaveShortcut method. SaveShortcut begins by calling PutPropertiesToShortcut. This
method calls the IShellLink put method for each property to assign the current
value of the TWinShortcut properties to the corresponding shortcut properties. The
only part of this process that is complex is converting the HotkeyModifiers
property to the form required by the SetHotkey method. The series of if statements
set the appropriate bits in the byte variable HotKeyMods. SetHotkey is called with
a single-word parameter that's constructed by shifting HotKeyMods left eight bits
to place it in the high-order byte of the word and adding the value of the HotKey
property. Next, a call to GetFullShortcutPath returns the path to the link file.
Finally, the IPersistFile Save method is called with the full path to the link file
as a parameter. Again, the path must be cast first to a WideString, and then to a
PWideChar.
Conclusion
You can create and modify any Windows shortcut using the methods of the IShellLink
and IPersistFile interfaces implemented by the ShellLink object. Although this
article doesn't cover every method in detail, it should give you everything you
need for most shortcut operations. For more detailed information about the
interfaces or any of their methods, consult the Win32 Programmers Reference online
help file that's installed with Delphi 5.
Listing One - TWinShortcut
52 unit WinShortcut;
53
54 interface
55
56 uses
57 Windows, Messages, SysUtils, Classes, Graphics, Controls,
58 Forms, Dialogs, ComObj, ShlObj, ShellAPI, ActiveX, Menus,
59 ComCtrls;
60
61 type
62 TWinShortcut = class(TComponent)
63 private
64 { Private declarations. }
65 FArguments: string;
66 FDescription: string;
67 FHotkey: Word;
68 FHotKeyModifiers: THKModifiers;
69 FIconFile: string;
70 FIconIndex: Integer;
71 FShortcutFileName: string;
72 FShortcutPath: string;
73 FRunWindow: Integer;
74 FSpecialFolder: Integer;
75 FTarget: string;
76 FWorkingDirectory: string;
77 protected
78 { Protected declarations. }
79 FPersistFile: IPersistFile;
80 FShellLink: IShellLink;
81 function GetFullShortcutPath: string;
82 procedure GetPropertiesFromShortcut;
83 function GetSpecialFolderPath: string;
84 procedure PutPropertiesToShortcut;
85 procedure SetShortcutFileName(Value: string);
86 procedure SetShortcutPath(Value: string);
87 procedure SetSpecialFolder(Value: Integer);
88 public
89 { Public declarations. }
90 constructor Create(AOwner: TComponent); override;
91 destructor Destroy; override;
92 procedure OpenShortcut;
93 procedure SaveShortcut;
94 published
95 { Published declarations. }
96 property Arguments: string
97 read FArguments write FArguments;
98 property Description: string
99 read FDescription write FDescription;
100 property HotKey: Word read FHotkey write FHotkey;
101 property HotKeyModifiers: THKModifiers
102 read FHotKeyModifiers write FHotKeyModifiers;
103 property IconFile: string
104 read FIconFile write FIconFile;
105 property IconIndex: Integer
106 read FIconIndex write FIconIndex;
107 property RunWindow: Integer
108 read FRunWindow write FRunWindow;
109 property Target: string read FTarget write FTarget;
110 property ShortcutFileName: string
111 read FShortcutFileName write SetShortcutFileName;
112 property ShortcutPath: string
113 read FShortcutPath write SetShortcutPath;
114 property SpecialFolder: Integer
115 read FSpecialFolder write SetSpecialFolder;
116 property WorkingDirectory: string
117 read FWorkingDirectory write FWorkingDirectory;
118 end;
119
120 procedure register;
121
122 implementation
123
124 uses CommCtrl;
125
126 const
127 Backslash = '\';
128 LinkExtension = '.LNK';
129
130 { ********* Constructor and Destructor ********** }
131
132 constructor TWinShortcut.Create(AOwner: TComponent);
133 begin
134 { Create the ShellLink object and get an IShellLink and
135 an IPersistFile reference to it. }
136 inherited;
137 FShellLink :=
138 CreateComObject(CLSID_ShellLink) as IShellLink;
139 FPersistFile := FShellLink as IPersistFile;
140 end;
141
142 destructor TWinShortcut.Destroy;
143 begin
144 { Free the ShellLink object. }
145 FShellLink := nil;
146 FPersistFile := nil;
147 inherited;
148 end;
149
150 { ********* Property Getter and Setter Methods ********** }
151
152 procedure TWinShortcut.SetShortcutFileName(Value: string);
153 begin
154 FShortcutFileName := Value;
155 { If the file name does not end with the .LNK extension,
156 add the extension. }
157 if CompareText(ExtractFileExt(FShortcutFileName),
158 LinkExtension) <> 0 then
159 FShortcutFileName := FShortcutFileName + LinkExtension;
160 end;
161
162 procedure TWinShortcut.SetShortcutPath(Value: string);
163 begin
164 FShortcutPath := Value;
165 { Make sure the path ends with a backslash. }
166 if Copy(FShortcutPath,
167 Length(FShortcutPath), 1) <> Backslash then
168 FShortcutPath := FShortcutPath + Backslash;
169 end;
170
171 procedure TWinShortcut.SetSpecialFolder(Value: Integer);
172 begin
173 FSpecialFolder := Value;
174 { Clear the ShortcutPath when a value is assinged to the
175 SpecialFolder property. The SpecialFolder property will
176 not be used to get the path to the link file if the
177 ShortcutPath property is not null. }
178 FShortcutPath := '';
179 end;
180
181 { ********* Custom Methods ********** }
182
183 function TWinShortcut.GetFullShortcutPath: string;
184 { Gets the path to the shortcut file. If the ShortcutPath
185 property is null, the path comes from the SpecialFolder
186 property. }
187 begin
188 if FShortcutPath = '' then
189 Result := GetSpecialFolderPath
190 else
191 Result := FShortcutPath;
192 Result := Result + FShortcutFileName;
193 end;
194
195 procedure TWinShortcut.GetPropertiesFromShortcut;
196 { Calls the appropriate IShellLink method to get the value
197 of each property of the link and assign that value to the
198 corresponding property of this component. }
199 var
200 CharStr: array[0..MAX_PATH] of Char;
201 WinFindData: TWin32FindData;
202 RunWin: Integer;
203 HotKeyWord: Word;
204 HotKeyMod: Byte;
205 begin
206 OleCheck(FShellLink.GetPath(CharStr, MAX_PATH,
207 WinFindData, SLGP_UNCPRIORITY));
208 Target := CharStr;
209 OleCheck(FShellLink.GetArguments(CharStr, MAX_PATH));
210 Arguments := CharStr;
211 OleCheck(FShellLink.GetDescription(CharStr, MAX_PATH));
212 Description := CharStr;
213 OleCheck(
214 FShellLink.GetWorkingDirectory(CharStr, MAX_PATH));
215 WorkingDirectory := CharStr;
216 OleCheck(FShellLink.GetIconLocation(CharStr, MAX_PATH,
217 FIconIndex));
218 IconFile := CharStr;
219 OleCheck(FShellLink.GetShowCmd(RunWin));
220 RunWindow := RunWin;
221 OleCheck(FShellLink.GetHotkey(HotKeyWord));
222 { Extract the HotKey and Modifier properties. }
223 HotKey := HotKeyWord;
224 HotKeyMod := Hi(HotKeyWord);
225 if (HotKeyMod and HOTKEYF_ALT) = HOTKEYF_ALT then
226 Include(FHotKeyModifiers, hkAlt);
227 if (HotKeyMod and HOTKEYF_CONTROL) = HOTKEYF_CONTROL then
228 Include(FHotKeyModifiers, hkCtrl);
229 if (HotKeyMod and HOTKEYF_SHIFT) = HOTKEYF_SHIFT then
230 Include(FHotKeyModifiers, hkShift);
231 if (HotKeyMod and HOTKEYF_EXT) = HOTKEYF_EXT then
232 Include(FHotKeyModifiers, hkExt);
233 end;
234
235 function TWinShortcut.GetSpecialFolderPath: string;
236 { Returns the full path to the special folder specified in
237 the SpecialFolder property. A backslash is appended to
238 the path. }
239 var
240 ItemIdList: PItemIdList;
241 CharStr: array[0..MAX_PATH] of Char;
242 begin
243 OleCheck(ShGetSpecialFolderLocation(0, FSpecialFolder,
244 ItemIdList));
245 if ShGetPathFromIdList(ItemIdList, CharStr) then
246 begin
247 Result := CharStr;
248 Result := Result + Backslash;
249 end; // if
250 end;
251
252 procedure TWinShortcut.OpenShortcut;
253 { Opens the shortcut and loads its properties into the
254 component properties. }
255 var
256 FullPath: string;
257 begin
258 FullPath := GetFullShortcutPath;
259 OleCheck(FPersistFile.Load(PWideChar(WideString(
260 FullPath)), STGM_READWRITE));
261 GetPropertiesFromShortcut;
262 end;
263
264 procedure TWinShortcut.PutPropertiesToShortcut;
265 { Calls the appropriate IShellLink method to assign the
266 value of each of the components properties to the
267 corresponding property of the shortcut. }
268 var
269 HotKeyMods: Byte;
270 begin
271 HotKeyMods := 0;
272 OleCheck(FShellLink.SetPath(PChar(FTarget)));
273 OleCheck(FShellLink.SetIconLocation(PChar(FIconFile),
274 FIconIndex));
275 OleCheck(FShellLink.SetDescription(PChar(FDescription)));
276 OleCheck(FShellLink.SetWorkingDirectory(PChar(
277 FWorkingDirectory)));
278 OleCheck(FShellLink.SetArguments(PChar(FArguments)));
279 OleCheck(FShellLink.SetShowCmd(FRunWindow));
280 if hkShift in FHotKeyModifiers then
281 HotKeyMods := HotKeyMods or HOTKEYF_SHIFT;
282 if hkAlt in FHotKeyModifiers then
283 HotKeyMods := HotKeyMods or HOTKEYF_ALT;
284 if hkCtrl in FHotKeyModifiers then
285 HotKeyMods := HotKeyMods or HOTKEYF_CONTROL;
286 if hkExt in FHotKeyModifiers then
287 HotKeyMods := HotKeyMods or HOTKEYF_EXT;
288 OleCheck(
289 FShellLink.SetHotkey((HotKeyMods shl 8) + HotKey));
290 end;
291
292 procedure TWinShortcut.SaveShortcut;
293 { Copies the component properties to the shortcut
294 and saves it. }
295 var
296 FullPath: string;
297 begin
298 PutPropertiesToShortcut;
299 FullPath := GetFullShortcutPath;
300 OleCheck(FPersistFile.Save(PWideChar(
301 WideString(FullPath)), True));
302 end;
303
304 procedure register;
305 begin
306 RegisterComponents('DI', [TWinShortcut]);
307 end;
308
309 end.
End Listing One
Component Download: http://www.baltsoft.com/files/dkb/attachment/A_Quick_Way_to_Shortcuts.zip
|