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
2 IShellLinkA = interface(IUnknown) { sl. }
3 [SID_IShellLinkA]
4 function GetPath(pszFile: PAnsiChar; cchMaxPath: Integer;
5 var pfd: TWin32FindData; fFlags: DWORD): HResult;
6 stdcall;
7 function GetIDList(var ppidl: PItemIDList): HResult;
8 stdcall;
9 function SetIDList(pidl: PItemIDList): HResult; stdcall;
10 function GetDescription(pszName: PAnsiChar;
11 cchMaxName: Integer): HResult; stdcall;
12 function SetDescription(pszName: PAnsiChar): HResult;
13 stdcall;
14 function GetWorkingDirectory(pszDir: PAnsiChar;
15 cchMaxPath: Integer): HResult; stdcall;
16 function SetWorkingDirectory(pszDir: PAnsiChar): HResult;
17 stdcall;
18 function GetArguments(pszArgs: PAnsiChar;
19 cchMaxPath: Integer): HResult; stdcall;
20 function SetArguments(pszArgs: PAnsiChar): HResult;
21 stdcall;
22 function GetHotkey(var pwHotkey: Word): HResult; stdcall;
23 function SetHotkey(wHotkey: Word): HResult; stdcall;
24 function GetShowCmd(out piShowCmd: Integer): HResult;
25 stdcall;
26 function SetShowCmd(iShowCmd: Integer): HResult; stdcall;
27 function GetIconLocation(pszIconPath: PAnsiChar;
28 cchIconPath: Integer; out piIcon: Integer): HResult;
29 stdcall;
30 function SetIconLocation(pszIconPath: PAnsiChar;
31 iIcon: Integer): HResult; stdcall;
32 function SetRelativePath(pszPathRel: PAnsiChar;
33 dwReserved: DWORD): HResult; stdcall;
34 function Resolve(Wnd: HWND; fFlags: DWORD): HResult;
35 stdcall;
36 function SetPath(pszFile: PAnsiChar): HResult; stdcall;
37 end;
38 //Figure 1: The IShellLink interface.
39
40 IPersistFile = interface(IPersist)
41 ['{ 0000010B-0000-0000-C000-000000000046 }']
42 function IsDirty: HResult; stdcall;
43 function Load(pszFileName: POleStr; dwMode: Longint):
44 HResult; stdcall;
45 function Save(pszFileName: POleStr; fRemember: BOOL):
46 HResult; stdcall;
47 function SaveCompleted(pszFileName: POleStr): HResult;
48 stdcall;
49 function GetCurFile(out pszFileName: POleStr): HResult;
50 stdcall;
51 end;
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:
52
53 FShellLink := CreateComObject(CLSID_ShellLink) as IShellLink;
54 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:
55
56 OleCheck(FPersistFile.Load(PWideChar(WideString(FullPath)),
57 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
58 unit WinShortcut;
59
60 interface
61
62 uses
63 Windows, Messages, SysUtils, Classes, Graphics, Controls,
64 Forms, Dialogs, ComObj, ShlObj, ShellAPI, ActiveX, Menus,
65 ComCtrls;
66
67 type
68 TWinShortcut = class(TComponent)
69 private
70 { Private declarations. }
71 FArguments: string;
72 FDescription: string;
73 FHotkey: Word;
74 FHotKeyModifiers: THKModifiers;
75 FIconFile: string;
76 FIconIndex: Integer;
77 FShortcutFileName: string;
78 FShortcutPath: string;
79 FRunWindow: Integer;
80 FSpecialFolder: Integer;
81 FTarget: string;
82 FWorkingDirectory: string;
83 protected
84 { Protected declarations. }
85 FPersistFile: IPersistFile;
86 FShellLink: IShellLink;
87 function GetFullShortcutPath: string;
88 procedure GetPropertiesFromShortcut;
89 function GetSpecialFolderPath: string;
90 procedure PutPropertiesToShortcut;
91 procedure SetShortcutFileName(Value: string);
92 procedure SetShortcutPath(Value: string);
93 procedure SetSpecialFolder(Value: Integer);
94 public
95 { Public declarations. }
96 constructor Create(AOwner: TComponent); override;
97 destructor Destroy; override;
98 procedure OpenShortcut;
99 procedure SaveShortcut;
100 published
101 { Published declarations. }
102 property Arguments: string
103 read FArguments write FArguments;
104 property Description: string
105 read FDescription write FDescription;
106 property HotKey: Word read FHotkey write FHotkey;
107 property HotKeyModifiers: THKModifiers
108 read FHotKeyModifiers write FHotKeyModifiers;
109 property IconFile: string
110 read FIconFile write FIconFile;
111 property IconIndex: Integer
112 read FIconIndex write FIconIndex;
113 property RunWindow: Integer
114 read FRunWindow write FRunWindow;
115 property Target: string read FTarget write FTarget;
116 property ShortcutFileName: string
117 read FShortcutFileName write SetShortcutFileName;
118 property ShortcutPath: string
119 read FShortcutPath write SetShortcutPath;
120 property SpecialFolder: Integer
121 read FSpecialFolder write SetSpecialFolder;
122 property WorkingDirectory: string
123 read FWorkingDirectory write FWorkingDirectory;
124 end;
125
126 procedure register;
127
128 implementation
129
130 uses CommCtrl;
131
132 const
133 Backslash = '\';
134 LinkExtension = '.LNK';
135
136 { ********* Constructor and Destructor ********** }
137
138 constructor TWinShortcut.Create(AOwner: TComponent);
139 begin
140 { Create the ShellLink object and get an IShellLink and
141 an IPersistFile reference to it. }
142 inherited;
143 FShellLink :=
144 CreateComObject(CLSID_ShellLink) as IShellLink;
145 FPersistFile := FShellLink as IPersistFile;
146 end;
147
148 destructor TWinShortcut.Destroy;
149 begin
150 { Free the ShellLink object. }
151 FShellLink := nil;
152 FPersistFile := nil;
153 inherited;
154 end;
155
156 { ********* Property Getter and Setter Methods ********** }
157
158 procedure TWinShortcut.SetShortcutFileName(Value: string);
159 begin
160 FShortcutFileName := Value;
161 { If the file name does not end with the .LNK extension,
162 add the extension. }
163 if CompareText(ExtractFileExt(FShortcutFileName),
164 LinkExtension) <> 0 then
165 FShortcutFileName := FShortcutFileName + LinkExtension;
166 end;
167
168 procedure TWinShortcut.SetShortcutPath(Value: string);
169 begin
170 FShortcutPath := Value;
171 { Make sure the path ends with a backslash. }
172 if Copy(FShortcutPath,
173 Length(FShortcutPath), 1) <> Backslash then
174 FShortcutPath := FShortcutPath + Backslash;
175 end;
176
177 procedure TWinShortcut.SetSpecialFolder(Value: Integer);
178 begin
179 FSpecialFolder := Value;
180 { Clear the ShortcutPath when a value is assinged to the
181 SpecialFolder property. The SpecialFolder property will
182 not be used to get the path to the link file if the
183 ShortcutPath property is not null. }
184 FShortcutPath := '';
185 end;
186
187 { ********* Custom Methods ********** }
188
189 function TWinShortcut.GetFullShortcutPath: string;
190 { Gets the path to the shortcut file. If the ShortcutPath
191 property is null, the path comes from the SpecialFolder
192 property. }
193 begin
194 if FShortcutPath = '' then
195 Result := GetSpecialFolderPath
196 else
197 Result := FShortcutPath;
198 Result := Result + FShortcutFileName;
199 end;
200
201 procedure TWinShortcut.GetPropertiesFromShortcut;
202 { Calls the appropriate IShellLink method to get the value
203 of each property of the link and assign that value to the
204 corresponding property of this component. }
205 var
206 CharStr: array[0..MAX_PATH] of Char;
207 WinFindData: TWin32FindData;
208 RunWin: Integer;
209 HotKeyWord: Word;
210 HotKeyMod: Byte;
211 begin
212 OleCheck(FShellLink.GetPath(CharStr, MAX_PATH,
213 WinFindData, SLGP_UNCPRIORITY));
214 Target := CharStr;
215 OleCheck(FShellLink.GetArguments(CharStr, MAX_PATH));
216 Arguments := CharStr;
217 OleCheck(FShellLink.GetDescription(CharStr, MAX_PATH));
218 Description := CharStr;
219 OleCheck(
220 FShellLink.GetWorkingDirectory(CharStr, MAX_PATH));
221 WorkingDirectory := CharStr;
222 OleCheck(FShellLink.GetIconLocation(CharStr, MAX_PATH,
223 FIconIndex));
224 IconFile := CharStr;
225 OleCheck(FShellLink.GetShowCmd(RunWin));
226 RunWindow := RunWin;
227 OleCheck(FShellLink.GetHotkey(HotKeyWord));
228 { Extract the HotKey and Modifier properties. }
229 HotKey := HotKeyWord;
230 HotKeyMod := Hi(HotKeyWord);
231 if (HotKeyMod and HOTKEYF_ALT) = HOTKEYF_ALT then
232 Include(FHotKeyModifiers, hkAlt);
233 if (HotKeyMod and HOTKEYF_CONTROL) = HOTKEYF_CONTROL then
234 Include(FHotKeyModifiers, hkCtrl);
235 if (HotKeyMod and HOTKEYF_SHIFT) = HOTKEYF_SHIFT then
236 Include(FHotKeyModifiers, hkShift);
237 if (HotKeyMod and HOTKEYF_EXT) = HOTKEYF_EXT then
238 Include(FHotKeyModifiers, hkExt);
239 end;
240
241 function TWinShortcut.GetSpecialFolderPath: string;
242 { Returns the full path to the special folder specified in
243 the SpecialFolder property. A backslash is appended to
244 the path. }
245 var
246 ItemIdList: PItemIdList;
247 CharStr: array[0..MAX_PATH] of Char;
248 begin
249 OleCheck(ShGetSpecialFolderLocation(0, FSpecialFolder,
250 ItemIdList));
251 if ShGetPathFromIdList(ItemIdList, CharStr) then
252 begin
253 Result := CharStr;
254 Result := Result + Backslash;
255 end; // if
256 end;
257
258 procedure TWinShortcut.OpenShortcut;
259 { Opens the shortcut and loads its properties into the
260 component properties. }
261 var
262 FullPath: string;
263 begin
264 FullPath := GetFullShortcutPath;
265 OleCheck(FPersistFile.Load(PWideChar(WideString(
266 FullPath)), STGM_READWRITE));
267 GetPropertiesFromShortcut;
268 end;
269
270 procedure TWinShortcut.PutPropertiesToShortcut;
271 { Calls the appropriate IShellLink method to assign the
272 value of each of the components properties to the
273 corresponding property of the shortcut. }
274 var
275 HotKeyMods: Byte;
276 begin
277 HotKeyMods := 0;
278 OleCheck(FShellLink.SetPath(PChar(FTarget)));
279 OleCheck(FShellLink.SetIconLocation(PChar(FIconFile),
280 FIconIndex));
281 OleCheck(FShellLink.SetDescription(PChar(FDescription)));
282 OleCheck(FShellLink.SetWorkingDirectory(PChar(
283 FWorkingDirectory)));
284 OleCheck(FShellLink.SetArguments(PChar(FArguments)));
285 OleCheck(FShellLink.SetShowCmd(FRunWindow));
286 if hkShift in FHotKeyModifiers then
287 HotKeyMods := HotKeyMods or HOTKEYF_SHIFT;
288 if hkAlt in FHotKeyModifiers then
289 HotKeyMods := HotKeyMods or HOTKEYF_ALT;
290 if hkCtrl in FHotKeyModifiers then
291 HotKeyMods := HotKeyMods or HOTKEYF_CONTROL;
292 if hkExt in FHotKeyModifiers then
293 HotKeyMods := HotKeyMods or HOTKEYF_EXT;
294 OleCheck(
295 FShellLink.SetHotkey((HotKeyMods shl 8) + HotKey));
296 end;
297
298 procedure TWinShortcut.SaveShortcut;
299 { Copies the component properties to the shortcut
300 and saves it. }
301 var
302 FullPath: string;
303 begin
304 PutPropertiesToShortcut;
305 FullPath := GetFullShortcutPath;
306 OleCheck(FPersistFile.Save(PWideChar(
307 WideString(FullPath)), True));
308 end;
309
310 procedure register;
311 begin
312 RegisterComponents('DI', [TWinShortcut]);
313 end;
314
315 end.
End Listing One
Component Download: www.baltsoft.com/files/dkb/attachment/A_Quick_Way_to_Shortcuts.zip
|