320 likes | 664 Views
Advanced ArcObjects/ATL applications. Lesson 4 overview. Integrated component solutions Dockable windows Custom layers Property pages Persistence Exercise 4: Great circle custom layer. ArcObjects component integration . Applications often require integrating many objects
E N D
Lesson 4 overview • Integrated component solutions • Dockable windows • Custom layers • Property pages • Persistence • Exercise 4: Great circle custom layer
ArcObjects component integration • Applications often require integrating many objects • Logical groupings 1. External: Commands, toolbars, property pages, windows, layers 2. Internal: Application extensions, filters, persistence 3. Database: Custom features and class extensions Toolbar Command Dockable Window Extension IExtension IExtensionConfig ClassExtension ICustom
Application startup revisited 1. Application 2. Document 3. Extensions 4. New or existing document 5. Registered dockable windows are created 6. Commands and toolbars
Dockable windows • Application-level customization • Application manages window creation • Visible state is saved in .mxt or .mxd • Show and hide window with a tool Instructor Demo
Implementing dockable windows • IDockableWindowDef • OnCreate – Passed in an application reference when created • ChildHWND – Provide a child hWnd source • UserData – Window can maintain user data if necessary // ICAoVBDockWindow public: // IDockableWindowDef STDMETHOD(OnCreate)(IDispatch * hook); STDMETHOD(get_ChildHWND)(OLE_HANDLE * hWnd); STDMETHOD(get_Name)(BSTR * Name); STDMETHOD(get_Caption)(BSTR * Caption); STDMETHOD(OnDestroy)(); STDMETHOD(get_UserData)(VARIANT * data); private: IApplicationPtr m_ipApp; OLE_HANDLE m_childHwnd; //Child window
Consuming VB window components • Create VB ActiveX DLL or OCX • Add components to a frame • Create public member(s) to access window • Import into VC++ // PreviewDockWindow.h : Declaration of the CPreviewDockWindow #import "..\AoVBServer\AoLib_VBServer.dll" raw_interfaces_only, raw_native_types, no_namespace, named_guids HRESULT CAoDockWindow::FinalConstruct() { return m_ipVBPreviewer.CreateInstance(CLSID_CAoWindowServer); } STDMETHODIMP CAoDockWindow::get_ChildHWND(OLE_HANDLE * hWnd) { if (hWnd == NULL) return E_POINTER; m_ipVBPreviewer->get_hWnd(hWnd); // Get window from VB component return S_OK; }
Finding the dockable window • Reference the application: Hook • Use IDockableWindowManager::GetDockableWindow() • Pass in the CLSID to find window STDMETHODIMP CAoDockableWindowCommand::OnCreate(IDispatch * hook) { m_ipApp = hook; CComBSTR bsVal; ::StringFromCLSID(CLSID_PreviewDockWindow, &bsVal); // Dockable window to find IUIDPtr ipUid(CLSID_UID); ipUid->put_Value(CComVariant(bsVal)); // Dockable window CLSID … IDockableWindowManagerPtr ipDockMgr(m_ipApp); // Get the window manager IDockableWindowPtr ipDockWindow; ipDockMgr->GetDockableWindow(ipUID, &m_ipDockWindow);// Find the window return S_OK; }
Controlling the visibility of the window • Use a command to manage the state • ICommand::OnClick() to open and close • IDockableWindow::Dock(esriDockxxx) • ESRI constants: esriDockShow and esriDockHide • Deactivate your tool when the window is hidden STDMETHODIMP CAoDockableWindowCommand::OnClick() { m_bVisible = !m_bVisible; // Toggle this each time HRESULT hr; if (m_bVisible) hr = m_ipDockWindow->Dock(esriDockShow); else { hr = m_ipDockWindow->Dock(esriDockHide); m_ipApp->putref_CurrentTool(0); // Deactivate the tool } return hr; }
Dockable window design considerations • Window can be closed manually by user with the x • Manage ICommand, Enabled, and Checked properties • Match command with the window state // ICommand STDMETHODIMP CAoDockableWindowCommand::get_Enabled(VARIANT_BOOL * Enabled) { VARIANT_BOOL bVisible; m_ipDockWindow->IsVisible(&bVisible); if (bVisible == VARIANT_FALSE) // Window was manually closed ‘x’ { m_ipApp->putref_CurrentTool(0); // Deactivate tool m_bVisible = FALSE; // Don't deactivate again! } *Enabled = VARIANT_TRUE; return S_OK; } STDMETHODIMP CAoDockableWindowCommand::get_Checked(VARIANT_BOOL * Checked) { VARIANT_BOOL bVisible; m_ipDockWindow->IsVisible(&bVisible); *Checked = bVisible; return S_OK; }
Custom layers • Map-level customization • Very application specific • Involve drawing (custom) elements to the screen • Used as an alternative to custom workspace factories • Design considerations • Drawing and storing elements • Make as functional as an esriCore::FeatureLayer • May require writing custom tools and other components • Example: GreatCircleLayer Instructor Demo
Implementing a layer • ILayer • Supports draw phases • Controls drawing behavior • Controls visibility • IGeoDataset • Extent • Spatial reference • IPersist and IPersistStream • Enables ILayer property data to be stored • Enables custom layer data to be stored
Drawing phases • Custom layer must identify the supported draw phases • Tells system how and when to draw the layer • tagesriDrawPhase • Each phase represents an off-screen bitmap (cache) esriDPGeography 1 - Draw geography esriDPAnnoation 2 - Draw annotation esriDPSelection 4 – Draw selection STDMETHODIMP CMyLayer::get_SupportedDrawPhases(long* pDrawPhases) { if (!pDrawPhases) return E_POINTER; // We are only interested in the geography phase, can be or’ed *pDrawPhases = esriDPGeography; return S_OK; }
Layer caches • All layers are drawn into one cache • Any layer can be drawn into a separate cache • Improves drawing speed if refreshed frequently • IActiveView::PartialRefresh(esriViewGeography, pUnkLayer, ipLayerExtent); STDMETHODIMP CMyLayer::get_Cached(VARIANT_BOOL* pCached) { if (!pCached) return E_POINTER; *pCached = m_bCached; return S_OK; } STDMETHODIMP CMyLayer::put_Cached(VARIANT_BOOL cached) { m_bCached = cached; return S_OK; }
Drawing the layer • The system can ask to draw the layer at any time • Draw code must be ready in ILayer::Draw • Always verify the following • esriDrawPhase, pDisplay, and pTrackCancel STDMETHODIMP CMyLayer::Draw(esriDrawPhase drawPhase, IDisplay* pDisplay, ITrackCancel* pTrackCancel) { if (!pDisplay) return E_POINTER; if (drawPhase == esriDPGeography) { VARIANT_BOOL continueDrawing; if (pTrackCancel) pTrackCancel->Continue(&continueDrawing); if (continueDrawing) // Ok to draw features DrawFeatures(); … } }
Drawing layer features • Draw each geometry manually • Decide on best methodology: Linear, by size, position… • Set symbol into the display first • Do not call StartDrawing() or FinishDrawing() STDMETHODIMP CMyLayer::Draw(esriDrawPhase drawPhase, IDisplay* pDisplay, ITrackCancel* pTrackCancel) { if (!pDisplay) return E_POINTER; … // Ok to draw feature(s) HRESULT hr = pDisplay->SetSymbol(m_pSymbol); if (FAILED(hr)) return hr; hr = pDisplay->DrawPoint(pGeom); if (FAILED(hr)) return hr; }
Georeferencing layers • All layers must support IGeoDataset • Defines the extent and spatial reference • Required by ArcMap to • Control layer visibility from TOC • Control invalid drawing extent • Enable command tools (e.g., Zoom to layer)
Completing the layer implementation • IPersistStream • ILegendInfo • Others are optional • See FeatureLayer • IPublishLayer for internet publishing • Associated objects may be required • Specialized commands and tools • Specialized property page • Specialize event handling
Property sheets and pages • Purpose: To view and edit attributes of objects • Examples: Element, layer, or a map frame • Potential uses • Show property sheets on-the-fly • Show custom sheets with selected property pages only • Create custom property pages for specific objects • Example: GCLPropertyPage Instructor Demo
Showing property sheets and pages • ComPropertySheet • Automatically loads pages in a specific category • Populate an ISet to pass in filter objects (e.g., Layer) • EditProperties – Shows property sheet and pages IComPropertySheetPtr ipComPropertySheet(CLSID_ComPropertySheet); // Generic sheet IUIDPtr ipUID(CLSID_UID); // Define the category GUID v.bstrVal = ::SysAllocString(OLESTR("{1476C782-6F57-11D2-A2C6-080009B6F22B}")); ipUID->put_Value(v); ipComPropertySheet->AddCategoryID(ipUID);// Filter pages by category IUnknown* pUnk; if (FAILED(hr = pLayer->QueryInterface(&pUnk))) return hr; ISetPtr ipSet(CLSID_Set); ipSet->Add(pUnk);// Search criteria is type ILayer ipComPropertySheet->EditProperties(ipSet,(OLE_HANDLE) hParentWindow, &vb) // Show
Load all pages that apply to ISet 1. CATID Each page evaluates ISet 2. Pass ISet Loading property pages 1. CATID: ESRI Layer Property Pages 2. ISet: ILayer Component Categories
Custom property pages • Implement • IPropertyPage • IPropertyPageContext • Design same as MS property pages • Key members • Applies, Show, and Apply
Designing a property page in ATL • New ATL property page object • Provides default implementation • IPropertyPageImpl<> • IPropertyPage • Need IPropertyPageContext • Use the Resource Wizard • Add and position window elements • Event handling code must be added manually • MSG_MAP Instructor Demo
IPropertyPage::Applies • PropertySheet passes in an array of objects (ISet) • Page tells the sheet to load page or not STDMETHODIMP CGCLayerPropertyPage::Applies(VARIANT unkArray, VARIANT_BOOL * Applies) { // Default Applies to False *Applies = VARIANT_FALSE; SAFEARRAY *saArray; saArray = unkArray.parray; HRESULT hr = SafeArrayLock(saArray); if (FAILED(hr)) return hr; // Look for object we can edit long lNumElements = saArray->rgsabound->cElements; for (long i = 0; i < lNumElements; i++) { // Attempt to QI for IEventCustomLayer IGreatCircleLayerPtr ipGCLayer(ippUnk[i]); if (ipGCLayer != 0) { // Interface and the property page therefore applies *Applies = VARIANT_TRUE; } } return S_OK; }
IPropertyPage::Show • Executes when the window itself is created • Opportunity to set the value of the page elements STDMETHODIMP CGCLayerPropertyPage::Show(UINT nCmdShow) { // Let the ATL property page take care of showing and hiding the dialog hr = IPropertyPageImpl<CGCLayerPropertyPage>::Show(nCmdShow); if (nCmdShow != SW_HIDE) { for (UINT i = 0; i < m_nObjects; i++) { IGreatCircleLayerPtr ipGCLayer(m_ppUnk[i]); // Get the properties from the layer if ((ipGCLayer != 0) && (m_hWnd != 0)) { long lDelay; hr = ipGCLayer->get_Delay(&lDelay); SetDlgItemInt(IDC_TIMERINTERVAL, (UINT) (lDelay)); double dShift; hr = ipGCLayer->get_Shift(&dShift); SetDlgItemInt(IDC_SPEEDVALUE, (UINT) dShift); } } } }
IPropertyPage::Apply • Access property page array of objects • Find your object and set the new values • Synchronize property page values with object STDMETHODIMP CGCLayerPropertyPage::Apply(void) { for (UINT i = 0; i < m_nObjects; i++) { IGreatCircleLayerPtr ipGCLayer(m_ppUnk[i]); if (ipGCLayer != 0) { // Apply the changes from the property page hr = ipGCLayer->put_Delay(GetDlgItemInt(IDC_TIMERINTERVAL)); if (FAILED(hr)) return hr; hr = ipGCLayer->put_Shift((double) GetDlgItemInt(IDC_SPEEDVALUE)); if (FAILED(hr)) return hr; } m_bDirty = FALSE; return S_OK; }
Supporting persistence • Implement IPersistStream • Objects may contain other objects that support persistence • Internal objects are automatically asked to persist themselves (e.g., MxDocument > Map > Layers > Symbols) • Must be supported by all custom layers • Store ILayer data: Name, visibility, min, and max scale • Store custom data if necessary • Example: GreatCircleLayer Instructor Demo
IPersistStream::Save • Save standard types and objects STDMETHODIMP CGreatCircleLayer::Save(IStream * pStm, BOOL fClearDirty) { // Persist the relevant info to the stream pStm->Write(&cCurVers , sizeof(cCurVers), 0)); // ILayer members m_bsName.WriteToStream(pStm); pStm->Write(&m_bIsVisible, sizeof(m_bIsVisible), 0); pStm->Write(&m_bShowTips, sizeof(m_bShowTips), 0); pStm->Write(&m_bCached, sizeof(m_bCached), 0); pStm->Write(&m_dMinimumScale, sizeof(m_dMinimumScale), 0); pStm->Write(&m_dMaximumScale, sizeof(m_dMaximumScale), 0); pStm->Write(&m_dReferenceScale, sizeof(m_dReferenceScale), 0); // IGreatCircleLayer members pStm->Write(&m_lDelay, sizeof(m_lDelay), 0); pStm->Write(&m_dShift, sizeof(m_dShift), 0); IObjectStreamPtr ipObjectStream(CLSID_ObjectStream); ipObjectStream->putref_Stream(pStm); hr = ipObjectStream->SaveObject(m_ipMovingSymbol); hr = ipObjectStream->SaveObject(m_ipAnimatePoint); hr = ipObjectStream->SaveObject(m_ipProjectedExtent); m_bIsDirty = false; return S_OK; }
IPersistStream::Load • Populate data values and objects STDMETHODIMP CGreatCircleLayer::Load(IStream * pStm) { // Check the version of persistence short vers; if (FAILED(pStm->Read(&vers, sizeof(vers), 0))) return E_FAIL; if (vers > cCurVers) return E_FAIL; // Not forward compatible // ILayer members m_bsName.ReadFromStream(pStm); pStm->Read(&m_bIsVisible, sizeof(m_bIsVisible), 0); pStm->Read(&m_bShowTips, sizeof(m_bShowTips), 0); pStm->Read(&m_bCached, sizeof(m_bCached), 0); pStm->Read(&m_dMinimumScale, sizeof(m_dMinimumScale), 0); pStm->Read(&m_dMaximumScale, sizeof(m_dMaximumScale), 0); pStm->Read(&m_dReferenceScale, sizeof(m_dReferenceScale), 0); // IGreatCircleLayer members pStm->Read(&m_lDelay, sizeof(m_lDelay), 0); pStm->Read(&m_dShift, sizeof(m_dShift), 0); // Now the objects - use an object stream IObjectStreamPtr ipObjectStream(CLSID_ObjectStream); ipObjectStream->putref_Stream(pStm); hr = ipObjectStream->LoadObject((GUID*) &IID_ISymbol, 0, (IUnknown**) &m_ipMovingSymbol); … m_bIsDirty = false; return S_OK; }
IPersistStream::GetSizeMax • Determine the size required to store all data STDMETHODIMP CGreatCircleLayer::GetSizeMax(_ULARGE_INTEGER* pcbSize) { pcbSize->QuadPart = sizeof(cCurVers); // Ilayer – Standard types pcbSize->QuadPart += m_bsName ? SysStringByteLen(m_bsName) + sizeof(OLECHAR) : 0; pcbSize->QuadPart += sizeof(m_bIsVisible); pcbSize->QuadPart += sizeof(m_bShowTips); pcbSize->QuadPart += sizeof(m_bCached); pcbSize->QuadPart += sizeof(m_dMinimumScale); pcbSize->QuadPart += sizeof(m_dMaximumScale); pcbSize->QuadPart += sizeof(m_dReferenceScale); // IGreatCircleLayer – Standard types pcbSize->QuadPart += sizeof(m_lDelay); pcbSize->QuadPart += sizeof(m_dShift); pcbSize->QuadPart += sizeof(m_bActive); pcbSize->QuadPart += sizeof(m_dPlace); _ULARGE_INTEGER objSize; // 64 bit unsigned integer struct // IGreatCircleLayer – Objects IPersistStreamPtr ipPersistStream(m_ipMovingSymbol); ipPersistStream->GetSizeMax(&objSize); pcbSize->QuadPart += objSize.QuadPart; … }
Exercise 4 overview IUnknown • Implements ILayer… • Custom tools and toolbar • Property page and persistence • Challenge: Legend support IToolBarDef CGCClear CGCLToolBar IUnknown CGCAddLayer ICommand CGCDigitizeDeparture ICommand CGCDigitizeDestination ICommand CGCClear ICommand ILayer CGreatCircleLayer ICommand CGCAnimate IGeoDataset ILayerDrawProperties IPersistStream ILegendInfo IPropertyPage CGCLayerPropertyPage IPropertyPageContext
Review • Explain the start up sequence for commands and windows? • What object should control the visibility of a dockable window? • What interfaces should be implemented with ILayer? • When should a layer draw itself? • Describe the property page loading cycle? • How are data and objects persisted in VC++?