Sdílet prostřednictvím


TN041: Migrace z prostředí MFC/OLE1 do MFC/OLE2

Poznámka

Následující technická poznámka se od prvního zahrnutí do online dokumentace neaktualizovala. V důsledku toho můžou být některé postupy a témata zastaralé nebo nesprávné. Nejnovější informace doporučujeme vyhledat v online indexu dokumentace, které vás zajímá.

Obecné problémy související s migrací

Jedním z cílů návrhu pro třídy OLE 2 v prostředí MFC 2.5 (a vyšší) bylo zachovat většinu stejné architektury, která byla zavedena v prostředí MFC 2.0 pro podporu OLE 1.0. V důsledku toho mnoho stejných tříd OLE v MFC 2.0 stále existuje v této verzi MFC (COleDocument, , COleServerDocCOleClientItem, COleServerItem). Kromě toho je mnoho rozhraní API v těchto třídách úplně stejné. Ole 2 se ale výrazně liší od OLE 1.0, takže můžete očekávat, že se některé podrobnosti změnily. Pokud znáte podporu OLE1 knihovny MFC 2.0, budete se cítit doma s podporou MFC 2.0.

Pokud používáte existující aplikaci MFC/OLE1 a přidáváte do ní funkce OLE 2, měli byste nejprve přečíst tuto poznámku. Tato poznámka popisuje některé obecné problémy, se kterými se můžete setkat při přenosu funkcí OLE1 do MFC/OLE 2 a poté popisuje problémy zjištěné při portování dvou aplikací zahrnutých v prostředí MFC 2.0: ukázky OLE MFC OCLIENT a HIERSVR.

Architektura dokumentů a zobrazení MFC je důležitá

Pokud vaše aplikace nepoužívá architekturu dokumentů a zobrazení knihovny MFC a chcete do aplikace přidat podporu OLE 2, teď je čas přejít na dokument nebo zobrazení. Mnohé z výhod tříd OLE 2 knihovny MFC jsou realizovány pouze tehdy, když vaše aplikace používá integrovanou architekturu a komponenty MFC.

Implementace serveru nebo kontejneru bez použití architektury MFC je možná, ale nedoporučuje se.

Použití implementace MFC místo vlastní implementace

MFC "naskenovaná implementace" třídy, jako CToolBarje , CStatusBara CScrollView mají integrovaný speciální případ kód pro podporu OLE 2. Pokud tedy tyto třídy můžete použít ve své aplikaci, budete mít prospěch z úsilí, které do nich vložíte, aby je technologie OLE věděla. Pro tyto účely je opět možné "roll-your-own" třídy, ale není navržena. Pokud potřebujete implementovat podobné funkce, je zdrojový kód MFC skvělým odkazem pro práci s některými jemnými body OLE (zejména pokud jde o místní aktivaci).

Prozkoumání ukázkového kódu MFC

Existuje řada ukázek MFC, které zahrnují funkce OLE. Každá z těchto aplikací implementuje OLE z jiného úhlu:

  • HIERSVR Je určen především pro použití jako serverová aplikace. Byla zahrnuta v prostředí MFC 2.0 jako aplikace MFC/OLE1 a byla portována do knihovny MFC/OLE 2 a poté rozšířena tak, aby implementovala mnoho funkcí OLE dostupných v OLE 2.

  • OCLIENT Toto je samostatná kontejnerová aplikace, která má demonstrovat mnoho funkcí OLE z hlediska kontejneru. Byl také portován z MFC 2.0 a poté rozšířen tak, aby podporoval mnoho pokročilejších funkcí OLE, jako jsou vlastní formáty schránky a odkazy na vložené položky.

  • DRAWCLI Tato aplikace implementuje podporu kontejneru OLE podobně jako OCLIENT, s tím rozdílem, že to dělá v rámci existující objektově orientované kreslicí program. Ukazuje, jak můžete implementovat podporu kontejneru OLE a integrovat ho do stávající aplikace.

  • SUPERPAD Tato aplikace, stejně jako je jemně samostatná aplikace, je také server OLE. Podpora serveru, který implementuje, je poměrně minimalistický. Zvláštní zájem je, jak používá služby schránky OLE ke kopírování dat do schránky, ale používá funkce integrované do ovládacího prvku Windows "edit" k implementaci funkce vložení schránky. To ukazuje zajímavou kombinaci tradičního použití rozhraní API systému Windows a také integrace s novými rozhraními OLE API.

Další informace o ukázkových aplikacích najdete v nápovědě k ukázkové knihovně MFC.

Případová studie: OCLIENT z MFC 2.0

Jak je popsáno výše, OCLIENT byl součástí MFC 2.0 a implementovaný OLE s MFC/OLE1. Kroky, kterými byla tato aplikace původně převedena na použití tříd MFC/OLE 2, jsou popsány níže. Po dokončení počátečního portu byla přidána řada funkcí, aby bylo možné lépe znázornit třídy MFC/OLE. Tyto funkce zde nebudou pokryty; další informace o těchto pokročilých funkcích najdete v samotné ukázce.

Poznámka

Chyby kompilátoru a podrobný proces byly vytvořeny pomocí visual C++ 2.0. V sadě Visual C++ 4.0 se mohly změnit konkrétní chybové zprávy a umístění, ale koncepční informace zůstávají platné.

Zprovoznění a zprovoznění

Přístup přijatý k portu ukázky OCLIENT do MFC/OLE je začít jeho sestavením a opravou zjevných chyb kompilátoru, které budou mít za následek. Pokud vezmete ukázku OCLIENT z prostředí MFC 2.0 a zkompilujete ji v této verzi knihovny MFC, zjistíte, že neexistuje tolik chyb, které je potřeba vyřešit. Chyby v pořadí, v jakém k nim došlo, jsou popsány níže.

Kompilace a oprava chyb

\oclient\mainview.cpp(104) : error C2660: 'Draw' : function does not take 4 parameters

První chyba se týká COleClientItem::Draw. V prostředí MFC/OLE1 trvalo více parametrů, než přebírá verze MFC/OLE. Dodatečné parametry nebyly často nezbytné a obvykle NULL (jako v tomto příkladu). Tato verze KNIHOVNY MFC může automaticky určit hodnoty pro lpWBounds, když cdC, na který je nakreslena, je metasoubor DC. Kromě toho parametr pFormatDC už není nutný, protože architektura vytvoří jeden z atributu DC předávaného pDC. Chcete-li tento problém vyřešit, jednoduše odeberete dva nadbytečné null parametry volání Draw.

\oclient\mainview.cpp(273) : error C2065: 'OLE_MAXNAMESIZE' : undeclared identifier
\oclient\mainview.cpp(273) : error C2057: expected constant expression
\oclient\mainview.cpp(280) : error C2664: 'CreateLinkFromClipboard' : cannot convert parameter 1 from 'char [1]' to 'enum ::tagOLERENDER '
\oclient\mainview.cpp(286) : error C2664: 'CreateFromClipboard' : cannot convert parameter 1 from 'char [1]' to 'enum ::tagOLERENDER '
\oclient\mainview.cpp(288) : error C2664: 'CreateStaticFromClipboard' : cannot convert parameter 1 from 'char [1]' to 'enum ::tagOLERENDER '

Výše uvedené chyby vyplývají ze skutečnosti, že všechny COleClientItem::CreateXXXX funkce v prostředí MFC/OLE1 vyžadovaly předání jedinečného názvu představující položku. Toto byl požadavek základního rozhraní OLE API. To není nutné v prostředí MFC/OLE 2, protože technologie OLE 2 nepoužívá DDE jako základní komunikační mechanismus (název byl použit v konverzacích DDE). Pokud chcete tento problém vyřešit, můžete funkci odebrat CreateNewName i všechny odkazy na ni. V této verzi je snadné zjistit, co jednotlivé funkce MFC/OLE očekávají, jednoduše tak, že umístíte kurzor na volání a stisknete klávesu F1.

Další oblastí, která se výrazně liší, je zpracování schránky OLE 2. S rozhraními OLE1 jste použili rozhraní API schránky systému Windows, která komunikují se schránkou. S OLE 2 to se provádí s jiným mechanismem. Rozhraní API MFC/OLE1 předpokládala, že schránka byla otevřena před zkopírováním COleClientItem objektu do schránky. To už není nutné a způsobí selhání všech operací schránky MFC/OLE. Když upravíte kód tak, aby odebral závislosti, CreateNewNameměli byste také odebrat kód, který se otevře a zavře schránku systému Windows.

\oclient\mainview.cpp(332) : error C2065: 'AfxOleInsertDialog' : undeclared identifier
\oclient\mainview.cpp(332) : error C2064: term does not evaluate to a function
\oclient\mainview.cpp(344) : error C2057: expected constant expression
\oclient\mainview.cpp(347) : error C2039: 'CreateNewObject' : is not a member of 'CRectItem'

Tyto chyby jsou výsledkem obslužné rutiny CMainView::OnInsertObject . Zpracování příkazu Vložit nový objekt je další oblast, ve které se věci změnily poměrně trochu. V tomto případě je nejjednodušší jednoduše sloučit původní implementaci s aplikací AppWizard pro novou aplikaci KONTEJNER OLE. Ve skutečnosti se jedná o techniku, kterou můžete použít pro přenos jiných aplikací. V prostředí MFC/OLE1 jste zobrazili dialogové okno Vložit objekt voláním AfxOleInsertDialog funkce. V této verzi vytvoříte objekt dialogového COleInsertObject okna a zavoláte DoModal. Kromě toho se nové položky OLE vytvářejí pomocí CLSID místo řetězce názvu třídy. Konečný výsledek by měl vypadat přibližně takto:

COleInsertDialog dlg;
if (dlg.DoModal() != IDOK)
    return;

BeginWaitCursor();

CRectItem* pItem = NULL;
TRY
{
    // First create the C++ object
    pItem = GetDocument()->CreateItem();
    ASSERT_VALID(pItem);

    // Initialize the item from the dialog data.
    if (!dlg.CreateItem(pItem))
        AfxThrowMemoryException();
            // any exception will do
    ASSERT_VALID(pItem);

    // run the object if appropriate
    if (dlg.GetSelectionType() == COleInsertDialog::createNewItem)
        pItem->DoVerb(OLEIVERB_SHOW, this);

    // update right away
    pItem->UpdateLink();
    pItem->UpdateItemRectFromServer();

    // set selection to newly inserted item
    SetSelection(pItem);
    pItem->Invalidate();
}
CATCH (CException, e)
{
    // clean up item
    if (pItem != NULL)
        GetDocument()->DeleteItem(pItem);

    AfxMessageBox(IDP_FAILED_TO_CREATE);
}
END_CATCH

EndWaitCursor();

Poznámka

Vložení nového objektu se může pro vaši aplikaci lišit):

Je také nutné zahrnout <afxodlgs.h>, který obsahuje deklaraci pro třídu dialogového COleInsertObject okna a další standardní dialogy poskytované MFC.

\oclient\mainview.cpp(367) : error C2065: 'OLEVERB_PRIMARY' : undeclared identifier
\oclient\mainview.cpp(367) : error C2660: 'DoVerb' : function does not take 1 parameters

Tyto chyby jsou způsobeny skutečností, že některé konstanty OLE1 se změnily v OLE 2, i když v konceptu jsou stejné. V tomto případě OLEVERB_PRIMARY došlo ke změně na OLEIVERB_PRIMARY. V ole1 i OLE 2 je primární příkaz obvykle spuštěn kontejnerem, když uživatel dvakrát klikne na položku.

Kromě toho DoVerb teď přebírá další parametr – ukazatel na zobrazení (CView*). Tento parametr se používá jenom k implementaci vizuálních úprav (nebo místní aktivace). Prozatím nastavíte tento parametr na hodnotu NULL, protože tuto funkci v tuto chvíli neimimujete.

Pokud chcete zajistit, aby se architektura nikdy nepokoušla o místní aktivaci, měli byste ji přepsat COleClientItem::CanActivate následujícím způsobem:

BOOL CRectItem::CanActivate()
{
    return FALSE;
}
\oclient\rectitem.cpp(53) : error C2065: 'GetBounds' : undeclared identifier
\oclient\rectitem.cpp(53) : error C2064: term does not evaluate to a function
\oclient\rectitem.cpp(84) : error C2065: 'SetBounds' : undeclared identifier
\oclient\rectitem.cpp(84) : error C2064: term does not evaluate to a function

V prostředí MFC/OLE1 COleClientItem::GetBounds a SetBounds byly použity k dotazování a manipulaci s rozsahem položky ( left členové top byli vždy nula). V prostředí MFC/OLE 2 je to přímo podporováno COleClientItem::GetExtent a SetExtent, které řeší velikost nebo CSize místo toho.

Kód pro nové volání SetItemRectToServer a UpdateItemRectFromServer vypadají takto:

BOOL CRectItem::UpdateItemRectFromServer()
{
    ASSERT(m_bTrackServerSize);
    CSize size;
    if (!GetExtent(&size))
        return FALSE;    // blank

    // map from HIMETRIC to screen coordinates
    {
        CClientDC screenDC(NULL);
        screenDC.SetMapMode(MM_HIMETRIC);
        screenDC.LPtoDP(&size);
    }
    // just set the item size
    if (m_rect.Size() != size)
    {
        // invalidate the old size/position
        Invalidate();
        m_rect.right = m_rect.left + size.cx;
        m_rect.bottom = m_rect.top + size.cy;
        // as well as the new size/position
        Invalidate();
    }
    return TRUE;
}

BOOL CRectItem::SetItemRectToServer()
{
    // set the official bounds for the embedded item
    CSize size = m_rect.Size();
    {
        CClientDC screenDC(NULL);
        screenDC.SetMapMode(MM_HIMETRIC);
        screenDC.DPtoLP(&size);
    }
    TRY
    {
        SetExtent(size);    // may do a wait
    }
    CATCH(CException, e)
    {
        return FALSE;  // links will not allow SetBounds
    }
    END_CATCH
    return TRUE;
}
\oclient\frame.cpp(50) : error C2039: 'InWaitForRelease' : is not a member of 'COleClientItem'
\oclient\frame.cpp(50) : error C2065: 'InWaitForRelease' : undeclared identifier
\oclient\frame.cpp(50) : error C2064: term does not evaluate to a function

V mfc/OLE1 synchronní volání rozhraní API z kontejneru na server byly simulovány, protože OLE1 byl v mnoha případech ze své podstaty asynchronní. Před zpracováním příkazů od uživatele bylo nutné zkontrolovat nevyřízených asynchronních volání. MFC/OLE1 poskytla COleClientItem::InWaitForRelease funkci k tomu. V prostředí MFC/OLE 2 to není nutné, takže můžete odebrat přepsání OnCommand v objektu CMainFrame všechny dohromady.

V tomto okamžiku bude OCLIENT kompilovat a propojit.

Další nezbytné změny

Existuje několik věcí, které se neprovedou, aby se ale OCLIENT nespouštět. Místo později je lepší tyto problémy vyřešit.

Nejprve je nutné inicializovat knihovny OLE. To se provádí voláním AfxOleInit z InitInstance:

if (!AfxOleInit())
{
    AfxMessageBox("Failed to initialize OLE libraries");
    return FALSE;
}

Je také vhodné zkontrolovat, jestli virtuální funkce neobsahuje změny seznamu parametrů. Jednou z takových funkcí je COleClientItem::OnChangepřepsání v každé aplikaci kontejneru MFC/OLE. Když se podíváte na online nápovědu, uvidíte, že byla přidána další hodnota DWORD dwParam. Nový CRectItem::OnChange vypadá takto:

void
CRectItem::OnChange(OLE_NOTIFICATION wNotification, DWORD dwParam)
{
    if (m_bTrackServerSize && !UpdateItemRectFromServer())
    {
        // Blank object
        if (wNotification == OLE_CLOSED)
        {
            // no data received for the object - destroy it
            ASSERT(!IsVisible());
            GetDocument()->DeleteItem(this);
            return; // no update (item is gone now)
        }
    }
    if (wNotification != OLE_CLOSED)
        Dirty();
    Invalidate();
    // any change will cause a redraw
}

V prostředí MFC/OLE1 jsou kontejnerové aplikace odvozené z třídy dokumentu .COleClientDoc V prostředí MFC/OLE 2 byla tato třída odebrána a nahrazena COleDocument (tato nová organizace usnadňuje sestavování aplikací typu kontejner/server). Existuje #define, který se mapuje COleClientDoc tak, aby COleDocument zjednodušil přenos aplikací MFC/OLE1 do MFC/OLE 2, například OCLIENT. Jednou z funkcí, které COleDocument nebyly poskytnuty COleClientDoc , je standardní položka mapování zpráv příkazu. To se provádí tak, aby serverové aplikace, které také používají COleDocument (nepřímo), nepřenesly s nimi režijní náklady těchto obslužných rutin příkazů, pokud nejsou kontejnerovou nebo serverovou aplikací. Do mapy zpráv CMainDoc musíte přidat následující položky:

ON_UPDATE_COMMAND_UI(ID_EDIT_PASTE, OnUpdatePasteMenu)
ON_UPDATE_COMMAND_UI(ID_EDIT_PASTE_LINK, OnUpdatePasteLinkMenu)
ON_UPDATE_COMMAND_UI(ID_OLE_EDIT_LINKS, OnUpdateEditLinksMenu)
ON_COMMAND(ID_OLE_EDIT_LINKS, COleDocument::OnEditLinks)
ON_UPDATE_COMMAND_UI(ID_OLE_VERB_FIRST, OnUpdateObjectVerbMenu)
ON_UPDATE_COMMAND_UI(ID_OLE_EDIT_CONVERT, OnUpdateObjectVerbMenu)
ON_COMMAND(ID_OLE_EDIT_CONVERT, OnEditConvert)

Implementace všech těchto příkazů je v COleDocument, což je základní třída pro váš dokument.

V tuto chvíli je OCLIENT funkční aplikace kontejneru OLE. Položky libovolného typu (OLE1 nebo OLE 2) je možné vložit. Vzhledem k tomu, že nezbytný kód pro povolení místní aktivace není implementován, položky se upravují v samostatném okně podobně jako u OLE1. V další části najdete informace o nezbytných změnách, které umožňují místní úpravy (někdy označované jako Vizuální úpravy).

Přidání "vizuálních úprav"

Jednou z nejzajímavějších funkcí OLE je místní aktivace (nebo vizuální úpravy). Tato funkce umožňuje serverové aplikaci převzít části uživatelského rozhraní kontejneru, aby uživateli poskytla plynulejší rozhraní pro úpravy. Pokud chcete implementovat místní aktivaci do OCLIENT, je potřeba přidat některé speciální prostředky a také další kód. Tyto prostředky a kód jsou obvykle poskytovány AppWizard – ve skutečnosti, většina kódu zde byla půjčována přímo z čerstvé aplikace AppWizard s podporou "Container".

Nejprve je nutné přidat prostředek nabídky, který se má použít, pokud je položka, která je aktivní. Tento dodatečný prostředek nabídky můžete vytvořit v jazyce Visual C++ zkopírováním prostředku IDR_OCLITYPE a odebráním všech automaticky otevíraných oken a souborů. Mezi automaticky otevíraná okna Soubor a Okno se vloží dva oddělovače pruhů, které označují oddělení skupin (mělo by vypadat takto: File || Window). Další informace o tom, co tyto oddělovače znamenají a jak jsou nabídky serveru a kontejneru sloučeny, naleznete v tématu Nabídky a prostředky: Slučování nabídek.

Jakmile tyto nabídky vytvoříte, musíte o nich dát vědět rozhraní. To se provádí voláním CDocTemplate::SetContainerInfo šablony dokumentu předtím, než ji přidáte do seznamu šablon dokumentů ve vaší instance InitInstance. Nový kód pro registraci šablony dokumentu vypadá takto:

CDocTemplate* pTemplate = new CMultiDocTemplate(
    IDR_OLECLITYPE,
    RUNTIME_CLASS(CMainDoc),
    RUNTIME_CLASS(CMDIChildWnd), // standard MDI child frame
    RUNTIME_CLASS(CMainView));

pTemplate->SetContainerInfo(IDR_OLECLITYPE_INPLACE);

AddDocTemplate(pTemplate);

Prostředek IDR_OLECLITYPE_INPLACE je speciální místní prostředek vytvořený v jazyce Visual C++.

Pokud chcete povolit místní aktivaci, je potřeba změnit CView v odvozené třídě (CMainView) i COleClientItem v odvozené třídě (CRectItem). Všechna tato přepsání poskytuje AppWizard a většina implementace bude pocházet přímo z výchozí aplikace AppWizard.

V prvním kroku tohoto portu byla místní aktivace zcela zakázána přepsáním COleClientItem::CanActivate. Toto přepsání by mělo být odebráno, aby se povolila místní aktivace. Kromě toho byla hodnota NULL předána všem voláním DoVerb (existují dva z nich), protože poskytnutí zobrazení bylo nezbytné pouze pro místní aktivaci. Pokud chcete plně implementovat místní aktivaci, je nutné předat správné zobrazení ve DoVerb volání. Jedno z těchto volání je v CMainView::OnInsertObject:

pItem->DoVerb(OLEIVERB_SHOW, this);

Další je v CMainView::OnLButtonDblClk:

m_pSelection->DoVerb(OLEIVERB_PRIMARY, this);

Je nutné přepsat COleClientItem::OnGetItemPosition. To serveru řekne, kam má umístit okno vzhledem k okně kontejneru, když je položka aktivována na místě. U OCLIENT je implementace triviální:

void CRectItem::OnGetItemPosition(CRect& rPosition)
{
    rPosition = m_rect;
}

Většina serverů také implementuje místní změnu velikosti. Tím umožníte, aby okno serveru mělo velikost a přesunulo se, když uživatel upravuje položku. Kontejner se musí této akce zúčastnit, protože přesunutí nebo změna velikosti okna obvykle ovlivňuje umístění a velikost v samotném dokumentu kontejneru. Implementace pro OCLIENT synchronizuje interní obdélník udržovaný m_rect s novou polohou a velikostí.

BOOL CRectItem::OnChangeItemPosition(const CRect& rectPos)
{
    ASSERT_VALID(this);

    if (!COleClientItem::OnChangeItemPosition(rectPos))
        return FALSE;

    Invalidate();
    m_rect = rectPos;
    Invalidate();
    GetDocument()->SetModifiedFlag();

    return TRUE;
}

V tomto okamžiku je k dispozici dostatek kódu, který umožní aktivaci položky na místě a řešení změny velikosti a přesunutí položky, když je aktivní, ale uživatel nemůže ukončit relaci úprav. I když některé servery tuto funkci poskytnou samy o sobě zpracováním řídicího klíče, doporučuje se, aby kontejnery poskytovaly dva způsoby deaktivace položky: (1) kliknutím mimo položku a (2) stisknutím klávesy ESCAPE.

Pro klávesu ESCAPE přidejte akcelerátor pomocí jazyka Visual C++, který mapuje klíč VK_ESCAPE na příkaz, ID_CANCEL_EDIT se přidá do prostředků. Obslužná rutina pro tento příkaz:

// The following command handler provides the standard
// keyboard user interface to cancel an in-place
// editing session.void CMainView::OnCancelEdit()
{
    // Close any in-place active item on this view.
    COleClientItem* pActiveItem =
        GetDocument()->GetInPlaceActiveItem(this);
    if (pActiveItem != NULL)
        pActiveItem->Close();
    ASSERT(GetDocument()->GetInPlaceActiveItem(this) == NULL);
}

Chcete-li zpracovat případ, kdy uživatel klikne mimo položku, přidejte následující kód na začátek CMainView::SetSelection:

if (pNewSel != m_pSelection || pNewSel == NULL)
{
    COleClientItem* pActiveItem =
        GetDocument()->GetInPlaceActiveItem(this);
    if (pActiveItem != NULL&& pActiveItem != pNewSel)
        pActiveItem->Close();
}

Pokud je položka na místě aktivní, měla by mít fokus. Abyste měli jistotu, že se jedná o případ, kdy zpracováváte OnSetFocus, aby se fokus při přijetí fokusu vždy přenesl na aktivní položku:

// Special handling of OnSetFocus and OnSize are required
// when an object is being edited in-place.
void CMainView::OnSetFocus(CWnd* pOldWnd)
{
    COleClientItem* pActiveItem =
        GetDocument()->GetInPlaceActiveItem(this);

    if (pActiveItem != NULL &&
        pActiveItem->GetItemState() == COleClientItem::activeUIState)
    {
        // need to set focus to this item if it is same view
        CWnd* pWnd = pActiveItem->GetInPlaceWindow();
        if (pWnd != NULL)
        {
            pWnd->SetFocus();   // don't call the base class
            return;
        }
    }

    CView::OnSetFocus(pOldWnd);
}

Při změně velikosti zobrazení je nutné upozornit aktivní položku, že se změnil obdélník výřezu. K tomu zadáte obslužnou rutinu pro OnSize:

void CMainView::OnSize(UINT nType, int cx, int cy)
{
    CView::OnSize(nType, cx, cy);
    COleClientItem* pActiveItem =
        GetDocument()->GetInPlaceActiveItem(this);
    if (pActiveItem != NULL)
        pActiveItem->SetItemRects();
}

Případová studie: HIERSVR z MFC 2.0

HiERSVR byl také součástí MFC 2.0 a implementovaný OLE s MFC/OLE1. Tato poznámka stručně popisuje kroky, kterými byla tato aplikace původně převedena na použití tříd MFC/OLE 2. Po dokončení počátečního portu bylo přidáno několik funkcí, aby bylo možné lépe znázornit třídy MFC/OLE 2. Tyto funkce zde nebudou pokryty; další informace o těchto pokročilých funkcích najdete v samotné ukázce.

Poznámka

Chyby kompilátoru a podrobný proces byly vytvořeny pomocí visual C++ 2.0. V sadě Visual C++ 4.0 se mohly změnit konkrétní chybové zprávy a umístění, ale koncepční informace zůstávají platné.

Zprovoznění a zprovoznění

Přístup přijatý k přenesení ukázky HIERSVR do MFC/OLE je začít jeho sestavením a opravou jasných chyb kompilátoru, které budou mít za následek. Pokud vezmete ukázku HIERSVR z prostředí MFC 2.0 a zkompilujete ji v rámci této verze mfc, zjistíte, že neexistuje mnoho chyb, které by bylo možné vyřešit (i když existuje více než u ukázky OCLIENT). Chyby v pořadí, v jakém k nim obvykle dochází, jsou popsány níže.

Kompilace a oprava chyb

\hiersvr\hiersvr.cpp(83) : error C2039: 'RunEmbedded' : is not a member of 'COleTemplateServer'

Tato první chyba ukazuje mnohem větší problém s InitInstance funkcí pro servery. Inicializace vyžadovaná pro server OLE je pravděpodobně jednou z největších změn, které budete muset provést v aplikaci MFC/OLE1, aby byla spuštěna. Nejlepší věcí, kterou je potřeba udělat, je podívat se, co AppWizard vytvoří pro server OLE, a podle potřeby upravit kód. Tady je několik bodů, které je potřeba mít na paměti:

Je nutné inicializovat knihovny OLE voláním AfxOleInit

Volání SetServerInfo v objektu šablony dokumentu k nastavení serverových prostředků popisovačů a informací o třídě modulu runtime, které nelze nastavit pomocí konstruktoru CDocTemplate .

Pokud je příkaz /Embedding na příkazovém řádku, nezobrazujte hlavní okno aplikace.

Pro dokument budete potřebovat identifikátor GUID . Jedná se o jedinečný identifikátor typu dokumentu (128 bitů). AppWizard pro vás vytvoří tento kód – takže pokud použijete techniku popsanou zde, když zkopírujete nový kód z nové serverové aplikace vygenerované pomocí AppWizard, můžete jednoduše "ukrást" identifikátor GUID této aplikace. Pokud ne, můžete použít GUIDGEN.EXE nástroj v adresáři BIN.

Je nutné "připojit" objekt COleTemplateServer k šabloně dokumentu voláním COleTemplateServer::ConnectTemplate.

Aktualizujte systémový registr, když je aplikace spuštěná samostatně. Tímto způsobem, pokud uživatel přesune .EXE pro vaši aplikaci, spuštění z jeho nového umístění aktualizuje databázi registrace systému Windows tak, aby odkazovala na nové umístění.

Po použití všech těchto změn na základě toho, co AppWizard vytvoří pro InitInstance, InitInstance by se měl identifikátor (a související identifikátor GUID) pro HIERSVR přečíst takto:

// this is the GUID for HIERSVR documents
static const GUID BASED_CODE clsid =
{ 0xA0A16360L, 0xC19B, 0x101A, { 0x8C, 0xE5, 0x00, 0xDD, 0x01, 0x11, 0x3F, 0x12 } };

/////////////////////////////////////////////////////////////////////////////
// COLEServerApp initialization

BOOL COLEServerApp::InitInstance()
{
    // OLE 2 initialization
    if (!AfxOleInit())
    {
        AfxMessageBox("Initialization of the OLE failed!");
        return FALSE;
    }

    // Standard initialization
    LoadStdProfileSettings();   // Load standard INI file options

    // Register document templates
    CDocTemplate* pDocTemplate;
    pDocTemplate = new CMultiDocTemplate(IDR_HIERSVRTYPE,
        RUNTIME_CLASS(CServerDoc),
        RUNTIME_CLASS(CMDIChildWnd),
        RUNTIME_CLASS(CServerView));
    pDocTemplate->SetServerInfo(IDR_HIERSVRTYPE_SRVR_EMB);
    AddDocTemplate(pDocTemplate);

    // create main MDI Frame window
    CMainFrame* pMainFrame = new CMainFrame;
    if (!pMainFrame->LoadFrame(IDR_MAINFRAME))
        return FALSE;
    m_pMainWnd = pMainFrame;

    SetDialogBkColor(); // gray look

    // enable file manager drag/drop and DDE Execute open
    m_pMainWnd->DragAcceptFiles();
    EnableShellOpen();

    m_server.ConnectTemplate(clsid, pDocTemplate, FALSE);
    COleTemplateServer::RegisterAll();

    // try to launch as an OLE server
    if (RunEmbedded())
    {
        // "short-circuit" initialization -- run as server!
        return TRUE;
    }
    m_server.UpdateRegistry();
    RegisterShellFileTypes();

    // not run as OLE server, so show the main window
    if (m_lpCmdLine[0] == '\0')
    {
        // create a new (empty) document
        OnFileNew();
    }
    else
    {
        // open an existing document
        OpenDocumentFile(m_lpCmdLine);
    }

    pMainFrame->ShowWindow(m_nCmdShow);
    pMainFrame->UpdateWindow();

    return TRUE;
}

Všimněte si, že výše uvedený kód odkazuje na nové ID prostředku IDR_HIERSVRTYPE_SRVR_EMB. Toto je prostředek nabídky, který se má použít při úpravě dokumentu, který je vložen v jiném kontejneru. V prostředí MFC/OLE1 byly položky nabídky specifické pro úpravu vložené položky změněny za běhu. Použití zcela jiné struktury nabídek při úpravě vložené položky místo úprav dokumentu založeného na souboru usnadňuje poskytování různých uživatelských rozhraní pro tyto dva samostatné režimy. Jak uvidíte později, při úpravách vloženého objektu se použije zcela samostatný prostředek nabídky.

Pokud chcete vytvořit tento prostředek, načtěte skript prostředku do jazyka Visual C++ a zkopírujte existující prostředek nabídky IDR_HIERSVRTYPE. Přejmenujte nový prostředek na IDR_HIERSVRTYPE_SRVR_EMB (jedná se o stejnou konvenci pojmenování, kterou Používá AppWizard). Dále změňte "Uložit soubor" na "Aktualizace souborů"; zadejte ID příkazu ID_FILE_UPDATE. Změňte také možnost Uložit soubor jako na "Uložit soubor jako"; zadejte ID příkazu ID_FILE_SAVE_COPY_AS. Architektura poskytuje implementaci obou těchto příkazů.

\hiersvr\svritem.h(60) : error C2433: 'OLESTATUS' : 'virtual' not permitted on data declarations
\hiersvr\svritem.h(60) : error C2501: 'OLESTATUS' : missing decl-specifiers
\hiersvr\svritem.h(60) : error C2146: syntax error : missing ';' before identifier 'OnSetData'
\hiersvr\svritem.h(60) : error C2061: syntax error : identifier 'OLECLIPFORMAT'
\hiersvr\svritem.h(60) : error C2501: 'OnSetData' : missing decl-specifiers

Existuje řada chyb vyplývajících OnSetDataz přepsání , protože odkazuje na typ OLESTATUS . OLESTATUS byl způsob, jakým funkce OLE1 vrátila chyby. To se změnilo na HRESULT v OLE 2, i když MFC obvykle převede HRESULT na COleException obsahující chybu. V tomto konkrétním případě už přepsání OnSetData není nutné, takže nejjednodušší věcí je ji odebrat.

\hiersvr\svritem.cpp(30) : error C2660: 'COleServerItem::COleServerItem' : function does not take 1 parameters

Konstruktor COleServerItem přebírá další parametr BOOL. Tento příznak určuje, jak se správa paměti provádí u COleServerItem objektů. Když ho nastavíte na TRUE, architektura zpracovává správu paměti těchto objektů – odstraní je, když už nejsou nezbytné. HIERSVR používá CServerItem objekty (odvozené od COleServerItem) jako součást svých nativních dat, takže tento příznak nastavíte na FALSE. Díky tomu může HIERSVR určit, kdy se každá položka serveru odstraní.

\hiersvr\svritem.cpp(44) : error C2259: 'CServerItem' : illegal attempt to instantiate abstract class
\hiersvr\svritem.cpp(44) : error C2259: 'CServerItem' : illegal attempt to instantiate abstract class

Jak tyto chyby naznačují, existují některé funkce pure-virtual, které nebyly přepsány v CServerItem. Nejpravděpodobnější příčinou je skutečnost, že se změnil seznam parametrů OnDraw. Pokud chcete tuto chybu opravit, změňte CServerItem::OnDraw ji následujícím způsobem (a také deklaraci v souboru svritem.h):

BOOL CServerItem::OnDraw(CDC* pDC, CSize& rSize)
{
    // request from OLE to draw node
    pDC->SetMapMode(MM_TEXT); // always in pixels
    return DoDraw(pDC, CPoint(0, 0), FALSE);
}

Nový parametr je rSize. To vám umožní vyplnit velikost výkresu, pokud je to vhodné. Tato velikost musí být v HIMETRICE. V tomto případě není vhodné vyplnit tuto hodnotu, takže volání OnGetExtent architektury pro načtení rozsahu. Aby to fungovalo, budete muset implementovat OnGetExtent:

BOOL CServerItem::OnGetExtent(DVASPECT dwDrawAspect, CSize& rSize)
{
    if (dwDrawAspect != DVASPECT_CONTENT)
        return COleServerItem::OnGetExtent(dwDrawAspect, rSize);

    rSize = CalcNodeSize();
    return TRUE;
}
\hiersvr\svritem.cpp(104) : error C2065: 'm_rectBounds' : undeclared identifier
\hiersvr\svritem.cpp(104) : error C2228: left of '.SetRect' must have class/struct/union type
\hiersvr\svritem.cpp(106) : error C2664: 'void __pascal __far DPtoLP(struct ::tagPOINT __far *,
    int)__far const ' : cannot convert parameter 1 from 'int __far *' to 'struct ::tagPOINT __far *'

Ve funkci CServerItem::CalcNodeSize je velikost položky převedena na HIMETRIC a uložena v m_rectBounds. Nezdokumentovaný člen "m_rectBounds" neexistuje (byl částečně nahrazen m_sizeExtent, ale v OLE 2 má tento člen mírně jiné použití než m_rectBounds COleServerItem v OLE1). Místo nastavení velikosti HIMETRIC do této členské proměnné ji vrátíte. Tato návratová hodnota se používá v OnGetExtentdříve implementované.

CSize CServerItem::CalcNodeSize()
{
    CClientDC dcScreen(NULL);

    m_sizeNode = dcScreen.GetTextExtent(m_strDescription,
        m_strDescription.GetLength());
    m_sizeNode += CSize(CX_INSET * 2, CY_INSET * 2);

    // set suggested HIMETRIC size
    CSize size(m_sizeNode.cx, m_sizeNode.cy);
    dcScreen.SetMapMode(MM_HIMETRIC);
    dcScreen.DPtoLP(&size);
    return size;
}

CServerItem také přepisuje COleServerItem::OnGetTextData. Tato funkce je zastaralá v prostředí MFC/OLE a nahrazuje se jiným mechanismem. Verze MFC 3.0 ukázky MFC OLE HIERSVR implementuje tuto funkci přepsáním COleServerItem::OnRenderFileData. Tato funkce není pro tento základní port důležitá, takže můžete přepsání OnGetTextData odebrat.

V souboru svritem.cpp existuje mnoho dalších chyb, které nebyly vyřešeny. Nejedná se o skutečné chyby – pouze chyby způsobené předchozími chybami.

\hiersvr\svrview.cpp(325) : error C2660: 'CopyToClipboard' : function does not take 2 parameters

COleServerItem::CopyToClipboard příznak už nepodporuje bIncludeNative . Nativní data (data zapsaná funkcí Serializace položky serveru) se vždy zkopírují, takže odeberete první parametr. Kromě toho vyvolá výjimku, CopyToClipboard když dojde k chybě místo vrácení NEPRAVDA. Změňte kód pro CServerView::OnEditCopy následujícím způsobem:

void CServerView::OnEditCopy()
{
    if (m_pSelectedNode == NULL)
        AfxThrowNotSupportedException();

    TRY
    {
        m_pSelectedNode->CopyToClipboard(TRUE);
    }
    CATCH_ALL(e)
    {
        AfxMessageBox("Copy to clipboard failed");
    }
    END_CATCH_ALL
}

I když došlo k více chybám vyplývajícím z kompilace verze KNIHOVNY MFC 2.0 HIERSVR, než byla pro stejnou verzi OCLIENT, ve skutečnosti došlo k menšímu počtu změn.

V tomto okamžiku HIERSVR zkompiluje a bude fungovat jako server OLE, ale bez místní funkce pro úpravy, která bude implementována dále.

Přidání "vizuálních úprav"

Pokud chcete do této serverové aplikace přidat "Vizuální úpravy" (nebo místní aktivaci), musíte se postarat jen o několik věcí:

  • Pokud je položka aktivní, potřebujete speciální prostředek nabídky.

  • Tato aplikace obsahuje panel nástrojů, takže budete potřebovat panel nástrojů s pouze podmnožinou normálního panelu nástrojů, aby odpovídala příkazům nabídky dostupným na serveru (odpovídá výše uvedenému prostředku nabídky).

  • Potřebujete novou třídu odvozenou z COleIPFrameWnd toho, která poskytuje místní uživatelské rozhraní (podobně jako CMainFrame odvozené od CMDIFrameWnd, poskytuje uživatelské rozhraní MDI).

  • O těchto speciálních prostředcích a třídách musíte rozhraní sdělit.

Prostředek nabídky se snadno vytvoří. Spusťte Visual C++, zkopírujte prostředek nabídky IDR_HIERSVRTYPE do prostředku nabídky s názvem IDR_HIERSVRTYPE_SRVR_IP. Upravte nabídku tak, aby zůstaly pouze místní nabídky Upravit a Nápověda. Mezi nabídky Upravit a Nápověda přidejte do nabídky dva oddělovače (měl by vypadat takto: Edit || Help). Další informace o tom, co tyto oddělovače znamenají a jak se slučují nabídky serveru a kontejneru, najdete v tématu Nabídky a prostředky: Slučování nabídek.

Rastrový obrázek pro panel nástrojů podmnožina lze snadno vytvořit zkopírováním z nové aplikace vygenerované AppWizard s zaškrtnutou možností Server. Tento rastrový obrázek je pak možné importovat do jazyka Visual C++. Nezapomeňte rastrový obrázek poskytnout ID IDR_HIERSVRTYPE_SRVR_IP.

Třída odvozená z COleIPFrameWnd lze zkopírovat z aplikace vygenerované AppWizard s podporou serveru. Zkopírujte oba soubory, IPFRAME. CPP a IPFRAME. H a přidejte je do projektu. Ujistěte se, že LoadBitmap volání odkazuje na IDR_HIERSVRTYPE_SRVR_IP, rastrový obrázek vytvořený v předchozím kroku.

Teď, když jsou vytvořeny všechny nové prostředky a třídy, přidejte potřebný kód, aby o nich architektura věděla (a ví, že tato aplikace teď podporuje místní úpravy). To se provádí přidáním několika dalších parametrů do SetServerInfo volání funkce InitInstance :

pDocTemplate->SetServerInfo(IDR_HIERSVRTYPE_SRVR_EMB,
    IDR_HIERSVRTYPE_SRVR_IP,
    RUNTIME_CLASS(CInPlaceFrame));

Teď je připravená na místní spuštění v jakémkoli kontejneru, který podporuje také místní aktivaci. V kódu ale stále číhá jedna menší chyba. HIERSVR podporuje místní nabídku, která se zobrazí, když uživatel stiskne pravé tlačítko myši. Tato nabídka funguje, když je HIERSVR plně otevřený, ale nefunguje při úpravách vkládání na místě. Důvod je možné připnout na tento jeden řádek kódu v CServerView::OnRButtonDown:

pMenu->TrackPopupMenu(TPM_CENTERALIGN | TPM_RIGHTBUTTON,
    point.x,
    point.y,
    AfxGetApp()->m_pMainWnd);

Všimněte si odkazu na AfxGetApp()->m_pMainWnd. Když je server aktivovaný na místě, má hlavní okno a m_pMainWnd je nastaven, ale obvykle je neviditelný. Kromě toho toto okno odkazuje na hlavní okno aplikace, okno rámce MDI, které se zobrazí, když je server plně otevřený nebo běží samostatně. Neodkazuje na aktivní okno rámečku , které je při místní aktivaci rámečku okno odvozené z COleIPFrameWnd. Chcete-li získat správné aktivní okno i při místních úpravách, tato verze MFC přidá novou funkci, AfxGetMainWnd. Obecně byste tuto funkci měli použít místo AfxGetApp()->m_pMainWnd. Tento kód se musí změnit následujícím způsobem:

pMenu->TrackPopupMenu(TPM_CENTERALIGN | TPM_RIGHTBUTTON,
    point.x,
    point.y,
    AfxGetMainWnd());

Teď máte server OLE minimálně povolený pro funkční místní aktivaci. V prostředí MFC/OLE 2 je však stále k dispozici mnoho funkcí, které nebyly v prostředí MFC/OLE1 k dispozici. Další nápady na funkce, které byste mohli chtít implementovat, najdete v ukázce HIERSVR. Některé z funkcí, které HIERSVR implementují, jsou uvedené níže:

  • Přiblížení, pro skutečné chování WYSIWYG s ohledem na kontejner.

  • Přetáhněte nebo přetáhněte a vlastní formát schránky.

  • Posouvání okna kontejneru při změně výběru

Ukázka HIERSVR v prostředí MFC 3.0 také používá mírně odlišný návrh pro položky serveru. To pomáhá šetřit paměť a díky tomu jsou vaše odkazy flexibilnější. S 2.0 verze HIERSVR každý uzel ve stromu je-aCOleServerItem. COleServerItem nese trochu větší režii, než je nezbytně nutné pro každý z těchto uzlů, ale COleServerItem vyžaduje se pro každé aktivní propojení. Ale ve většině případů existuje velmi málo aktivních odkazů v daném okamžiku. Aby to bylo efektivnější, HIERSVR v této verzi MFC odděluje uzel od COleServerItemuzlu . Má CServerNode i CServerItem třídu. ( CServerItem odvozeno) COleServerItemse vytvoří pouze podle potřeby. Jakmile kontejner (nebo kontejnery) přestane používat tento konkrétní odkaz na daný uzel, objekt CServerItem přidružený k CServerNode se odstraní. Tento návrh je efektivnější a flexibilnější. Při práci s více výběrovými odkazy přichází její flexibilita. Žádná z těchto dvou verzí HIERSVR nepodporuje vícenásobný výběr, ale bylo by mnohem jednodušší přidat (a podporovat odkazy na takové výběry) s verzí KNIHOVNY MFC 3.0 HIERSVR, protože COleServerItem je oddělená od nativních dat.

Viz také

Technické poznámky podle čísel
Technické poznámky podle kategorií