Uwaga
Dostęp do tej strony wymaga autoryzacji. Może spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
Uwaga / Notatka
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/Widok MFC, a chcesz dodać obsługę OLE 2 do aplikacji, nadszedł czas, aby przejść do architektury Dokument/Widok. 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żyj implementacji MFC zamiast własnej
Klasy MFC, takie jak CToolBar
, CStatusBar
i CScrollView
, zawierają wbudowany kod dla obsługi OLE 2. Jeśli więc możesz użyć tych klas w swojej aplikacji, skorzystasz z wysiłku włożonego w to, aby były zgodne z OLE. Można ponownie tworzyć własne klasy w tym miejscu do tych celów, ale nie jest to sugerowane. Jeśli musisz zaimplementować podobną funkcjonalność, kod źródłowy MFC jest doskonałym odniesieniem do pracy z niektórymi bardziej złożonymi kwestiami 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ż portowany 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, oprócz bycia samodzielną aplikacją, jest również serwerem OLE. Obsługa serwera, który implementuje, jest dość minimalistyczna. Szczególnie interesujące jest to, że do kopiowania danych do schowka używane są usługi schowka OLE, natomiast do zaimplementowania funkcji wklejania schowka używane są wbudowane funkcje kontrolki "edycji" systemu Windows. 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 został zaimplementowany w 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 / Notatka
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.
Uruchamianie i Konfiguracja
Podejście zastosowane do przeniesienia przykładu OCLIENT do MFC/OLE polega na rozpoczęciu jego budowy i naprawieniu oczywistych błędów kompilacji, które wykryje kompilator. 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 przyjmowano więcej parametrów niż przyjmowała 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ż framework zbuduje jeden z "atrybutowego kontekstu urządzenia" przekazanego w pDC. 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 F1.
Innym obszarem, który znacznie się różni, jest zarządzanie schowkiem OLE 2. W przypadku OLE1 używano interfejsów API schowka Windows do współpracy ze schowkiem. W przypadku ole 2 odbywa się to za pomocą innego mechanizmu. Interfejsy API MFC/OLE1 przyjmowały, że schowek jest otwarty przed skopiowaniem obiektu COleClientItem
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 CreateNewName
, należy również usunąć kod, który otwiera 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. Wywołując funkcję AfxOleInsertDialog
, zostanie wyświetlone okno dialogowe „Wstaw obiekt” w MFC/OLE1. W tej wersji utworzysz obiekt dialogowy COleInsertObject
i wywołasz 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 / Notatka
Polecenie "Wstaw nowy obiekt" może wyglądać inaczej w Twojej 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 OLE1, jak i OLE 2 czasownik podstawowy jest zwykle wykonywany przez kontener, gdy użytkownik kliknie dwukrotnie na element.
Ponadto DoVerb
teraz pobiera dodatkowy parametr — wskaźnik do widoku (CView
*). Ten parametr jest używany tylko do implementacji "edycji wizualnej" (lub aktywizacji na miejscu). Na razie ustawisz ten parametr na wartość NULL, ponieważ w tej chwili nie implementujesz tej funkcji.
Aby upewnić się, że platforma nigdy nie próbuje aktywować funkcji w miejscu, należy zastąpić 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 używano COleClientItem::GetBounds
i SetBounds
do zapytań i manipulacji 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
i SetExtent
, które zajmują się rozmiarem lub CSize
.
Kod dla Twoich nowych wywołań SetItemRectToServer i UpdateItemRectFromServer wygląda 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 synchronicznych wywołań API w MFC/OLE1 z kontenera do serwera były symulowane, ponieważ OLE1 było z natury asynchroniczne w wielu przypadkach. Konieczne było sprawdzenie, czy istnieje trwające wywołanie asynchroniczne, zanim przetworzono polecenia od użytkownika. MFC/OLE1 dostarczył funkcję COleClientItem::InWaitForRelease
w tym celu. W MFC/OLE 2 nie jest to konieczne, więc można całkowicie usunąć przesłanianie OnCommand w CMainFrame.
W tym momencie program OCLIENT skompiluje i połączy.
Inne niezbędne zmiany
Istnieje kilka rzeczy, które nie zostały wykonane, a które jednak uniemożliwią uruchomienie klienta OCLIENT. Lepiej jest rozwiązać te problemy teraz, a nie później.
Najpierw należy zainicjować biblioteki OLE. Odbywa się to poprzez wywołanie AfxOleInit
z 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ęta 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 kontenerowe wywodzą klasę dokumentu z COleClientDoc
. 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 który mapuje COleClientDoc
na COleDocument
w celu uproszczenia przenoszenia aplikacji MFC/OLE1 do MFC/OLE 2, takich jak OCLIENT. Jedną z funkcji, które nie zostały dostarczone przez COleDocument
, a którą zapewnia COleClientDoc
, są standardowe wpisy mapy komunikatów poleceń. 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 COleDocument
, 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 na miejscu, elementy są edytowane w osobnym oknie, podobnie jak w OLE1. W następnej sekcji omówiono niezbędne zmiany umożliwiające edycję w miejscu (czasami nazywaną "edytowaniem wizualnym").
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ć lokalną aktywację dla klienta OCLIENT, należy dodać kilka specjalnych zasobów, a także dodatkowy kod. Te zasoby i ten kod są zwykle dostarczane przez aplikację AppWizard — w rzeczywistości większość kodu tutaj została pożyczona bezpośrednio z nowo utworzonej aplikacji AppWizard obsługującej „Kontener”.
Przede wszystkim należy dodać zasób menu do użycia, gdy istnieje element, który jest aktywny na miejscu. Ten dodatkowy zasób menu można utworzyć w programie Visual C++, kopiując zasób IDR_OCLITYPE i zachowując jedynie okienka podręczne Plik oraz 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 lokalny utworzony w środowisku Visual C++.
Aby umożliwić aktywację w miejscu, konieczne są zmiany zarówno w klasie pochodnej CView
(CMainView), jak i w klasie pochodnej 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 lokalne uruchamianie zostało całkowicie wyłączone przez nadpisanie COleClientItem::CanActivate
. To zastąpienie powinno zostać usunięte, aby umożliwić natychmiastową aktywację. Ponadto wartość NULL została przekazana do wszystkich dwóch wywołań DoVerb
, ponieważ udostępnienie widoku było konieczne tylko na potrzeby aktywacji bezpośredniej. Aby w pełni zaimplementować lokalną aktywację, należy przekazać poprawny 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);
Konieczne jest zastąpienie COleClientItem::OnGetItemPosition
. Informuje to serwer, gdzie należy umieścić jego okno względem okna kontenera, gdy element jest aktywowany na 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 bezpośrednio w oknie". Dzięki temu okno serwera może zmieniać rozmiar i być przesunięte 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 ESCAPE.
W przypadku klawisza ESCAPE dodaj akcelerator w języku Visual C++, który mapuje klawisz VK_ESCAPE do polecenia, ID_CANCEL_EDIT jest dodawany do zasobów. Obsługa tego polecenia odbywa się w następujący sposób:
// 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 elementu CMainView::SetSelection
.
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 fokus jest zawsze przenoszony do aktywnego elementu, gdy widok przejmuje fokus, obsługuj zdarzenie OnSetFocus.
// 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, że prostokąt przycinania uległ zmianie. W tym celu należy podać procedurę obsługi dla OnSize
programu :
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 / Notatka
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.
Uruchamianie i Konfiguracja
Podejście do przenoszenia przykładu HIERSVR do MFC/OLE polega na rozpoczęciu kompilacji i usuwaniu oczywistych błędów kompilatora, które się pojawią w wyniku tego. 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 .EXE dla aplikacji, uruchomienie jej 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 AppWizard tworzy dla InitInstance
, InitInstance
(i powiązany identyfikator GUID) dla HIERSVR powinien wyglądać następująco:
// 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
Występuje wiele błędów wynikających z przesłonięcia elementu OnSetData
, ponieważ odnosi się do typu OLESTATUS.
OLESTATUS był sposobem, w jaki OLE1 zwracał błędy. To zmieniło się na HRESULT w OLE 2, podczas gdy MFC zazwyczaj 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. HIERSVR używa obiektów CServerItem
(pochodzących z COleServerItem
) jako część danych natywnych, więc tę flagę ustawisz na FALSE. Dzięki temu HIERSVR może określić, 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
Te błędy wskazują, że istnieją pewne funkcje "pure-virtual", które nie zostały nadpisane 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 określenie rozmiaru rysunku, jeśli to wygodne. Ten rozmiar musi być w HIMETRYCE. W takim przypadku nie jest wygodne uzupełnianie tej wartości, więc system 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 "COleServerItem
" 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). Zamiast ustawiać rozmiar HIMETRIC w tej zmiennej składowej, zwrócisz go. Ta zwrócona wartość jest używana w wcześniej zaimplementowanej metodzie OnGetExtent
.
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 również zastępuje COleServerItem::OnGetTextData
. 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ę funkcjonalność przez nadpisanie COleServerItem::OnRenderFileData
. Ta funkcjonalność nie jest ważna dla tego portu podstawowego, więc można usunąć przesłonięcie OnGetTextData.
Istnieje wiele innych błędów w 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
nie obsługuje już flagi bIncludeNative
. Dane natywne (dane zapisane przez funkcję Serialize elementu serwera) są zawsze kopiowane, więc usuwasz pierwszy parametr. Ponadto CopyToClipboard
, gdy wystąpi błąd, zamiast zwracania wartości FALSE, zgłosi 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, połączy i zacznie działać jako serwer OLE, ale bez funkcji edycji w miejscu, która zostanie zaimplementowana następnie.
Dodawanie "Edycji wizualnej"
Aby dodać do tej aplikacji serwera "Edycję wizualną" (lub aktywację w miejscu), należy zadbać tylko o 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 zawierający jedynie część normalnego paska narzędzi, aby dopasować polecenia menu dostępne z serwera (zgodne z zasobami menu wymienionymi powyżej).
Potrzebna jest nowa klasa pochodna
COleIPFrameWnd
, która udostępnia interfejs użytkownika w miejscu wykonywania (podobnie jak CMainFrame, pochodzący zCMDIFrameWnd
, 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.
Bitmapę dla paska narzędzi podzbioru można łatwo utworzyć, kopiując ją z nowej aplikacji wygenerowanej przez 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.
Klasa pochodząca z COleIPFrameWnd
może być również skopiowana z aplikacji wygenerowanej przez 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
w funkcji InitInstance
.
pDocTemplate->SetServerInfo(IDR_HIERSVRTYPE_SRVR_EMB,
IDR_HIERSVRTYPE_SRVR_IP,
RUNTIME_CLASS(CInPlaceFrame));
Teraz jest gotowy do uruchamiania bezpośrednio w dowolnym kontenerze, który obsługuje również bezpośrednią aktywację. Istnieje jednak jedna niewielka usterka, która nadal znajduje się w kodzie. HIERSVR obsługuje menu kontekstowe wyświetlane, gdy użytkownik naciska prawy przycisk myszy. To menu działa, gdy HIERSVR jest całkowicie otwarty, ale nie działa podczas edytowania osadzonego obiektu bezpośrednio. 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ć właściwe aktywne okno nawet w przypadku edycji bezpośredniej, 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ą 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:
Przybliżanie — dla rzeczywistego zachowania WYSIWYG w odniesieniu do pojemnika.
Przeciągnij/upuść i niestandardowy format schowka.
Przewijanie okna kontenera podczas zmiany zaznaczenia.
Przykład HIERSVR w MFC 3.0 również wykorzystuje nieco inną konstrukcję 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 is-aCOleServerItem
.
COleServerItem
powoduje trochę większy nadmiar niż jest to ściśle 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 COleServerItem
) 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ść ujawnia się przy pracy z wieloma linkami do wyboru. Żadna z tych dwóch wersji HIERSVR nie obsługuje wyboru wielokrotnego, ale byłoby znacznie łatwiej to dodać (i obsługiwać linki do takich wyborów) z wersją HIERSVR MFC 3.0, ponieważ COleServerItem
jest oddzielona od danych natywnych.
Zobacz także
Uwagi techniczne według numeru
Uwagi techniczne według kategorii