Udostępnij za pośrednictwem


TN041: migracja z MFC/OLE1 do MFC/OLE 2

Uwaga

Następująca uwaga techniczna nie została zaktualizowana, ponieważ została po raz pierwszy uwzględniona w dokumentacji online. W związku z tym niektóre procedury i tematy mogą być nieaktualne lub nieprawidłowe. Aby uzyskać najnowsze informacje, zaleca się wyszukanie interesującego tematu w indeksie dokumentacji online.

Ogólne problemy związane z migracją

Jednym z celów projektowych klas OLE 2 w MFC 2.5 (i nowszych) było zachowanie dużej części tej samej architektury wprowadzonej w MFC 2.0 dla obsługi OLE 1.0. W związku z tym wiele z tych samych klas OLE w MFC 2.0 nadal istnieje w tej wersji MFC (COleDocument, COleServerDoc, COleClientItem, COleServerItem). Ponadto wiele interfejsów API w tych klasach jest dokładnie takie same. Jednak ole 2 różni się znacząco od OLE 1.0, więc można się spodziewać, że niektóre szczegóły uległy zmianie. Jeśli znasz obsługę OLE1 MFC 2.0, poczujesz się w domu z obsługą MFC 2.0.

Jeśli korzystasz z istniejącej aplikacji MFC/OLE1 i dodasz do niej funkcję OLE 2, najpierw przeczytaj tę notatkę. Ta uwaga obejmuje niektóre ogólne problemy, które mogą wystąpić podczas przenoszenia funkcji OLE1 do MFC/OLE 2, a następnie omawia wykryte problemy podczas przenoszenia dwóch aplikacji zawartych w MFC 2.0: przykłady MFC OLE OCLIENT i HIERSVR.

Architektura dokumentu/widoku MFC jest ważna

Jeśli aplikacja nie korzysta z architektury dokumentów/widoków MFC i chcesz dodać obsługę ole 2 do aplikacji, nadszedł czas, aby przejść do dokumentu/widoku. Wiele zalet klas OLE 2 MFC jest realizowane tylko wtedy, gdy aplikacja korzysta z wbudowanej architektury i składników MFC.

Implementowanie serwera lub kontenera bez korzystania z architektury MFC jest możliwe, ale nie jest zalecane.

Używanie implementacji MFC zamiast własnej

Klasy MFC "implementacja w puszce", takie jak CToolBar, CStatusBari CScrollView mają wbudowany specjalny kod przypadku dla obsługi OLE 2. Jeśli więc możesz użyć tych klas w aplikacji, skorzystaj z nakładu pracy wprowadzonego w celu poinformowania ich o ole. Ponownie, istnieje możliwość "roll-your-own" klas w tym miejscu do tych celów, ale nie jest to sugerowane. Jeśli musisz zaimplementować podobne funkcje, kod źródłowy MFC jest doskonałym odwołaniem do obsługi niektórych bardziej precyzyjnych punktów OLE (zwłaszcza jeśli chodzi o aktywację w miejscu).

Badanie przykładowego kodu MFC

Istnieje wiele przykładów MFC, które obejmują funkcje OLE. Każda z tych aplikacji implementuje ole z innego kąta:

  • HIERSVR przeznaczone głównie do użycia jako aplikacja serwerowa. Został on uwzględniony w MFC 2.0 jako aplikacja MFC/OLE1 i został przekierowany do MFC/OLE 2, a następnie rozszerzony tak, aby implementował wiele funkcji OLE dostępnych w OLE 2.

  • OCLIENT Jest to autonomiczna aplikacja kontenera przeznaczona do zademonstrowania wielu funkcji OLE z punktu widzenia kontenera. Został on również przekierowany z MFC 2.0, a następnie rozszerzony o obsługę wielu bardziej zaawansowanych funkcji OLE, takich jak niestandardowe formaty schowka i linki do elementów osadzonych.

  • DRAWCLI Ta aplikacja implementuje obsługę kontenera OLE, podobnie jak program OCLIENT, z tą różnicą, że robi to w ramach istniejącego programu rysunkowego zorientowanego na obiekt. Pokazuje on, jak można zaimplementować obsługę kontenera OLE i zintegrować go z istniejącą aplikacją.

  • SUPERPAD Ta aplikacja, a także dobra aplikacja autonomiczna, jest również serwerem OLE. Obsługa serwera, który implementuje, jest dość minimalistyczna. Szczególnie interesujące jest użycie usług schowka OLE do kopiowania danych do schowka, ale używa funkcji wbudowanych w kontrolkę "edytuj" systemu Windows w celu zaimplementowania funkcji wklejania schowka. Pokazuje to interesującą mieszankę tradycyjnego użycia interfejsu API systemu Windows, a także integrację z nowymi interfejsami API OLE.

Aby uzyskać więcej informacji na temat przykładowych aplikacji, zobacz "Przykładowa pomoc MFC".

Analiza przypadku: OCLIENT z MFC 2.0

Jak wspomniano powyżej, OCLIENT został uwzględniony w MFC 2.0 i zaimplementowano ole z MFC/OLE1. Poniżej opisano kroki, w których ta aplikacja została początkowo przekonwertowana w celu użycia klas MFC/OLE 2. Po ukończeniu początkowego portu dodano wiele funkcji, aby lepiej zilustrować klasy MFC/OLE. Te funkcje nie zostaną tutaj omówione; Zapoznaj się z samym przykładem, aby uzyskać więcej informacji na temat tych zaawansowanych funkcji.

Uwaga

Błędy kompilatora i proces krok po kroku zostały utworzone za pomocą programu Visual C++ 2.0. Określone komunikaty o błędach i lokalizacje mogły ulec zmianie w programie Visual C++ 4.0, ale informacje koncepcyjne pozostają prawidłowe.

Rozpoczynanie pracy

Podejście do przenoszenia przykładu OCLIENT do MFC/OLE polega na rozpoczęciu jego tworzenia i naprawianiu oczywistych błędów kompilatora, które będą skutkować. Jeśli weźmiesz przykład OCLIENT z MFC 2.0 i skompilujesz go w tej wersji MFC, okaże się, że nie ma zbyt wielu błędów do rozwiązania. Błędy w kolejności, w jakiej wystąpiły, zostały opisane poniżej.

Kompilowanie i naprawianie błędów

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

Pierwszy błąd dotyczy .COleClientItem::Draw W MFC/OLE1 zajęło więcej parametrów niż przyjmuje wersja MFC/OLE. Dodatkowe parametry często nie były konieczne i zwykle mają wartość NULL (jak w tym przykładzie). Ta wersja MFC może automatycznie określić wartości dla lpWBounds, gdy kontroler CDC, do którego jest rysowany, jest metaplikowym kontrolerem domeny. Ponadto parametr pFormatDC nie jest już konieczny, ponieważ struktura skompiluje jeden z "atrybutu kontrolera domeny" przekazanego przez centrum dystrybucji kluczy. Aby rozwiązać ten problem, wystarczy usunąć dwa dodatkowe parametry NULL do wywołania 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 '

Powyższe błędy wynikają z faktu, że wszystkie COleClientItem::CreateXXXX funkcje w MFC/OLE1 wymagały przekazania unikatowej nazwy do reprezentowania elementu. Było to wymaganie podstawowego interfejsu API OLE. Nie jest to konieczne w MFC/OLE 2, ponieważ OLE 2 nie używa DDE jako podstawowego mechanizmu komunikacji (nazwa była używana w rozmowach DDE). Aby rozwiązać ten problem, możesz usunąć CreateNewName funkcję, a także wszystkie odwołania do niej. Łatwo jest dowiedzieć się, czego oczekuje każda funkcja MFC/OLE w tej wersji, umieszczając kursor na wywołaniu i naciskając klawisz F1.

Innym obszarem, który jest znacznie inny, jest obsługa schowka OLE 2. W przypadku ole1 użyto interfejsów API schowka systemu Windows, które współdziałają ze schowkiem. W przypadku ole 2 odbywa się to za pomocą innego mechanizmu. Interfejsy API MFC/OLE1 zakładały, że schowek został otwarty przed skopiowaniem COleClientItem obiektu do schowka. Nie jest to już konieczne i spowoduje niepowodzenie wszystkich operacji schowka MFC/OLE. Edytując kod w celu usunięcia zależności od CreateNewNameprogramu , należy również usunąć kod, który zostanie otwarty i zamyka schowek systemu 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'

Te błędy wynikają z CMainView::OnInsertObject programu obsługi. Obsługa polecenia "Wstaw nowy obiekt" to kolejny obszar, w którym elementy uległy zmianie. W takim przypadku najłatwiej jest po prostu scalić oryginalną implementację z elementem dostarczonym przez aplikację AppWizard dla nowej aplikacji kontenera OLE. W rzeczywistości jest to technika, którą można zastosować do przenoszenia innych aplikacji. W MFC/OLE1 zostanie wyświetlone okno dialogowe "Wstaw obiekt", wywołując AfxOleInsertDialog funkcję . W tej wersji utworzysz obiekt okna dialogowego i wywołasz metodę COleInsertObject DoModal. Ponadto nowe elementy OLE są tworzone za pomocą identyfikatora CLSID zamiast ciągu classname. Wynik końcowy powinien wyglądać mniej więcej tak:

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();

Uwaga

Wstaw nowy obiekt może być inny dla aplikacji):

Należy również uwzględnić <afxodlgs.h>, który zawiera deklarację klasy dialogowej COleInsertObject , a także inne standardowe okna dialogowe dostarczone przez MFC.

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

Te błędy są spowodowane faktem, że niektóre stałe OLE1 uległy zmianie w ole 2, mimo że w koncepcji są takie same. W tym przypadku OLEVERB_PRIMARY zmieniono na OLEIVERB_PRIMARY. Zarówno w przypadku ole1, jak i OLE 2, czasownik podstawowy jest zwykle wykonywany przez kontener, gdy użytkownik kliknie dwukrotnie element.

Ponadto DoVerb teraz pobiera dodatkowy parametr — wskaźnik do widoku (CView*). Ten parametr jest używany tylko do implementowania "edycji wizualnej" (lub aktywacji w miejscu). Na razie ustawisz ten parametr na wartość NULL, ponieważ w tej chwili nie implementujesz tej funkcji.

Aby upewnić się, że struktura nigdy nie próbuje aktywować w miejscu, należy przesłonić COleClientItem::CanActivate w następujący sposób:

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

W MFC/OLE1 COleClientItem::GetBounds użyto SetBounds metody do wykonywania zapytań i manipulowania zakresem elementu (składowe left i top były zawsze zerowe). W MFC/OLE 2 jest to bardziej bezpośrednio obsługiwane przez COleClientItem::GetExtent elementy i SetExtent, które zajmują się rozmiarem lub CSize zamiast tego.

Kod nowego wywołania SetItemRectToServer i UpdateItemRectFromServer wyglądają następująco:

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

W przypadku wywołań interfejsu API synchronicznego MFC/OLE1 z kontenera na serwer były symulowane, ponieważ ole1 było z natury asynchroniczne w wielu przypadkach. Przed przetworzeniem poleceń od użytkownika konieczne było sprawdzenie zaległego wywołania asynchronicznego. MFC/OLE1 dostarczył funkcji COleClientItem::InWaitForRelease w tym celu. W MFC/OLE 2 nie jest to konieczne, więc można usunąć przesłonięcia polecenia OnCommand w CMainFrame razem.

W tym momencie program OCLIENT skompiluje i połączy.

Inne niezbędne zmiany

Nie ma jednak kilku rzeczy, które nie spowodują uruchomienia klienta OCLIENT. Lepiej jest rozwiązać te problemy teraz, a nie później.

Najpierw należy zainicjować biblioteki OLE. Odbywa się to za pomocą wywołania metody AfxOleInit :InitInstance

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

Dobrym pomysłem jest również sprawdzenie funkcji wirtualnych pod kątem zmian listy parametrów. Jedną z takich funkcji jest COleClientItem::OnChange, przesłonięć w każdej aplikacji kontenera MFC/OLE. Patrząc na pomoc online, zobaczysz, że dodano dodatkowy "DWORD dwParam". Nowy CRectItem::OnChange wygląda następująco:

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
}

W MFC/OLE1 aplikacje kontenerów pochodnych klasy dokumentu z COleClientDocklasy . W MFC/OLE 2 ta klasa została usunięta i zastąpiona przez COleDocument (ta nowa organizacja ułatwia tworzenie aplikacji kontenera/serwera). Istnieje #define mapowania COleClientDoc w celu COleDocument uproszczenia przenoszenia aplikacji MFC/OLE1 do MFC/OLE 2, takich jak OCLIENT. Jedną z funkcji, które nie zostały dostarczone przez COleDocument COleClientDoc program, jest standardowe wpisy mapy komunikatów polecenia. Dzieje się tak, aby aplikacje serwera, które również używają COleDocument (pośrednio), nie przewożą z nimi obciążeń związanych z tymi procedurami obsługi poleceń, chyba że są aplikacją kontenera/serwera. Należy dodać następujące wpisy do mapy komunikatów CMainDoc:

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)

Implementacja wszystkich tych poleceń znajduje się w COleDocumentpliku , który jest klasą bazową dokumentu.

W tym momencie OCLIENT jest funkcjonalną aplikacją kontenera OLE. Istnieje możliwość wstawienia elementów dowolnego typu (OLE1 lub OLE 2). Ponieważ nie zaimplementowano kodu niezbędnego do włączenia aktywacji w miejscu, elementy są edytowane w osobnym oknie podobnie jak w przypadku ole1. W następnej sekcji omówiono niezbędne zmiany umożliwiające edytowanie w miejscu (czasami nazywane "edycją wizualizacji").

Dodawanie "Edycji wizualnej"

Jedną z najbardziej interesujących funkcji OLE jest aktywacja w miejscu (lub "Edycja wizualna"). Ta funkcja umożliwia aplikacji serwera przejęcie części interfejsu użytkownika kontenera w celu zapewnienia bardziej bezproblemowego interfejsu edycji dla użytkownika. Aby zaimplementować aktywację w miejscu do klienta OCLIENT, należy dodać kilka specjalnych zasobów, a także dodatkowy kod. Te zasoby i kod są zwykle dostarczane przez aplikację AppWizard — w rzeczywistości większość kodu w tym miejscu została pożyczona bezpośrednio z nowej aplikacji AppWizard z obsługą "Kontener".

Przede wszystkim należy dodać zasób menu, który ma być używany, gdy istnieje element, który jest aktywny w miejscu. Ten dodatkowy zasób menu można utworzyć w programie Visual C++, kopiując zasób IDR_OCLITYPE i usuwając wszystkie okienka podręczne Plik i okno. Dwa paski separatora są wstawiane między wyskakującym okienkami Plik i Okno, aby wskazać rozdzielenie grup (powinno to wyglądać następująco: File || Window). Aby uzyskać więcej informacji na temat znaczenia tych separatorów oraz sposobu scalania menu serwera i kontenera, zobacz Menu i zasoby: Scalanie menu.

Po utworzeniu tych menu należy poinformować platformę o nich. Jest to wykonywane przez wywołanie CDocTemplate::SetContainerInfo szablonu dokumentu przed dodaniem go do listy szablonów dokumentów w initInstance. Nowy kod do zarejestrowania szablonu dokumentu wygląda następująco:

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);

Zasób IDR_OLECLITYPE_INPLACE to specjalny zasób w miejscu utworzony w języku Visual C++.

Aby włączyć aktywację w miejscu, należy zmienić zarówno CView klasę pochodną (CMainView), jak i klasę pochodną COleClientItem (CRectItem). Wszystkie te przesłonięcia są dostarczane przez aplikację AppWizard, a większość implementacji będzie pochodzić bezpośrednio z domyślnej aplikacji AppWizard.

W pierwszym kroku tego portu aktywacja w miejscu została całkowicie wyłączona przez zastąpienie elementu COleClientItem::CanActivate. To zastąpienie powinno zostać usunięte, aby umożliwić aktywację w miejscu. Ponadto wartość NULL została przekazana do wszystkich wywołań DoVerb (istnieją dwa z nich), ponieważ udostępnienie widoku było konieczne tylko do aktywacji w miejscu. Aby w pełni zaimplementować aktywację w miejscu, należy przekazać prawidłowy widok w wywołaniu DoVerb . Jedno z tych wywołań ma wartość :CMainView::OnInsertObject

pItem->DoVerb(OLEIVERB_SHOW, this);

Inny element znajduje się w pliku CMainView::OnLButtonDblClk:

m_pSelection->DoVerb(OLEIVERB_PRIMARY, this);

Należy zastąpić COleClientItem::OnGetItemPositionwartość . Informuje to serwer, gdzie należy umieścić okno względem okna kontenera, gdy element jest aktywowany w miejscu. W przypadku klienta OCLIENT implementacja jest banalna:

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

Większość serwerów implementuje również to, co nazywa się "zmianą rozmiaru w miejscu". Dzięki temu okno serwera może mieć rozmiar i przenieść podczas edytowania elementu przez użytkownika. Kontener musi uczestniczyć w tej akcji, ponieważ przeniesienie lub zmiana rozmiaru okna zwykle wpływa na położenie i rozmiar samego dokumentu kontenera. Implementacja OCLIENT synchronizuje wewnętrzny prostokąt obsługiwany przez m_rect z nową pozycją i rozmiarem.

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

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

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

    return TRUE;
}

W tym momencie jest wystarczająca ilość kodu, aby umożliwić aktywowanie elementu w miejscu i radzenie sobie z ustalaniem rozmiaru i przenoszeniem elementu, gdy jest aktywny, ale żaden kod nie umożliwi użytkownikowi zakończenia sesji edycji. Chociaż niektóre serwery udostępniają tę funkcję samodzielnie, obsługując klucz ucieczki, zaleca się, aby kontenery udostępniały dwa sposoby dezaktywowania elementu: (1) przez kliknięcie poza elementem i (2) przez naciśnięcie klawisza ESCAPE.

W przypadku klucza ESCAPE dodaj akcelerator w języku Visual C++, który mapuje klucz VK_ESCAPE na polecenie, ID_CANCEL_EDIT jest dodawany do zasobów. Procedura obsługi dla tego polecenia jest następująca:

// 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);
}

Aby obsłużyć przypadek, w którym użytkownik kliknie poza elementem, dodaj następujący kod na początku CMainView::SetSelectionelementu :

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

Gdy element jest aktywny w miejscu, powinien mieć fokus. Aby upewnić się, że tak jest w przypadku obsługi fokusu OnSetFocus, aby fokus był zawsze przenoszony do aktywnego elementu, gdy widok odbiera fokus:

// 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);
}

Po zmianie rozmiaru widoku należy powiadomić aktywny element o zmianie prostokąta wycinków. W tym celu należy podać procedurę obsługi dla OnSizeprogramu :

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

Analiza przypadku: HIERSVR z MFC 2.0

HiERSVR został również uwzględniony w MFC 2.0 i zaimplementowano ole z MFC/OLE1. Ta uwaga krótko opisuje kroki, za pomocą których ta aplikacja została początkowo przekonwertowana w celu użycia klas MFC/OLE 2. Po ukończeniu początkowego portu dodano wiele funkcji, aby lepiej zilustrować klasy MFC/OLE 2. Te funkcje nie zostaną tutaj omówione; Zapoznaj się z samym przykładem, aby uzyskać więcej informacji na temat tych zaawansowanych funkcji.

Uwaga

Błędy kompilatora i proces krok po kroku zostały utworzone za pomocą programu Visual C++ 2.0. Określone komunikaty o błędach i lokalizacje mogły ulec zmianie w programie Visual C++ 4.0, ale informacje koncepcyjne pozostają prawidłowe.

Rozpoczynanie pracy

Podejście do przenoszenia przykładu HIERSVR do MFC/OLE polega na rozpoczęciu jego tworzenia i naprawianiu oczywistych błędów kompilatora, które będą skutkować. Jeśli weźmiesz przykład HIERSVR z MFC 2.0 i skompilujesz go w tej wersji MFC, okaże się, że nie ma wielu błędów do rozwiązania (chociaż istnieje więcej niż w przykładzie OCLIENT). Błędy w kolejności, w której zwykle występują, są opisane poniżej.

Kompilowanie i naprawianie błędów

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

Ten pierwszy błąd wskazuje znacznie większy problem z funkcją InitInstance dla serwerów. Inicjowanie wymagane dla serwera OLE jest prawdopodobnie jedną z największych zmian, które należy wprowadzić w aplikacji MFC/OLE1, aby ją uruchomić. Najlepszym rozwiązaniem jest zapoznanie się z tym, co aplikacja AppWizard tworzy dla serwera OLE i odpowiednio zmodyfikuj kod. Oto kilka kwestii, o których należy pamiętać:

Konieczne jest zainicjowanie bibliotek OLE przez wywołanie metody AfxOleInit

Wywołaj metodę SetServerInfo w obiekcie szablonu dokumentu, aby ustawić uchwyty zasobów serwera i informacje o klasie środowiska uruchomieniowego, których nie można ustawić za pomocą konstruktora CDocTemplate .

Nie pokazuj głównego okna aplikacji, jeśli /Embedding jest obecny w wierszu polecenia.

Będziesz potrzebować identyfikatora GUID dla dokumentu. Jest to unikatowy identyfikator typu dokumentu (128 bitów). Aplikacja AppWizard utworzy ją dla Ciebie — więc jeśli użyjesz techniki opisanej tutaj podczas kopiowania nowego kodu z nowej aplikacji serwera wygenerowanej przez aplikację AppWizard, możesz po prostu "ukraść" identyfikator GUID z tej aplikacji. Jeśli nie, możesz użyć narzędzia GUIDGEN.EXE w katalogu BIN.

Konieczne jest połączenie COleTemplateServer obiektu z szablonem dokumentu przez wywołanie metody COleTemplateServer::ConnectTemplate.

Zaktualizuj rejestr systemowy, gdy aplikacja jest uruchamiana autonomicznie. W ten sposób, jeśli użytkownik przeniesie plik EXE dla aplikacji, uruchomienie go z nowej lokalizacji spowoduje zaktualizowanie bazy danych rejestracji systemu Windows w celu wskazania nowej lokalizacji.

Po zastosowaniu wszystkich tych zmian w oparciu o to, co aplikacja AppWizard tworzy dla InitInstance, InitInstance (i powiązany identyfikator GUID) dla HIERSVR powinien być odczytany w następujący sposób:

// 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;
}

Zauważysz, że powyższy kod odnosi się do nowego identyfikatora zasobu, IDR_HIERSVRTYPE_SRVR_EMB. Jest to zasób menu, który ma być używany, gdy dokument osadzony w innym kontenerze jest edytowany. W MFC/OLE1 elementy menu specyficzne dla edytowania osadzonego elementu zostały zmodyfikowane na bieżąco. Używanie zupełnie innej struktury menu podczas edytowania elementu osadzonego zamiast edytowania dokumentu opartego na plikach znacznie ułatwia udostępnianie różnych interfejsów użytkownika dla tych dwóch oddzielnych trybów. Jak zobaczysz później, podczas edytowania osadzonego obiektu jest używany całkowicie oddzielny zasób menu.

Aby utworzyć ten zasób, załaduj skrypt zasobu do programu Visual C++ i skopiuj istniejący zasób menu IDR_HIERSVRTYPE. Zmień nazwę nowego zasobu na IDR_HIERSVRTYPE_SRVR_EMB (jest to ta sama konwencja nazewnictwa używana przez aplikację AppWizard). Następna zmiana opcji "Zapisz plik" na "Aktualizacja pliku"; nadaj mu identyfikator polecenia ID_FILE_UPDATE. Zmień również "Plik Zapisz jako" na "Plik Zapisz kopię jako"; nadaj mu identyfikator polecenia ID_FILE_SAVE_COPY_AS. Platforma zapewnia implementację obu tych poleceń.

\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

Istnieje wiele błędów wynikających z przesłonięcia OnSetDataelementu , ponieważ odnosi się do typu OLESTATUS . OLESTATUS był sposobem, w jaki OLE1 zwrócił błędy. Ta zmiana na HRESULT w OLE 2, chociaż MFC zwykle konwertuje HRESULT na COleException zawierający błąd. W tym konkretnym przypadku zastąpienie OnSetData elementu nie jest już konieczne, więc najprostszą rzeczą do zrobienia jest jego usunięcie.

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

Konstruktor COleServerItem przyjmuje dodatkowy parametr "BOOL". Ta flaga określa sposób zarządzania pamięcią COleServerItem na obiektach. Ustawiając ją na true, platforma obsługuje zarządzanie pamięcią tych obiektów — usuwając je, gdy nie są już potrzebne. Funkcja HIERSVR używa CServerItem (pochodzącego z COleServerItem) obiektów w ramach danych natywnych, więc ustawisz tę flagę na FALSE. Dzięki temu FUNKCJA HIERSVR określa, kiedy każdy element serwera jest usuwany.

\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

Ponieważ te błędy wskazują, istnieją pewne funkcje "pure-virtual", które nie zostały zastąpione w CServerItem. Najprawdopodobniej jest to spowodowane tym, że lista parametrów OnDraw została zmieniona. Aby naprawić ten błąd, zmień CServerItem::OnDraw w następujący sposób (a także deklarację w pliku 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);
}

Nowy parametr to "rSize". Umożliwia to wypełnienie rozmiaru rysunku, jeśli jest to wygodne. Ten rozmiar musi być w HIMETRYCE. W takim przypadku nie jest wygodne wypełnienie tej wartości, więc struktura wywołuje metodę OnGetExtent w celu pobrania zakresu. Aby to zadziałało, musisz zaimplementować polecenie 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 *'

W funkcji CServerItem::CalcNodeSize rozmiar elementu jest konwertowany na HIMETRIC i przechowywany w m_rectBounds. Nieudokumentowany element członkowski "m_rectBounds" nie istnieje (został częściowo zastąpiony przez m_sizeExtent, ale w OLE 2 ten element członkowski ma nieco inne użycie niż m_rectBounds w OLE1).COleServerItem Zamiast ustawiać rozmiar HIMETRIC w tej zmiennej składowej, zwrócisz go. Ta wartość zwracana jest używana w metodzie OnGetExtent, zaimplementowanej wcześniej.

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 zastępuje COleServerItem::OnGetTextDatarównież element . Ta funkcja jest przestarzała w MFC/OLE i jest zastępowana przez inny mechanizm. Wersja MFC 3.0 przykładu MFC OLE HIERSVR implementuje tę funkcję przez zastąpienie .COleServerItem::OnRenderFileData Ta funkcja nie jest ważna dla tego portu podstawowego, więc można usunąć przesłonięcia OnGetTextData.

Istnieje wiele innych błędów w pliku svritem.cpp, które nie zostały rozwiązane. Nie są to błędy rzeczywiste — tylko błędy spowodowane poprzednimi błędami.

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

COleServerItem::CopyToClipboard flaga nie jest już obsługiwana bIncludeNative . Dane natywne (dane zapisane przez funkcję Serialize elementu serwera) są zawsze kopiowane, więc usuwasz pierwszy parametr. Ponadto CopyToClipboard w przypadku wystąpienia błędu zamiast zwracania wartości FALSE zostanie zgłoszony wyjątek. Zmień kod CServerView::OnEditCopy w następujący sposób:

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

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

Chociaż było więcej błędów wynikających z kompilacji wersji MFC 2.0 HIERSVR niż w przypadku tej samej wersji OCLIENT, w rzeczywistości było mniej zmian.

W tym momencie HIERSVR skompiluje i połączy i będzie działać jako serwer OLE, ale bez funkcji edycji w miejscu, która zostanie zaimplementowana w następnej kolejności.

Dodawanie "Edycji wizualnej"

Aby dodać do tej aplikacji serwera "Edycję wizualną" (lub aktywację w miejscu), należy dbać o tylko kilka rzeczy:

  • Potrzebny jest specjalny zasób menu, który ma być używany, gdy element jest aktywny.

  • Ta aplikacja ma pasek narzędzi, więc potrzebny będzie pasek narzędzi z tylko podzbiorem normalnego paska narzędzi, aby dopasować polecenia menu dostępne z serwera (pasuje do zasobu menu wymienionego powyżej).

  • Potrzebna jest nowa klasa pochodna COleIPFrameWnd , która udostępnia interfejs użytkownika w miejscu (podobnie jak CMainFrame, pochodzący z CMDIFrameWndklasy , udostępnia interfejs użytkownika MDI).

  • Musisz poinformować platformę o tych specjalnych zasobach i klasach.

Zasób menu jest łatwy do utworzenia. Uruchom program Visual C++, skopiuj zasób menu IDR_HIERSVRTYPE do zasobu menu o nazwie IDR_HIERSVRTYPE_SRVR_IP. Zmodyfikuj menu tak, aby tylko menu Edytuj i Pomoc pozostały. Dodaj dwa separatory do menu między menu Edytuj i Pomoc (powinien wyglądać następująco: Edit || Help). Aby uzyskać więcej informacji na temat znaczenia tych separatorów i sposobu scalania menu serwera i kontenera, zobacz Menu i zasoby: Scalanie menu.

Mapa bitowa dla paska narzędzi podzestawu można łatwo utworzyć, kopiując tę z nowej aplikacji wygenerowanej przez aplikację AppWizard z zaznaczoną opcją "Serwer". Tę mapę bitową można następnie zaimportować do programu Visual C++. Pamiętaj, aby nadać mapie bitowej identyfikator IDR_HIERSVRTYPE_SRVR_IP.

Klasę pochodzącą COleIPFrameWnd z klasy można również skopiować z aplikacji wygenerowanej przez aplikację AppWizard z obsługą serwera. Skopiuj oba pliki IPFRAME. CPP i IPFRAME. H i dodaj je do projektu. Upewnij się, że LoadBitmap wywołanie odnosi się do IDR_HIERSVRTYPE_SRVR_IP, mapy bitowej utworzonej w poprzednim kroku.

Teraz, po utworzeniu wszystkich nowych zasobów i klas, dodaj niezbędny kod, aby platforma wiedziała o tych zasobach (i wie, że ta aplikacja obsługuje teraz edycję w miejscu). Można to zrobić, dodając kilka dodatkowych parametrów do wywołania SetServerInfo InitInstance w funkcji:

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

Teraz jest gotowy do uruchamiania w miejscu w dowolnym kontenerze, który obsługuje również aktywację w miejscu. Ale istnieje jedna niewielka usterka nadal czai się w kodzie. FUNKCJA HIERSVR obsługuje menu kontekstowe wyświetlane, gdy użytkownik naciska prawy przycisk myszy. To menu działa, gdy funkcja HIERSVR jest w pełni otwarta, ale nie działa podczas edytowania osadzania w miejscu. Przyczynę można przypiąć do tego pojedynczego wiersza kodu w CServerView::OnRButtonDown:

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

Zwróć uwagę na odwołanie do AfxGetApp()->m_pMainWnd. Gdy serwer jest aktywowany w miejscu, ma ono okno główne, a m_pMainWnd jest ustawiona, ale zwykle jest niewidoczna. Ponadto to okno odnosi się do głównego okna aplikacji, okna ramki MDI wyświetlanego, gdy serwer jest w pełni otwarty lub uruchomiony autonomicznie. Nie odnosi się do aktywnego okna ramowego — które po aktywowaniu w miejscu jest oknem ramowym pochodzącym z COleIPFrameWnd. Aby uzyskać poprawne aktywne okno nawet w przypadku edycji w miejscu, ta wersja MFC dodaje nową funkcję AfxGetMainWnd. Ogólnie rzecz biorąc, należy użyć tej funkcji zamiast AfxGetApp()->m_pMainWnd. Ten kod musi ulec zmianie w następujący sposób:

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

Teraz masz serwer OLE z minimalną włączoną funkcjonalną aktywacją w miejscu. Jednak nadal istnieje wiele funkcji dostępnych w MFC/OLE 2, które nie były dostępne w MFC/OLE1. Zobacz przykład HIERSVR, aby uzyskać więcej pomysłów na funkcje, które warto zaimplementować. Poniżej wymieniono niektóre funkcje implementowane przez funkcję HIERSVR:

  • Powiększanie— w przypadku rzeczywistego zachowania WYSIWYG w odniesieniu do kontenera.

  • Przeciągnij/upuść i niestandardowy format schowka.

  • Przewijanie okna kontenera w miarę zmiany zaznaczenia.

Przykład HIERSVR w MFC 3.0 używa również nieco innego projektu dla elementów serwera. Pomaga to zaoszczędzić pamięć i sprawia, że linki są bardziej elastyczne. W wersji 2.0 HIERSVR każdy węzeł w drzewie jest-aCOleServerItem. COleServerItem w przypadku każdego z tych węzłów wymagane jest nieco większe obciążenie, niż jest to konieczne dla każdego z tych węzłów, ale COleServerItem jest wymagany dla każdego aktywnego łącza. Ale w większości przypadków istnieje bardzo niewiele aktywnych linków w danym momencie. Aby uczynić to bardziej wydajnym, hiersVR w tej wersji MFC oddziela węzeł od COleServerItem. Ma zarówno klasę CServerNode, jak i klasę CServerItem . Element CServerItem (pochodzący z COleServerItemelementu ) jest tworzony tylko w razie potrzeby. Gdy kontener (lub kontenery) przestanie używać tego konkretnego łącza do tego konkretnego węzła, obiekt CServerItem skojarzony z węzłem CServerNode zostanie usunięty. Ten projekt jest bardziej wydajny i bardziej elastyczny. Jego elastyczność występuje w przypadku obsługi wielu linków wyboru. Żadna z tych dwóch wersji HIERSVR nie obsługuje wyboru wielokrotnego, ale byłoby znacznie łatwiej dodać (i obsługiwać linki do takich wyborów) z wersją MFC 3.0 HIERSVR, ponieważ COleServerItem jest ona oddzielona od danych natywnych.

Zobacz też

Uwagi techniczne według numerów
Uwagi techniczne według kategorii