Home | Chi sono | Contattami
 

Progr. lineare

Delphi
 
Componenti
  Database
 
Miei articoli

Windows

Miei articoli 

 

Esempio di Shell Extension: Context Menu Handler per i file .dfm


Creare una Shell Extension non è sicuramente la cosa più facile da fare soprattutto per le conoscenze tecniche che occorre acquisire. Per chi volesse risparmiarsi un bel pò di tempo, la soluzione è sicuramente la libreria EasyNSE, VCL Open Source realizzata in Delphi e  veramente di ottima fattura. Tuttavia è interessante analizzare come creare da zero questo tipo di applicativi. In questo articolo vado a riportare un articolo di vecchia data (Gennaio 1998) pubblicato sul sito www.delphibydesign.com che oramai non esiste più. Nonostante si faccia riferimento a Delphi 3 ed ai sistemi di quel tempo (ossia ovviamente Windows 95 e Windows NT 4.0), il contenuto è tuttora valido (sorgenti di esempio compilati con successo con Delphi 7 e risultato correttamente eseguito su Windows XP). Si tratta di un Context Menù Handler relativo ai file .dfm (Delphi Form): in pratica viene aggiunta una voce al menù contestuale (pulsante destro del mouse) in corrispondenza dei file .dfm; al click su tale voce viene aperto un editor che visualizza il contenuto del dfm. Nel seguito andrò a riportare l'articolo nella sua versione originale.

 

Shell Extensions in Delphi 3

by Ray Konopka
December/January 1998, Vol. 8, No. 5

The Explorer is the central user interface metaphor in Windows, and your Delphi applications can embed special hooks right into Explorer.

Welcome to the third part of my series on COM support in Delphi 3. In part one, I introduced object interfaces and explained how they are defined using the interface keyword. In part two, I described how interfaces affect the creation of automation servers and controllers in Delphi 3. In this issue, I will describe how to extend the Windows Explorer shell by creating custom COM servers that implement specific, you guessed it, interfaces.

What Are Shell Extensions?

Shell extensions, as the term implies, provide a way to extend the abilities of the Windows 95 or NT 4.0 Explorer. There are several different types of shell extensions, which are categorized into two groups. While Table 1 summarizes the extensions that are associated with a particular file type, Table 2 lists the ones that are associated with file operations.


 

A shell extension is implemented as a COM object that resides inside an in-process COM server. Recall from part one that an in-process server is just a DLL, and that Delphi 3 calls them ActiveX Libraries. The Windows Explorer accesses the shell extension through interfaces. Consider the following: In the example that I presented in the last issue, the automation controller called the methods defined in the IViewer interface supported by the automation server. For shell extensions, the Windows Explorer itself functions as the controller and calls the methods in the COM object by using an interface reference.

Since Windows itself is calling the methods of a shell extension, the interfaces that Windows uses for accessing shell extensions are predefined. So, instead of defining new methods and properties of an interface like we do when creating an automation server, creating a shell extension involves implementing the appropriate methods defined by the shell extension interfaces. Unfortunately, actually implementing the methods can be a challenge. We’ll cover the details of these interfaces shortly.

Creating a Shell Extension

Unlike automation objects, there is no expert defined in Delphi that makes it easy to create a shell extension COM object. In fact, we have to code everything by hand. To demonstrate the process, we’ll construct a new context menu handler that displays a “View as Text” menu item when the user clicks on a Delphi form file using mouse button 2. If the user selects this menu item, the form file viewer application that I presented in part two is used to display the text representation of the selected form file.

To create a shell extension, you must first create the COM server that will hold the extension, or handler. Select the File|New menu item and then switch to the ActiveX page. Select the ActiveX Library icon and then click the OK button to instruct Delphi to create a new project file for the library. Next, create a new unit to contain the implementation of the shell extension. Select the File|New menu item and then double-click on the Unit icon.

Once the new unit is created, we need to declare the COM object class—that is, we must declare a new class that supports the particular interfaces of the shell extension. For a context menu, the class must support the IShellExtInit and IContextMenu interfaces. Of course, each of the methods in these interfaces must be defined in the implementation section of the unit.

Listing 1 is the source code for the FormView ActiveX library, and Listing 2 is the complete source code for the FormViewImpl unit, which implements the TFormViewContextMenu class. As you can see, this class descends from TComObject, which is defined in the ComObj unit, and lists IShellExtInit and IContextMenu as its supported interfaces. Recall that all interfaces are derived from IUnknown, so this class also supports the IUnknown interface. In addition, all interfaces listed in TComObject are supported. However, for our shell extension, all we need to be concerned with are the methods in IShellExtInit and IContextMenu.

The IShellExtInit Interface

To initialize a context menu shell extension, Windows uses the IShellExtInit interface. In addition to the standard IUnknown methods, IShellExtInit declares an Initialize method. However, the TFormViewContextMenu class does not define an Initialize method. Instead, it defines the ShellInit method and uses a "method resolution clause" to map the IShellExtInit.Initialize method to ShellInit. The reason for doing this is because the TComObject class defines a virtual Initialize method that is not compatible with IShellExtInit. (Method resolution clauses were covered in part one of this series.)

IShellExtInit.Initialize (and ShellInit) receive three parameters. The first identifies the folder containing the file objects being manipulated. The second parameter is a reference to an IDataObject interface, which is used to obtain the name of the selected files. The third parameter is the ProgID for the type of file that is selected.

Although the selected file names are stored in the same way that Windows stores file names during a drag operation, extracting the name of the selected file is not as simple as calling DragQueryFile. First, the information referenced by the DataObject must be converted into a storage medium that can be referenced within the shell extension. This is accomplished by setting up a TFormatEtc record and then calling the GetData method of the DataObject. Now the DragQueryFile can be called to obtain the name of the selected file, which is then stored in the private FFileName field. Notice in Listing 2 that we obtain the file name only if one file is selected.

The IContextMenu Interface

After the context menu handler is initialized via the IShellExtInit interface, Windows uses the IContextMenu interface to call the other methods of our context menu handler. Specifically, it will call QueryContextMenu, GetCommandString, and InvokeCommand. Let’s take a closer look at each one.

The QueryContextMenu method is called just before Windows displays a file object’s context menu. Implementing this method allows your shell extension to add menu items to the context menu. This is accomplished by calling the InsertMenu function and passing it the handle to the context menu, which is the first parameter to QueryContextMenu.

Although having access to the menu’s handle would suggest that you have complete access to the menu, you don’t. In fact, there are several restrictions in what you can do with the context menu. First, you can only add items (no moving or deleting items), and you are only allowed to add string items. In addition, you must insert the new items starting at the position indicated by the Index parameter. And finally, you must use the CmdFirst parameter as the starting menu identifier for any menu items added.

The Flags parameter passed to QueryContextMenu must be checked to make sure it is safe to add new menu items. The Flags parameter must contain at least one of the following values before a new menu item can be added: CMF_NORMAL, CMF_VERBSONLY, and CMF_EXPLORE. The CMF_NORMAL flag indicates that the selected file is located on the Windows Desktop. The CMF_VERBSONLY flag indicates that the selected file object is actually a shortcut to another file. The CMF_EXPLORE flag indicates that the file is being selected within a separate instance of Windows Explorer.

To demonstrate the effect of each flag, the QueryContextMenu method of the form viewer context menu alters the menu string depending on which flag is specified. Each situation is illustrated in Figures 1 through 3.



Figure  1: Selecting a Form File within the Explorer

 

Figure 2: Selecting a Form File on the Desktop

 

Figure 3: selecting a shortcut to a Form File

Take a closer look at the Explorer status bar in Figure 1. Notice that the status bar displays a help string associated with the selected item in the context menu. The Explorer requests this string from our context menu handler by calling the GetCommandString method. The Cmd parameter to this method indicates which menu item is selected, and the Flags parameter is used to determine when to return a help string. In particular, the help string is returned in the Name parameter only when Flags is equal to GCS_HELPTEXT.

When the user selects one of the menu items added by a context menu handler, Windows calls the InvokeCommand method. This method receives a single record parameter specifying the command to invoke and other useful information such as a window handle to use when displaying message boxes.

The InvokeCommand method for our form viewer context menu uses the CreateProcess function to start the FormFileViewer application that I presented in the last issue. The location of the viewer is found in the Registry, which the viewer updates whenever it is executed. Therefore, the shell extension will always be able to find the viewer. The file name of the select form file is passed to the viewer on the command line. The viewer processes the command line and displays the contents of the form file as shown in Figure 4.
 

Figure 4: Form Viewer started by a Shell Extension

Now that all of the necessary methods have been implemented, there is one more item that must be added to the FormViewImpl unit.

Creating a COM Object Factory

In order for our COM object to be created whenever the FormView DLL is loaded, we need to create an instance of a class factory that specifically creates an instance of our shell extension object. Recall from part one that I prefer the term object factory instead of class factory because a class factory creates objects and not classes.

In the initialization section of the unit implementing the shell extension COM object, you need to create an instance of the object factory. Fortunately, this is quite easy, thanks to the predefined classes in Delphi 3. In particular, we use the TComObjectFactory class defined in the ComObj unit. Notice that the class name and ClassID of the shell extension are passed to the constructor of the factory.

Because shell extensions are implemented as COM objects, and because Windows itself accesses them, we must generate a ClassID for any new shell extensions. But how do we do that? In the examples presented in part two of this series, Delphi automatically generated ClassIDs whenever one was needed. Fortunately, Delphi 3 provides the means to manually generate a ClassID within the code editor. Simply press the Ctrl+Shift+G key combination and Delphi automatically inserts a new GUID into the code editor. This is how the TGUID constant declared at the beginning of the FormViewImpl unit was created.

Registering a Shell Extension

Now that the FormViewImpl unit is completed, we can compile the FormView project and generate the COM Server DLL that Windows will load whenever the user clicks on a Delphi form file using mouse button 2. But how does Windows know that it should load the FormView.dll? As usual, Windows relies on specific entries in the Registry to accomplish this.

There are three ways the entries can be made in the Registry. First, they can be entered manually using RegEdit, but this is not recommended because incorrectly editing the Registry can cause system problems.

The second option is to merge the entries into the Registry. This involves creating a text file (with a REG extension) that contains the new entries. Clicking mouse button 2 on the file displays a context menu containing a Merge command. Selecting this command merges the items in the file into the Registry. This approach is safer than using RegEdit but requires customizing the file for each user because the complete path of the DLL must be specified in the REG file.

The third option is to add the entries programmatically. Most installation programs (for example, WISE Installation System) provide a way to modify the Registry, and since the installation program knows where the DLL is installed, the correct path can be determined during the installation process. This makes this approach the most appropriate for registering shell extensions.

Another alternative to using a custom installation program is to create a simple Delphi program that makes the necessary Registry entries. This is the approach I took for this article. Listing 3 shows the source code for the RegMain unit, which is the main form file unit in the Form-ViewReg program. Simply run this program from the same folder that contains the FormView.dll and the required entries are made in the Registry. Note that if you decide to move the FormView.dll, you will need to rerun the FormViewReg program.

The actual entries that need to be added depend on the type of shell extension, but all shell extensions require some basic settings. First, the ClassID of your extension must be registered under the HKEY_CLASSES_ROOT\CLSID key. The CLSID key contains a list of class identifier keys. Within each ClassID key, you must add an InProcServer32 key that specifies the location of the shell extension DLL.

The information that the shell uses to associate a shell extension with a particular file type is stored under the shellex key, which is under the ProgID for the file type. Context menu handlers are registered under the ContextMenuHandlers key. Within this key, the ClassID of the handler is listed. The FormCreate event handler in Listing 3 shows the entries that must be made for the Delphi form viewer context menu.

For More Information

I relied on two sources of information in developing the shell extension for this article. The first one is the Microsoft Development Network (MSDN). Since Microsoft defines the shell extension interfaces, this is the best place to find detailed information about what the methods of each interface must accomplish. The second source of information came from the Delphi 3 source code—in particular, the ShlObj unit, which has quite a few comments regarding the shell extension interfaces.

On the Drawing Board

Over the past three installments of “Delphi by Design,” the focus has been on COM support in Delphi 3. Along the way, we’ve seen how interfaces are used to manipulate objects, how interfaces define the abilities of automation servers, and how Windows itself relies on them. All of this information serves as a foundation for the topic I’ll be covering in the next issue: How to convert a native Delphi component into an ActiveX control. v

Copyright © 1998 The Coriolis Group, Inc. All rights reserved.
 

Listing 1 - FormView.dpr

library FormView; uses ComServ, FormViewImpl in 'FormViewImpl.pas'; exports DllGetClassObject, DllCanUnloadNow, DllRegisterServer, DllUnregisterServer; {$R *.RES} begin end.

Listing 2 - FormViewImpl.pas

{=============================================================== FormViewImpl Unit This unit implements the TFormViewContentMenu class, which is a COM object that supports the IShellExt and IContextMenu interfaces. ===============================================================} unit FormViewImpl; interface uses Windows, Forms, StdCtrls, ShellApi, SysUtils, Classes, Controls, ComServ, ComObj, ShlObj, ActiveX; const CLSID_DelphiFormViewerContextMenu: TGUID = '{F169D961-B907-11D0-B8FA-A85800C10000}'; type TFormViewContextMenu = class( TComObject, IShellExtInit, IContextMenu ) private FFileName: string; public // IShellExtInit Methods // Use a Method Resolution Clause because Initialize is // defined as a virtual method in TComObject function IShellExtInit.Initialize = ShellInit; function ShellInit( Folder: PItemIDList; DataObject: IDataObject; ProgID: HKEY ): HResult; stdcall; // IContextMenu Methods function QueryContextMenu( Menu: HMENU; Index, CmdFirst, CmdLast, Flags: UINT ): HResult; stdcall; function GetCommandString( Cmd, Flags: UINT; Reserved: PUINT; Name: LPSTR; MaxSize: UINT ): HResult; stdcall; function InvokeCommand( var CommandInfo: TCMInvokeCommandInfo ): HResult; stdcall; end; implementation uses Registry; {==================================} {== TFormViewContextMenu Methods ==} {==================================} function TFormViewContextMenu.ShellInit( Folder: PItemIDList; DataObject: IDataObject; ProgID: HKEY ): HResult; var Medium: TStgMedium; FE: TFormatEtc; begin if DataObject = nil then begin Result := E_FAIL; Exit; end; with FE do begin cfFormat := CF_HDROP; ptd := nil; dwAspect := DVASPECT_CONTENT; lindex := -1; tymed := TYMED_HGLOBAL; end; // Transfer the data referenced by the IDataObject reference to // an HGLOBAL storage medium in CF_HDROP format. Result := DataObject.GetData( FE, Medium ); if Failed( Result ) then Exit; try // If only one file is selected, retrieve the file name and // store it in FileName. Otherwise fail. if DragQueryFile( Medium.hGlobal, $FFFFFFFF, nil, 0) = 1 then begin SetLength( FFileName, MAX_PATH ); DragQueryFile( Medium.hGlobal, 0, PChar(FFileName), MAX_PATH); Result := NOERROR; end else Result := E_FAIL; finally ReleaseStgMedium( Medium ); end; end; function TFormViewContextMenu.QueryContextMenu( Menu: HMENU; Index, CmdFirst, CmdLast, Flags: UINT ): HResult; var MenuText: string; AddMenuItem: Boolean; begin AddMenuItem := True; if ( Flags and $000F ) = CMF_NORMAL then MenuText := 'View (Form File on Desktop) as Text' else if ( Flags and CMF_VERBSONLY ) <> 0 then MenuText := 'View (Form File via Shortcut) as Text' else if ( Flags and CMF_EXPLORE ) <> 0 then MenuText := 'View (Form File in Explorer) as Text' else AddMenuItem := False; if AddMenuItem then begin InsertMenu( Menu, Index, mf_String or mf_ByPosition, CmdFirst, PChar( MenuText ) ); Result := 1; // Return number of menu items added end else Result := NOERROR; end; {= TFormViewContextMenu.QueryContextMenu =} function TFormViewContextMenu.GetCommandString( Cmd, Flags: UINT; Reserved: PUINT; Name: LPSTR; MaxSize: UINT ): HResult; begin case Cmd of 0: begin if Flags = GCS_HELPTEXT then begin // Return the string to be displayed in the Explorer // status bar when the menu item is selected StrCopy(Name, 'View the selected Delphi form file as text'); end; Result := NOERROR; end; else // Invalid menu item Result := E_INVALIDARG; end; end; {= TFormViewContextMenu.GetCommandString =} function GetViewerPath: string; var R: TRegIniFile; begin R := TRegIniFile.Create( '\Software\Raize\FormFileViewer' ); try Result := R.ReadString( 'Program', 'Path', '' ); Result := '"' + Result + '" "%s"'; finally R.Free; end; end; function TFormViewContextMenu.InvokeCommand( var CommandInfo: TCMInvokeCommandInfo ): HResult; var Success: Boolean; CmdLine: string; SI: TStartupInfo; PI: TProcessInformation; begin // Make sure we are not being called by an application if HiWord( Integer( CommandInfo.lpVerb ) ) <> 0 then begin Result := E_FAIL; Exit; end; // Execute the command specified by CommandInfo.lpVerb case LoWord( CommandInfo.lpVerb ) of 0: begin FillChar( SI, SizeOf( SI ), #0 ); SI.cb := SizeOf( SI ); SI.wShowWindow := sw_ShowNormal; SI.dwFlags := STARTF_USESHOWWINDOW; CmdLine := Format( GetViewerPath, [ FFileName ] ); Success := CreateProcess( nil, PChar( CmdLine ), nil, nil, True, 0, nil, nil, SI, PI ); if not Success then begin MessageBox( CommandInfo.hWnd, 'Could not start the Form File Viewer.', 'Error', mb_IconError or mb_OK ); end; Result := NOERROR; end; else // Invalid menu item Result := E_INVALIDARG; end; { case } end; {= TFormViewContextMenu.InvokeCommand =} initialization // Create a COM object factory which will be responsible for // creating instances of our shell extension. ComServer is // declared in ComServ unit. TComObjectFactory.Create( ComServer, TFormViewContextMenu, CLSID_DelphiFormViewerContextMenu, '', 'View Delphi Form Files', ciMultiInstance ); end.

Listing 3 - RegMain.pas

unit RegMain; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, ExtCtrls, StdCtrls; type TForm1 = class(TForm) Button1: TButton; Panel1: TPanel; Label1: TLabel; Image1: TImage; procedure Button1Click(Sender: TObject); procedure FormCreate(Sender: TObject); private { Private declarations } public { Public declarations } end; var Form1: TForm1; implementation {$R *.DFM} uses Registry; const FormViewerClassID = '{F169D961-B907-11D0-B8FA-A85800C10000}'; procedure TForm1.FormCreate(Sender: TObject); var Reg: TRegistry; begin Reg := TRegistry.Create; try with Reg do begin RootKey := HKEY_CLASSES_ROOT; OpenKey( '\CLSID\' + FormViewerClassID, True ); WriteString( '', 'Delphi Form Viewer Context Menu Shell Extension'); OpenKey( '\CLSID\' + FormViewerClassID + '\InProcServer32', True ); WriteString( '', ExtractFilePath( Application.ExeName ) + '\FormView.dll' ); WriteString( 'ThreadingModel', 'Apartment' ); CreateKey( '\DelphiForm\shellex\ContextMenuHandlers\' + FormViewerClassID ); end; finally Reg.Free; end; end; procedure TForm1.Button1Click(Sender: TObject); begin Close; end; end.

<<<Fine articolo originale>>>

Behh... siamo arrivati alla fine, non resta che scaricarsi i sorgenti: ho incluso un readme che descrive in linea di massima il pacchetto. 

DfmViewer.7z

 

   

 
 
Your Ad Here