Mirror

A Quick Way to Shortcuts (Views: 3381)


Problem/Question/Abstract:

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.

IShellLinkA = interface(IUnknown) { sl. }
  [SID_IShellLinkA]
  function GetPath(pszFile: PAnsiChar; cchMaxPath: Integer;
    var pfd: TWin32FindData; fFlags: DWORD): HResult;
    stdcall;
  function GetIDList(var ppidl: PItemIDList): HResult;
    stdcall;
  function SetIDList(pidl: PItemIDList): HResult; stdcall;
  function GetDescription(pszName: PAnsiChar;
    cchMaxName: Integer): HResult; stdcall;
  function SetDescription(pszName: PAnsiChar): HResult;
    stdcall;
  function GetWorkingDirectory(pszDir: PAnsiChar;
    cchMaxPath: Integer): HResult; stdcall;
  function SetWorkingDirectory(pszDir: PAnsiChar): HResult;
    stdcall;
  function GetArguments(pszArgs: PAnsiChar;
    cchMaxPath: Integer): HResult; stdcall;
  function SetArguments(pszArgs: PAnsiChar): HResult;
    stdcall;
  function GetHotkey(var pwHotkey: Word): HResult; stdcall;
  function SetHotkey(wHotkey: Word): HResult; stdcall;
  function GetShowCmd(out piShowCmd: Integer): HResult;
    stdcall;
  function SetShowCmd(iShowCmd: Integer): HResult; stdcall;
  function GetIconLocation(pszIconPath: PAnsiChar;
    cchIconPath: Integer; out piIcon: Integer): HResult;
    stdcall;
  function SetIconLocation(pszIconPath: PAnsiChar;
    iIcon: Integer): HResult; stdcall;
  function SetRelativePath(pszPathRel: PAnsiChar;
    dwReserved: DWORD): HResult; stdcall;
  function Resolve(Wnd: HWND; fFlags: DWORD): HResult;
    stdcall;
  function SetPath(pszFile: PAnsiChar): HResult; stdcall;
end;
Figure 1: The IShellLink interface.

IPersistFile = interface(IPersist)
  ['{ 0000010B-0000-0000-C000-000000000046 }']
  function IsDirty: HResult; stdcall;
  function Load(pszFileName: POleStr; dwMode: Longint):
    HResult; stdcall;
  function Save(pszFileName: POleStr; fRemember: BOOL):
    HResult; stdcall;
  function SaveCompleted(pszFileName: POleStr): HResult;
    stdcall;
  function GetCurFile(out pszFileName: POleStr): HResult;
    stdcall;
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:

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
unit WinShortcut;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls,
  Forms, Dialogs, ComObj, ShlObj, ShellAPI, ActiveX, Menus,
  ComCtrls;

type
  TWinShortcut = class(TComponent)
  private
    { Private declarations. }
    FArguments: string;
    FDescription: string;
    FHotkey: Word;
    FHotKeyModifiers: THKModifiers;
    FIconFile: string;
    FIconIndex: Integer;
    FShortcutFileName: string;
    FShortcutPath: string;
    FRunWindow: Integer;
    FSpecialFolder: Integer;
    FTarget: string;
    FWorkingDirectory: string;
  protected
    { Protected declarations. }
    FPersistFile: IPersistFile;
    FShellLink: IShellLink;
    function GetFullShortcutPath: string;
    procedure GetPropertiesFromShortcut;
    function GetSpecialFolderPath: string;
    procedure PutPropertiesToShortcut;
    procedure SetShortcutFileName(Value: string);
    procedure SetShortcutPath(Value: string);
    procedure SetSpecialFolder(Value: Integer);
  public
    { Public declarations. }
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    procedure OpenShortcut;
    procedure SaveShortcut;
  published
    { Published declarations. }
    property Arguments: string
      read FArguments write FArguments;
    property Description: string
      read FDescription write FDescription;
    property HotKey: Word read FHotkey write FHotkey;
    property HotKeyModifiers: THKModifiers
      read FHotKeyModifiers write FHotKeyModifiers;
    property IconFile: string
      read FIconFile write FIconFile;
    property IconIndex: Integer
      read FIconIndex write FIconIndex;
    property RunWindow: Integer
      read FRunWindow write FRunWindow;
    property Target: string read FTarget write FTarget;
    property ShortcutFileName: string
      read FShortcutFileName write SetShortcutFileName;
    property ShortcutPath: string
      read FShortcutPath write SetShortcutPath;
    property SpecialFolder: Integer
      read FSpecialFolder write SetSpecialFolder;
    property WorkingDirectory: string
      read FWorkingDirectory write FWorkingDirectory;
  end;

procedure Register;

implementation

uses CommCtrl;

const
  Backslash = '\';
  LinkExtension = '.LNK';

  { ********* Constructor and Destructor ********** }

constructor TWinShortcut.Create(AOwner: TComponent);
begin
  { Create the ShellLink object and get an IShellLink and
   an IPersistFile reference to it. }
  inherited;
  FShellLink :=
    CreateComObject(CLSID_ShellLink) as IShellLink;
  FPersistFile := FShellLink as IPersistFile;
end;

destructor TWinShortcut.Destroy;
begin
  { Free the ShellLink object. }
  FShellLink := nil;
  FPersistFile := nil;
  inherited;
end;

{ ********* Property Getter and Setter Methods ********** }

procedure TWinShortcut.SetShortcutFileName(Value: string);
begin
  FShortcutFileName := Value;
  { If the file name does not end with the .LNK extension,
   add the extension. }
  if CompareText(ExtractFileExt(FShortcutFileName),
    LinkExtension) <> 0 then
    FShortcutFileName := FShortcutFileName + LinkExtension;
end;

procedure TWinShortcut.SetShortcutPath(Value: string);
begin
  FShortcutPath := Value;
  { Make sure the path ends with a backslash. }
  if Copy(FShortcutPath,
    Length(FShortcutPath), 1) <> Backslash then
    FShortcutPath := FShortcutPath + Backslash;
end;

procedure TWinShortcut.SetSpecialFolder(Value: Integer);
begin
  FSpecialFolder := Value;
  { Clear the ShortcutPath when a value is assinged to the
   SpecialFolder property. The SpecialFolder property will
   not be used to get the path to the link file if the
   ShortcutPath property is not null. }
  FShortcutPath := '';
end;

{ ********* Custom Methods ********** }

function TWinShortcut.GetFullShortcutPath: string;
{ Gets the path to the shortcut file. If the ShortcutPath
  property is null, the path comes from the SpecialFolder
  property. }
begin
  if FShortcutPath = '' then
    Result := GetSpecialFolderPath
  else
    Result := FShortcutPath;
  Result := Result + FShortcutFileName;
end;

procedure TWinShortcut.GetPropertiesFromShortcut;
{ Calls the appropriate IShellLink method to get the value
  of each property of the link and assign that value to the
  corresponding property of this component. }
var
  CharStr: array[0..MAX_PATH] of Char;
  WinFindData: TWin32FindData;
  RunWin: Integer;
  HotKeyWord: Word;
  HotKeyMod: Byte;
begin
  OleCheck(FShellLink.GetPath(CharStr, MAX_PATH,
    WinFindData, SLGP_UNCPRIORITY));
  Target := CharStr;
  OleCheck(FShellLink.GetArguments(CharStr, MAX_PATH));
  Arguments := CharStr;
  OleCheck(FShellLink.GetDescription(CharStr, MAX_PATH));
  Description := CharStr;
  OleCheck(
    FShellLink.GetWorkingDirectory(CharStr, MAX_PATH));
  WorkingDirectory := CharStr;
  OleCheck(FShellLink.GetIconLocation(CharStr, MAX_PATH,
    FIconIndex));
  IconFile := CharStr;
  OleCheck(FShellLink.GetShowCmd(RunWin));
  RunWindow := RunWin;
  OleCheck(FShellLink.GetHotkey(HotKeyWord));
  { Extract the HotKey and Modifier properties. }
  HotKey := HotKeyWord;
  HotKeyMod := Hi(HotKeyWord);
  if (HotKeyMod and HOTKEYF_ALT) = HOTKEYF_ALT then
    Include(FHotKeyModifiers, hkAlt);
  if (HotKeyMod and HOTKEYF_CONTROL) = HOTKEYF_CONTROL then
    Include(FHotKeyModifiers, hkCtrl);
  if (HotKeyMod and HOTKEYF_SHIFT) = HOTKEYF_SHIFT then
    Include(FHotKeyModifiers, hkShift);
  if (HotKeyMod and HOTKEYF_EXT) = HOTKEYF_EXT then
    Include(FHotKeyModifiers, hkExt);
end;

function TWinShortcut.GetSpecialFolderPath: string;
{ Returns the full path to the special folder specified in
  the SpecialFolder property. A backslash is appended to
  the path. }
var
  ItemIdList: PItemIdList;
  CharStr: array[0..MAX_PATH] of Char;
begin
  OleCheck(ShGetSpecialFolderLocation(0, FSpecialFolder,
    ItemIdList));
  if ShGetPathFromIdList(ItemIdList, CharStr) then
  begin
    Result := CharStr;
    Result := Result + Backslash;
  end; // if
end;

procedure TWinShortcut.OpenShortcut;
{ Opens the shortcut and loads its properties into the
  component properties. }
var
  FullPath: string;
begin
  FullPath := GetFullShortcutPath;
  OleCheck(FPersistFile.Load(PWideChar(WideString(
    FullPath)), STGM_READWRITE));
  GetPropertiesFromShortcut;
end;

procedure TWinShortcut.PutPropertiesToShortcut;
{ Calls the appropriate IShellLink method to assign the
  value of each of the components properties to the
  corresponding property of the shortcut. }
var
  HotKeyMods: Byte;
begin
  HotKeyMods := 0;
  OleCheck(FShellLink.SetPath(PChar(FTarget)));
  OleCheck(FShellLink.SetIconLocation(PChar(FIconFile),
    FIconIndex));
  OleCheck(FShellLink.SetDescription(PChar(FDescription)));
  OleCheck(FShellLink.SetWorkingDirectory(PChar(
    FWorkingDirectory)));
  OleCheck(FShellLink.SetArguments(PChar(FArguments)));
  OleCheck(FShellLink.SetShowCmd(FRunWindow));
  if hkShift in FHotKeyModifiers then
    HotKeyMods := HotKeyMods or HOTKEYF_SHIFT;
  if hkAlt in FHotKeyModifiers then
    HotKeyMods := HotKeyMods or HOTKEYF_ALT;
  if hkCtrl in FHotKeyModifiers then
    HotKeyMods := HotKeyMods or HOTKEYF_CONTROL;
  if hkExt in FHotKeyModifiers then
    HotKeyMods := HotKeyMods or HOTKEYF_EXT;
  OleCheck(
    FShellLink.SetHotkey((HotKeyMods shl 8) + HotKey));
end;

procedure TWinShortcut.SaveShortcut;
{ Copies the component properties to the shortcut
  and saves it. }
var
  FullPath: string;
begin
  PutPropertiesToShortcut;
  FullPath := GetFullShortcutPath;
  OleCheck(FPersistFile.Save(PWideChar(
    WideString(FullPath)), True));
end;

procedure Register;
begin
  RegisterComponents('DI', [TWinShortcut]);
end;

end.
End Listing One


Component Download: http://www.baltsoft.com/files/dkb/attachment/A_Quick_Way_to_Shortcuts.zip

<< Back to main page