Invoke the Insert Object Dialog Box and Create an Object

We're now at the point at which we can create an object to put in the site. To do so, we need to retrieve a CLSID to pass to the OleCreate function. This is the purpose of the standard Insert Object dialog box provided in the OLE UI Library. As shown in Figure 17-1 on page 818, this dialog box has two modes. The first is Create New, in which the user selects the name of an object type to create. In return, the container receives the appropriate CLSID for that type. The other mode of this dialog box, shown in Figure 17-7, lets the user enter or browse for a filename. In response to this option, the container6 calls OleCreateFromFile to create either an initialized object or a package. (In Chapter 20, we'll enable a Link check box in this part of the dialog box and call OleCreateLinkToFile when that box is checked.)

Figure 17-7.

The Insert Object dialog box, with Create From File selected.

The OLE UI Library once again provides us with the dialog implementation, which is available through the OleUIInsertObject API function. The dialog scans the registry for anything marked "Insertable" and populates its Create New list box with those entries.

Dealing with this dialog means that we first need a menu command for it. Patron adds an Insert Object item on its Edit menu for this purpose. If you have a top-level Insert menu already, make an Object item instead. Now, when the user selects this command, Patron invokes the dialog from within CPatronDoc::InsertObject. On return, it checks to see which option was selected and passes the necessary information down to CPages::TenantCreate, which delegates to CPage::TenantCreate, which creates a new tenant and calls its CTenant::Create function. Inside CTenant::Create, we then call OleCreate or OleCreateFromFile and initialize the object. This initialization also handles iconic aspects when the Display As Icon box is checked in the dialog.

Calling OleUIInsertObject is a matter of filling an OLEUIINSERTOBJECT structure. Most of the work comes in handling what we get back, as you can see in CPatronDoc::InsertObject:7


BOOL CPatronDoc::InsertObject(HWND hWndFrame)
{
OLEUIINSERTOBJECT io;
DWORD dwData=0;
TCHAR szFile[CCHPATHMAX];
UINT uTemp;
BOOL fRet=FALSE;

if (NULL==m_pPG)
return FALSE;

memset(&io, 0, sizeof(io));

io.cbStruct=sizeof(io);
io.hWndOwner=hWndFrame;

szFile[0]=0;
io.lpszFile=szFile;
io.cchFile=CCHPATHMAX;

io.dwFlags=IOF_SELECTCREATENEW œ IOF_DISABLELINK;

uTemp=OleUIInsertObject(&io);

if (OLEUI_OK==uTemp)
{
TENANTTYPE tType;
LPVOID pv;
FORMATETC fe;

SETDefFormatEtc(fe, 0, TYMED_NULL); if (io.dwFlags & IOF_SELECTCREATENEW)
{
tType=TENANTTYPE_EMBEDDEDOBJECT;
pv=&io.clsid;
}
else
{
tType=TENANTTYPE_EMBEDDEDFILE;
pv=szFile;
}

if ((io.dwFlags & IOF_CHECKDISPLAYASICON)
&& NULL!=io.hMetaPict)
{
fe.dwAspect=DVASPECT_ICON;
dwData=(DWORD)(UINT)io.hMetaPict;
}

fRet=m_pPG->TenantCreate(tType, pv, &fe, NULL, dwData);

//Free this regardless of what we do with it.
INOLE_MetafilePictIconFree(io.hMetaPict);

if (fRet)
{
//Disable Printer Setup once we've created a tenant.
m_fPrintSetup=FALSE;
FDirtySet(TRUE);
}
}

return fRet;
}

The lpszFile and cchFile fields in the OLEUIINSERTOBJECT structure describe a buffer in which the dialog will return the filename if Create From File was selected. The IOF_CREATENEW flag forces initial selection of Create New, and IOF_DISABLELINK disables the linking abilities of this dialog (for now). On return, the dwFlags field tells us which option was selected. If it was Create New, we pass the CLSID of the user's selection from the list to CPages::TenantCreate. If Create From File was selected, we pass the filename instead. The TENANTCREATE_* flag differentiates the two cases. In addition, we initialize a FORMATETC structure to indicate whether the user wants an iconic display, in which case the structure field hMetaPict contains a metafile with the iconic representation in it. This data must always be freed when you have finished using it, which the helper function INOLE_MetafilePictIconFree (INOLE\HELPERS.CPP) does for us.

Down in CPage::TenantCreate, we create a new tenant, have that tenant create the object, position that tenant on the page, repaint it, and select it. In addition, we activate that new object immediately if it was created in response to the Create New option in the dialog. This means that we ask the tenant to call the object's IOleObject::DoVerb(OLEIVERB_PRIMARY) function and immediately update the object's storage. We do this because a new blank object is worthless and the first thing a user will do is activate the object to put some data in it. This step does exactly that. We'll see how the tenant performs this step later. We're interested here in what CTenant::Create does to create the new embedded object.


UINT CTenant::Create(TENANTTYPE tType, LPVOID pvType
, LPFORMATETC pFE, PPOINTL pptl, LPSIZEL pszl
, LPSTORAGE pIStorage, PPATRONOBJECT ppo, DWORD dwData)
{
HRESULT hr;
LPUNKNOWN pObj;
UINT uRet=CREATE_GRAPHICONLY;

[Validate arguments and determine placement using ppo.]

hr=ResultFromScode(E_FAIL);

//Now create object based specifically on type.
switch (tType)
{
case TENANTTYPE_NULL:
break;

case TENANTTYPE_STATIC:
hr=CreateStatic((LPDATAOBJECT)pvType, pFE, &pObj);
break;

case TENANTTYPE_EMBEDDEDOBJECT:
hr=OleCreate(*((LPCLSID)pvType), IID_IUnknown
, OLERENDER_DRAW, NULL, NULL, m_pIStorage
, (PPVOID)&pObj);
break;

case TENANTTYPE_EMBEDDEDFILE:
hr=OleCreateFromFile(CLSID_NULL, (LPTSTR)pvType
, IID_IUnknown, OLERENDER_DRAW, NULL, NULL
, m_pIStorage, (PPVOID)&pObj);
break;

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

default:
break;
}

//If creation didn't work, get rid of the element Open created.
if (FAILED(hr))
{
Destroy(pIStorage);
return CREATE_FAILED;
}

//We don't get size if PatronObject data was seen already.
if (!ObjectInitialize(pObj, pFE, dwData))
{
Destroy(pIStorage);
return CREATE_FAILED;
}

if (0==pszl->cx && 0==pszl->cy)
{
SIZEL szl;

//Try to get real size of object; default to 2" x 2".
SETSIZEL((*pszl), 2*LOMETRIC_PER_INCH, 2*LOMETRIC_PER_INCH);
hr=ResultFromScode(E_FAIL);

//Try IViewObject2 first and then IOleObject as a backup.
if (NULL!=m_pIViewObject2)
{
hr=m_pIViewObject2->GetExtent(m_fe.dwAspect, -1, NULL
, &szl);
}
else
{
if (NULL!=m_pIOleObject)
hr=m_pIOleObject->GetExtent(m_fe.dwAspect, &szl);
}

if (SUCCEEDED(hr))
{
//Convert HIMETRIC to our LOMETRIC mapping.
SETSIZEL((*pszl), szl.cx/10, szl.cy/10);
}
}

return uRet;
}

A tenant can create an embedded object in three ways: OleCreate, OleCreateFromFile, and OleCreateFromData. The first, OleCreate, creates a new embedded object from a CLSID. We pass to it the CLSID from the Insert Object dialog box, the interface we want (IUnknown), a render option (OLERENDER_DRAW), a pointer to a FORMATETC structure (NULL because we're using OLERENDER_DRAW), a pointer to an IOleClientSite interface (NULL because we'll give it to the object later), the storage element for this object (m_pIStorage), and the address in which to store the interface pointer that we want in return.

The second way to create an object, OleCreateFromFile, creates a new embedded object or a Package object using the file contents. You always pass CLSID_NULL along with the filename from the Insert Object dialog; the other arguments are the same.

The third way to create an object is from existing data in a data object through OleCreateFromData. We'll use this function when pasting a new object from the clipboard or accepting one through drag and drop. In this case, the data identifies the object to create, but otherwise the arguments are the same.

In all three cases, we get back the first interface pointer to a new and loaded object. We must now initialize the object. This event occurs in CTenant::ObjectInitialize, which happens to be the same initialization we need to do when loading an object again later:


BOOL CTenant::ObjectInitialize(LPUNKNOWN pObj, LPFORMATETC pFE
, DWORD dwData)
{
HRESULT hr;
LPPERSIST pIPersist=NULL;
DWORD dw;
PCDocument pDoc;
TCHAR szFile[CCHPATHMAX];

if (NULL==pObj œœ NULL==pFE)
return FALSE;

m_pObj=pObj;
m_fe=*pFE;
m_fe.ptd=NULL;
m_dwState=TENANTSTATE_DEFAULT;

m_tType=TENANTTYPE_EMBEDDEDOBJECT;

[Code to handle static objects omitted]

m_pIViewObject2=NULL;
hr=pObj->QueryInterface(IID_IViewObject2
, (PPVOID)&m_pIViewObject2);

if (FAILED(hr))
return FALSE;

m_pIViewObject2->SetAdvise(m_fe.dwAspect, 0, m_pImpIAdviseSink);

//We need an IOleObject most of the time, so get one here.
m_pIOleObject=NULL;
hr=pObj->QueryInterface(IID_IOleObject
, (PPVOID)&m_pIOleObject);

if (FAILED(hr))
return TRUE;

m_pIOleObject->GetMiscStatus(m_fe.dwAspect, &m_grfMisc);

if (OLEMISC_ONLYICONIC & m_grfMisc)
m_fe.dwAspect=DVASPECT_ICON;

m_pIOleObject->SetClientSite(m_pImpIOleClientSite);
m_pIOleObject->Advise(m_pImpIAdviseSink, &dw);

OleSetContainedObject(m_pIOleObject, TRUE);

pDoc=(PCDocument)SendMessage(GetParent(m_hWnd), DOCM_PDOCUMENT
, 0, 0L);

if (NULL!=pDoc)
pDoc->FilenameGet(szFile, CCHPATHMAX);
else
szFile[0]=0;

NotifyOfRename(szFile, NULL);

if (DVASPECT_ICON & m_fe.dwAspect)
{
DWORD dw=DVASPECT_CONTENT;
IAdviseSink *pSink;

pSink=(NULL==dwData) ? NULL : m_pImpIAdviseSink;

INOLE_SwitchDisplayAspect(m_pIOleObject, &dw
, DVASPECT_ICON, (HGLOBAL)(UINT)dwData, FALSE
, (NULL!=dwData), pSink, NULL);
}

return TRUE;
}

Initialization consists of the following steps:

Remember the type of the object (static, embedded, linked, and so on).

Establish a view change notification by calling IViewObject::SetAdvise, passing your IAdviseSink pointer and the aspect of interest. By doing this, you'll receive notifications for view changes in whatever you have displayed.

Pass your IOleClientSite pointer to the object by calling IOleObject::SetClientSite. You can also pass the pointer as an argument to OleCreate* functions, but you also need to call SetClientSite when loading an object with OleLoad, and OleLoad does not take such an argument. So to keep it all central (as well as explicit), the call is made here.

Pass your IAdviseSink pointer to IOleObject::Advise to receive other notifications. This is necessary for proper operation of the handler.

Call OleSetContainedObject to mark the object as stored in a container. This is generally to facilitate linking to embeddings that we'll support in Chapter 21, but calling it now does no harm. This function is really just a wrapper for IRunnableObject::SetContainedObject.

Provide the object with strings for its user interface by sending your application and document names to IOleObject::SetHostNames. Patron does this through CTenant::NotifyOfRename, which passes "Patron" as the application name and the filename of the document or "Untitled" as the document name (no path).

Handle iconic displays, which we'll look at later in this chapter.

In addition to these steps, Patron holds on to the object's IOleObject and IViewObject2 pointers simply because we'll use them often and would like to avoid excess QueryInterface calls.

At this point, Patron has created and initialized an object. When we get back into CPage::TenantCreate, Patron will activate the object, which runs the server and displays the new object in its user interface. As changes occur to that object, they'll be reflected in the container when someone next calls IViewObject2::Draw, as happens in CTenant::Draw. Keep in mind that CTenant::Draw remembers whether the object is open, so it draws the site shading when necessary.


Resizing Objects: IOleObject::SetExtent and IOleObject::GetExtent

While managing objects, your container might resize the site in which the object lives, as Patron does. In this situation, you should call IOleObject::SetExtent to let it know the exact size of its display. If the object is marked with OLEMISC_RECOMPOSEONRESIZE, you must also call OleRun before calling IOleObject::SetExtent. You then call IOleObject::Update and IOleObject::Close to bring the object back to the running state if it wasn't originally running. This sequence, which you can see in CTenant::SizeSet, allows even objects in local servers to redraw themselves for new scaling as best they can. In-process objects receive this call directly, so they are automatically optimized. In some cases, a container might not want to control an object's size, letting it be whatever size it wants. In this case, IOleObject::GetExtent asks the object how large it would like to be. Patron calls GetExtent after creating a new object to set the initial size of the site, but thereafter it will always tell the object the new extents when the site is resized by calling SetExtent.


6 Note again that a server must implement basic file moniker binding support for OleCreateFromFile to create something other than a package. Cosmo, for example, doesn't have this capability until Chapter 21.

7 When I create an object, I disable Patron's Printer Setup command. This has nothing to do with OLE and is here only because I'm lazy and didn't want to write code to reposition every object on a page after changing the paper size. In Patron, you can change the printer setup until you put something on a page, and then you're locked into that configuration.