8.3 Creating a Data Handler

only for RuBoard - do not distribute or recompile

8.3 Creating a Data Handler

We begin the data handler by adding a new class module to the RadEx Project called clsDataHandler. The handler will implement IDataObject and IPersistFile , which have already been defined in the type library from previous chapters. We should add the following code to the general declarations section of clsDataHandler:

 'clsDataHandler.cls Implements IDataObject Implements IPersistFile 

8.3.1 Implementing IPersistFile

The first thing we want to do is implement IPersistFile . Although IPersistFile supports a number of methods ( GetCurFile , IsDirty , Load , Save , and SaveCompleted ), there is only one method with which we are concerned , Load . Load

The Load method is implemented exactly as it was in Chapter 5. All we are doing is getting the name of the file being copied , which is passed to the Load method as its pszFileName argument. Example 8.2 shows the code to do this.

Example 8.2. The IPersistFile_Load Method
 'clsDataHandler.cls Implements IDataObject Implements IPersistFile Private m_sFile As String Private Sub IPersistFile_Load(ByVal pszFileName As LPCOLESTR, _      ByVal dwMode As DWORD)     m_sFile = Space(255)     CopyMemory ByVal StrPtr(m_sFile), ByVal pszFileName, Len(m_sFile)   End Sub 

8.3.2 Implementing IDataObject

With that out of the way, we can focus on our IDataObject implementation. Of the nine methods supported by IDataObject , we need to implement just three: QueryGetData , EnumFormatEtc , and GetData . QueryGetData

The first method we are concerned with is QueryGetData . Remember, the shell calls QueryGetData and passes in a pointer to a FORMATETC structure. QueryGetData must determine if the format indicated by the FORMATETC structure is valid for the data handler. If it is, QueryGetData returns S_OK ; otherwise , it returns DV_E_FORMATETC , which signals an invalid format.

Because we are dealing with HRESULT s in this function, we will need to replace the QueryGetData method in clsDataHandler with our own implementation, QueryGetDataVB . Swapping vtable entries (see Chapter 4) should be familiar to you by now. Just so you know, we'll have to swap the vtable entries for every method we implement for IDataObject , so get ready. The code to swap the QueryGetData method with the QueryGetDataVB function is as follows :

 'clsDataHandler.cls Implements IDataObject Implements IPersistFile Private m_sFile As String Private m_pOldQueryGetData As Long Private Sub Class_Initialize(  )     Dim pVtable As IDataObject     Set pVtable = Me          'QueryGetData is method 6 in the vtable.  m_pOldQueryGetData = SwapVtableEntry(ObjPtr(pVtable), _                                          6, _                                          AddressOf QueryGetDataVB)  End Sub 

QueryGetDataVB , which resides in handler.bas and is shown in Example 8.3, is a very simple function. It just checks the members of FORMATETC and returns S_OK if they match our data format. Otherwise, it returns DV_E_FORMATETC .

Example 8.3. QueryGetDataVB
 'handler.bas Public Function QueryGetDataVB(ByVal this As IDataObject, _   pformatetc As FORMATETC) As Long     'Default return value QueryGetDataVB = DV_E_FORMATETC          'Text format     If (fmtEtc.cfFormat And CF_TEXT) And _        (fmtEtc.dwAspect = DVASPECT_CONTENT) And _        (fmtEtc.tymed = TYMED_HGLOBAL) And _                         QueryGetDataVB = S_OK     End If      End Function 

As you can see, only three members of the structure participate in this interchange. The cfFormat member can contain more than one format; therefore, we need to use the And operator to determine if the format we are looking for is being described. Don't check for equality here. The shell will always group together several formats with Or . dwAspect and tymed , however, must be explicit values. EnumFormatEtc

EnumFormatEtc will need to return HRESULT s back to the shell. Therefore, we will swap this method with the EnumFormatEtcVB function, which lives in DataHandler.bas . We will add the code to achieve the swap to our Class_Initialize function:

 Private m_pOldQueryGetData As Long Private m_pOldEnumFormatEtc As Long Private Sub Class_Initialize(  )     Dim pVtable As IDataObject     Set pVtable = Me     m_pOldQueryGetData = SwapVtableEntry(ObjPtr(pVtable), _                                          6, _                                          AddressOf QueryGetDataVB)     m_pOldEnumFormatEtc = SwapVtableEntry(ObjPtr(pVtable), _                                            9, _                                           AddressOf EnumFormatEtcVB) End Sub 

Remember, the shell calls this method in order to retrieve all of the formats that are supported by the data object. The request is fulfilled by providing the shell with another object that supports the IEnumFORMATETC interface. We will not discuss this interface in detail here. All you need to know is that this interface supports four methods: Next , Reset , Skip , and Clone . Once the enumerator has been given to the shell, the Next method will be called repeatedly. This is Explorer's way of saying, "Next format, please ." The data object must provide the shell with a new format every time this method is called. Once all the formats have been provided, the method must return S_FALSE .

Fortunately for us, we don't have to create an enumeration object. As it turns out, we can register all the formats our object supports in the registry. Then we can call OleRegEnumFormatEtc. OleRegEnumFormatEtc returns a reference to an IEnumFORMATETC interface that is implemented somewhere deep in the bowels of ole32.dll . After we pass this interface pointer back to the shell, the shell will use it to enumerate all of the formats that have been stored in the registry. Example 8.4 demonstrates the process.

Example 8.4. EnumFormatEtcVB
 'handler.bas Public Declare Function OleRegEnumFormatEtc Lib "ole32.dll" ( _         refclsid As GUID, _          ByVal dwDirection As DATADIR, _          lpEnumFormatEtc As IEnumFORMATETC) As Long 'DataHandler.bas Public Function EnumFormatEtcVB(_      ByVal this As IDataObject, _      ByVal dwDirection As Long, _      ppenumFormatEtc As IEnumFORMATETC) As Long     Dim clsid As GUID     CLSIDFromProgID ByVal StrPtr("RadEx.clsDataHandler"), clsid          EnumFormatEtcVB = OleRegEnumFormatEtc(clsid, _          DATADIR_GET Or DATADIR_SET, ppenumFormatEtc)      End Function 

The first parameter to OleRegEnumFormatEtc is a REFCLSID , or a reference to a class identifier. This is a fancy way of saying a pointer to a GUID. We need an actual GUID here, and since there is no datatype that is 128 bits wide, we make our own. GUID is defined in handler.bas , not the type library, for reasons of automation compatibility. (It contains an array of bytes which is incompatible.) The definition looks like this:

 'handler.bas Public Type GUID     Data1 As Long     Data2 As Integer     Data3 As Integer     Data4(7) As Byte End Type 

OleRegEnumFormatEtc is expecting a reference to our data handler's CLSID. The easiest way to get this value is by calling CLSIDFromProgID , which will populate the GUID structure we need when passed the programmatic identifier of our data handler, which is Radex.clsDataHandler . CLSIDFromProgID is found in ole32.dll and is declared as follows:

 Public Declare Function CLSIDFromProgID Lib "ole32.dll" _      (ByVal lpszProgID As Long, pCLSID As GUID) As Long 

The second parameter can be either DATADIR_GET or DATADIR_SET or both. DATADIR_GET causes the function to enumerate all of the formats that can be passed to IDataObject::GetData ; DATADIR_SET enumerates the formats that could be passed to IDataObject::SetData .

Make Your Own Enumerator

You can create a class that implements IEnumFORMATETC yourself, if you want to provide your own enumerator. The implementation must be separate from the data object. For more details, see the discussion of IEnumIDList in Chapter 11. (All of the IEnum XXXX interfaces contain the same methods and implement the same behavior. Chapter 11 should provide you with enough details.) The IEnumFORMATETC definition is included in the type library for this purpose. Once you have created your own enumerator, implementing EnumFormatEtc would simply be a matter of passing your own enumerator back to the shell:

 Public Function EnumFormatEtcVB(_     ByVal this As IDataObject, _     ByVal dwDirection As Long, _     ppenumFormatEtc As IEnumFORMATETC) As Long     Dim ef As clsEnumFormatEtc 'Your enumerator     Set ef = New clsEnumFormatEtc     Set ppenumFormatEtc = ef End Function 

The third parameter is a pointer to a pointer to an IEnumFORMATETC interface. Conveniently, we can just pass in the value provided to us by Explorer.

The last thing we need to do to make sure that EnumFormatEtc will work properly is to actually register the data format(s) that we can provide, in our case CF_TEXT . The data format is registered under the CLSID of our data handler as shown in Figure 8.2.

Figure 8.2. Data format registry settings

The string "1,1,1,3" is the format itself and corresponds to the values <format, aspect, medium, direction> . In our case CF_TEXT = 1, DVASPECT_CONTENT = 1, and TYMED_HGLOBAL = 1. The direction value comes from the DATADIR enumeration, which is simply defined as:

 typedef enum tagDATADIR {      DATADIR_GET = 1,      DATADIR_SET = 2  } DATADIR; 

Therefore, (DATADIR_GET Or DATADIR_SET) = 3. This value implies that the format is valid for get and set operations. GetData

GetData is a little more complex than QueryGetData , but basically here's how it works: the shell passes in a pointer to a FORMATETC structure and a pointer to a STGMEDIUM structure. If the FORMATETC structure describes the format that we can provide, then the STGMEDIUM structure can be populated with a pointer to the data we want to make availablein our case, a string describing the Animal type contained in the .rad file. As is the case with QueryGetData , if the format queried is invalid, we return DV_E_FORMATETC . This means we need to swap the GetData in clsDataHandler with our own function so we can return HRESULT s. The Class_Initialize event for clsDataHandler does this, as the following code shows:

 'clsDataHandler.cls Private m_pOldGetData As Long Private m_pOldQueryGetData As Long Private m_pOldEnumFormatEtc As Long Private Sub Class_Initialize(  )     Dim pVtable As IDataObject     Set pVtable = Me          m_pOldQueryGetData = SwapVtableEntry(ObjPtr(pVtable), _                                          6, _                                          AddressOf QueryGetDataVB)     m_pOldEnumFormatEtc = SwapVtableEntry(ObjPtr(pVtable), _                                            9, _                                           AddressOf EnumFormatEtcVB)  m_pOldGetData = SwapVtableEntry(ObjPtr(pVtable), _                                      4, _                                     AddressOf GetDataVB)     'AddRef     Dim pUnk As IUnknownVB     Set pUnk = Me     pUnk.AddRef  End Sub 

Take note that the data handler needs to make a call to AddRef ; otherwise, the handler will terminate before the data can be transferred.

Now, let's break down GetDataVB , the first portion of which is shown in the following code fragment:

 'handler.bas Public Function GetDataVB(ByVal this As IDataObject, _                  pformatetcIn As FORMATETC, _                  pmedium As STGMEDIUM) As Long     GetDataVB = DV_E_FORMATETC          Dim b(  ) As Byte     Dim dataObj As clsDataHandler     Dim hGlobalMem As HGLOBAL     Dim pGlobalMem As Long     Dim szType As String     Dim szMsg As String  Set dataObj = this      If (pformatetcIn.cfFormat = CF_TEXT) And _        (pformatetcIn.dwAspect = DVASPECT_CONTENT) And _        (pformatetcIn.tymed = TYMED_HGLOBAL) Then _  

The first thing we will do is get a reference to our class object. By setting dataObj equal to this , we are calling QueryInterface on our object. This returns a reference back to our object (an IDispatch pointer) that we can use from handler.bas . This alleviates the need for a global variable.

We then compare the cfFormat , dwAspect , and tymed members of the FORMATETC structure to make sure it is the format we are looking to provide data for. If it is, we are good to go. Since we have a reference back to clsDataHandler that allows us to easily retrieve the name of our .rad file, we can then use GetPrivateProfileString to get the animal type from the selected file, as the next fragment from the GetDataVB method shows:

 'Get Animal type szType = Space(255) GetPrivateProfileString "Animal", _                         "Type", _                         "Unknown", _                         szType, _                         Len(szType), _  dataObj.FileName  

We have the animal type, so now what? Remember that the shell is expecting the format of text being transferred in global memory. What we need to do is copy the string into global memory. Before we do that, we actually need to lay our hands on some global memory. We can do that by calling GlobalAlloc , which is defined like this:

 Public Declare Function GlobalAlloc Lib "kernel32" _      (ByVal wFlags As Long, ByVal dwBytes As Long) As Long 

Using the HGLOBAL (handle to global memory) returned to us by GlobalAlloc , we can get a pointer to global memory itself and copy our string into that location. This is accomplished using GlobalLock , CopyMemory , and GlobalUnlock , as the next code fragment from the GetDataVB method shows:

 'Allocate global memory. hGlobalMem = GlobalAlloc(GMEM_MOVEABLE, 1024) 'Get a pointer to the global memory.  pGlobalMem = GlobalLock(hGlobalMem)  'Copy Animal type into global memory. szType = TrimNull(szType) szMsg = "The " & szType & " is on the clipboard." & vbCrLf b = StrConv(szMsg, vbFromUnicode) & vbNullChar  CopyMemory ByVal pGlobalMem, b(0), UBound(b) + 1  'Unlock global memory.  GlobalUnlock hGlobalMem  

We are not quite done yet. Now that the global memory we have allocated contains our string, we need to make it available to the clipboard. We do this by populating a STGMEDIUM structure and copying it to the location passed in by the shell:

 stgMed.pData = hGlobalMem stgMed.TYMED = TYMED_HGLOBAL Set stgMed.pUnkForRelease = this GetDataVB = S_OK 

8.3.3 Registration and Operation

Lastly, we have to register the data handler (and make sure the data format is registered, too). There can be only one data handler per file object, so registration is fairly simple (see Figure 8.3).

Figure 8.3. Data handler registry settings

Don't forget to add the CLSID to the approved shell extensions section!

If the data handler is not working at this point, try restarting the shell. Data handlers can be really picky!

The following registry script will handle registering this chapter's example. Note that statements appearing inside of square brackets must reside on the same line:

 REGEDIT4 [HKEY_CLASSES_ROOT\radfile\shellex\DataHandler] @ = "{5BE98B48-FD84-11D2-9FE5-00550076E06F}" [HKEY_CLASSES_ROOT\CLSID\{5BE98B48-FD84-11D2-9FE5-00550076E06F}\DataFormats\GetSet 
  REGEDIT4 [HKEY_CLASSES_ROOT\radfile\shellex\DataHandler] @ = "{5BE98B48-FD84-11D2-9FE5-00550076E06F}" [HKEY_CLASSES_ROOT\CLSID\{5BE98B48-FD84-11D2-9FE5-00550076E06F}\DataFormats\GetSet\0] @ = "1,1,1,3" [HKEY_CLASSES_ROOT\CLSID\{5BE98B48-FD84-11D2-9FE5-00550076E06F}\DataFormats\GetSet\1] @ = "2,1,16,3" [HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Shell Extensions\Approved] "{5BE98B48-FD84-11D2-9FE5-00550076E06F}" = "RAD data handler"  
] @ = "1,1,1,3" [HKEY_CLASSES_ROOT\CLSID\{5BE98B48-FD84-11D2-9FE5-00550076E06F}\DataFormats\GetSet] @ = "2,1,16,3" [HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Shell Extensions\Approved] "{5BE98B48-FD84-11D2-9FE5-00550076E06F}" = "RAD data handler"

Select a .rad file and select the Copy command from Explorer's Edit menu. The clipboard now contains our string. Open up Notepad or Word. The Paste command should be available because both of these programs support the CF_TEXT format. Microsoft Paint, on the other hand, cannot get the data because it wants CF_BITMAP .

As an exercise, you might want to modify the data handler to copy a picture of the animal to the clipboard using CF_BITMAP . Instead of TYMED_HGLOBAL , specify TYMED_GDI . The FORMATETC structure you are interested in looks like this:

 FORMATETC cfFormat = CF_BITMAP ptd = 0 dwAspect = DVASPECT_CONTENT lIndex = -1 TYMED = TYMED_GDI 

The bitmaps for the various animals could be stored in a resource file. You don't have to mess around with GlobalAlloc , because you only need a handle to a bitmap, which is easily attained by calling the LoadIcon API.

only for RuBoard - do not distribute or recompile

Visual Basic Shell Programming
Visual Basic Shell Programming
ISBN: B00007FY99
Year: 2000
Pages: 128

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net