Enable Linking from Clipboard and Drag-and-Drop Operations

In the same way that Patron creates embedded objects from both clipboard and drag-and-drop operations with OleCreateFromData, we can now create linked objects by using OleCreateLinkFromData. To accomplish this, we need to modify our pasting and our drag-and-drop code to handle CFSTR_LINKSOURCE and CFSTR_LINKSRCDESCRIPTOR. These formats are registered in the CPatronDoc constructor and are saved in the variables m_cfLinkSource and m_cfLinkSrcDescriptor.

Now we need a function such as CPatronDoc::FQueryPasteLinkFromData to check whether we can create a link from a data object:


BOOL CPatronDoc::FQueryPasteLinkFromData(LPDATAOBJECT pIDataObject
, LPFORMATETC pFE, PTENANTTYPE ptType)
{
HRESULT hr;

if (NULL==pIDataObject)
return FALSE;

hr=OleQueryLinkFromData(pIDataObject);

if (NOERROR!=hr)
return FALSE;

if (NULL!=pFE)
SETDefFormatEtc(*pFE, m_cfLinkSource, TYMED_ISTREAM);

if (NULL!=(LPVOID)ptType)
*ptType=TENANTTYPE_LINKEDOBJECTFROMDATA;

return TRUE;
}

You can see that OleQueryLinkFromData returns S_FALSE or NOERROR, so we have to compare the return value to NOERROR to test whether data is available. When the data is available, Patron fills a FORMATETC structure as appropriate for CFSTR_LINKSOURCE and indicates the type of tenant we can create from this data, specifically TENANTTYPE_LINKEDOBJECTFROMDATA. CTenant::Create uses this flag to call OleCreateLinkFromData:


//In CTenant::Create
switch (tType)
{
[Other cases]

case TENANTTYPE_LINKEDOBJECTFROMDATA:
hr=OleCreateLinkFromData((LPDATAOBJECT)pvType
, IID_IUnknown, OLERENDER_DRAW, NULL, NULL
, m_pIStorage, (PPVOID)&pObj);
break;

§

}

With this code in place, we can add the clipboard and drag-and-drop functionality to make use of it. Now, because the link-source format is typically the lowest in the order of available formats, it is unlikely that a Paste command by itself will create a linked object. We need to give the user other ways to perform a paste link. The first way is the Paste Special dialog box, which is the topic of the next section. Then we'll look at modifications to our drag-and-drop code to support linking as well.

Paste Link and Paste Special Commands

To repeat myself, users must have an explicit command with which to perform a paste link, as opposed to using a higher available clipboard format. There are two standard ways to present this command. The first is to enable linking in the Paste Special dialog box; the other is to create a Paste Link item on the Edit menu to give the user an explicit command. In Patron, we'll implement only the former because the result of both commands is the same.

In Chapter 17, you might have noticed that the Paste Special dialog box has a Paste Link button that has been disabled up to this point. To enable this feature, we need to add linkable entries in the OLEUIPASTESPECIAL data structure. You can see how we do this in the code on the following page.


BOOL CPatronDoc::PasteSpecial(HWND hWndFrame)
{
OLEUIPASTESPECIAL ps;
OLEUIPASTEENTRY rgPaste[6];
UINT rgcf[1]; //For ps.m_arrLinkTypes

[Code to initialize other rgPaste[0] through rgPaste[4]]

SETDefFormatEtc(rgPaste[5].fmtetc, m_cfLinkSource, TYMED_ISTREAM);
rgPaste[5].lpstrFormatName=PSZ(IDS_PASTELINK);
rgPaste[5].lpstrResultText=PSZ(IDS_PASTEASLINK);
rgPaste[5].dwFlags=OLEUIPASTE_LINKTYPE1 ś OLEUIPASTE_ENABLEICON;

//Types we can Paste Link from the clipboard
rgcf[0]=m_cfLinkSource;
ps.arrLinkTypes=rgcf;
ps.cLinkTypes=1;

§

uTemp=OleUIPasteSpecial(&ps);

§
}

The Paste Special dialog box will enable the Paste Link option if it encounters an OLEUIPASTEENTRY structure containing CFSTR_LINKSOURCE (such as the one shown in the preceding code, in rgPaste[5]) and if the data object from the clipboard also contains CFSTR_LINKSOURCE.

The next step might seem a little strange because the Paste Special dialog box is designed to handle more than one type of link information, such as (dare we say it) DDE links. You'll notice that the CFSTR_LINKSOURCE entry in the preceding code has the flag OLEUIPASTE_LINKTYPE1. This flag indicates that this entry is attached to the first clipboard format in the OLEUIPASTESPECIAL structure's arrLinkTypes field. This field is a pointer to an array of UINTs in which each element is some sort of link format. In our example, the array rgcf has only one entry, CFSTR_LINKSOURCE, so we indicate an array length of 1 in the cLinkTypes field. If we wanted to support another link-source format—say, an old DDE link—we would add that clipboard format to rgcf, increase cLinkTypes, and add another OLEUIPASTEENTRY structure with the flag OLEUIPASTE_LINKTYPE2, and so on. The Paste Special dialog box in the OLE UI Library supports up to eight link formats.

Now, if the user chooses Paste Link in the dialog and clicks OK, the fLink field in OLEUIPASTESPECIAL will be TRUE on return from OleUIPasteSpecial. In this case, we pass the clipboard's data object to CPatronDoc::PasteFromData with TENANTTYPE_LINKEDOBJECTFROMDATA, which calls down to CTenant::Create, as shown earlier. We also handle the cases in which the user chose Display As Icon, of course.

Drag-and-Drop Linking Feedback

The other way a user can create a linked object from existing data is to drop a data object into a compound document directly. As we learned in Chapter 13, OLE Drag and Drop specifies that the Shift+Ctrl key combination changes the semantics of a drag-and-drop operation to DROPEFFECT_LINK. Until now, Patron has supported only DROPEFFECT_COPY and DROPEFFECT_MOVE. Now we can add the code to check for the Shift+Ctrl combination as well. When a drop occurs and the latest effect is DROPEFFECT_LINK, we can toss the data object given to IDropTarget::Drop (DROPTGT.CPP) to CPatronDoc::PasteFromData. It is so useful to have centralized functions like this!

To handle all of this, Patron initializes the flag m_fLinkAllowed in IDropTarget::DragEnter by calling OleQueryCreateLinkFromData:


//Check whether we can link from this data object as well.
ppg->m_fLinkAllowed
=(NOERROR==OleQueryLinkFromData(pIDataSource));

//We never allow linking by a drag operation in the current document.
ppg->m_fLinkAllowed &= !ppg->m_fDragSource;

If this flag is FALSE, we never allow DROPEFFECT_LINK. Otherwise, we check for Shift+Ctrl elsewhere in the IDropTarget implementation with code such as the following:


*pdwEffect=DROPEFFECT_MOVE;

if (grfKeyState & MK_CONTROL)
{
if (ppg->m_fLinkAllowed && (grfKeyState & MK_SHIFT))
*pdwEffect=DROPEFFECT_LINK;
else
*pdwEffect=DROPEFFECT_COPY;
}

With these few minor changes, you now have a complete set of means through which a user can create a linked object in the container. At this point, you should be able to run a suitable server application (one that supports linking), create and save a file, copy some data to the clipboard, and then try to use Paste Link in your container or drag and drop data from that source. What should appear in your container is an object that looks like an embedded object from the same server. Activating it shows the object in the server with the server's normal user interface (no special changes as there were for embedding). As with embedding, changes made to the data in the server should be reflected in your container by virtue of your IAdviseSink receiving OnViewChange notifications.