A Subscribing DTC Walkthrough

This walkthrough describes how to build a Design-time Control (DTC) that subscribes to a choice published by another DTC. This subscribing DTC uses the choice published by the Visual Basic DTC sample. So if you want to preview it, you need to add both to your test page. You can find them in the directory DTCSDK60\samples\bin provided with this SDK. For more information, see Code Sample Summary.

The user interface for this subscribing DTC displays the text of choice published by another DTC. The subscribing DTC uses a ChoiceSink to get the published information.

For more information about creating DTCs, read Creating a DTC. If you want to look at the code without the walkthrough description, see the highlighted version of the ATL Sample's Header File and ATL Sample's Program File for the subscriber DTC. For more information about using Visual C++, see the online documentation.

Tip   If you paste code from help topics into your project file, be sure to copy the line above and below the entire code sample as well. Otherwise the text you paste loses the formatting it had while in the help topic. You can also copy code from the ATLSample project.

Subscribing to Choices in a DTC

The main tasks for subscribing to a choice are:

Create an ATL COM Application Project

In Visual C++, you can use the wizards to create the project and the control.

To create the project

  1. In Visual C++, create a new .DLL using the ATL COM AppWizard and accept the wizard defaults.

    For example, the sample included in this SDK is a project name ATLSample.

    Note   This creates a new .DLL that does not support MFC.

  2. Insert a new full control object into the project using the default attributes.

    For example, the ATL Sample project has SinkDTC in it.

    Tip   When you provide a name, you might want to set the type to "DTC."

  3. In the control's header file (.h), define the initial size of the control in the default constuctor.
    CSinkDTC()
    {
    m_sizeExtent.cx = 4000;            // 100'ths of millimeters
    m_sizeExtent.cy = 1500;            // 100'ths of millimeters
    m_sizeNatural = m_sizeExtent;
    }
    

Add the IActiveDesigner and the IDesignTimeControl Interfaces to the Class Definition

The IActiveDesigner interface provides the methods for using choices. The IDesignTimeControl provides the methods for generating the run-time text.

To add the interfaces

  1. In the control's header file (.h), add the interfaces as base classes of the control's class.
    public IActiveDesigner,
    public IDesignTimeControl
    
  2. Add the following two interfaces to the COM interface map.
    COM_INTERFACE_ENTRY_IID(IID_IActiveDesigner, IActiveDesigner)
    COM_INTERFACE_ENTRY(IDesignTimeControl)
    
  3. Add methods for IDesignTimeControl.
    STDMETHOD(get_DesignTimeControlSite)(IDesignTimeControlSite ** DesignTimeControlSite);
    STDMETHOD(putref_DesignTimeControlSite)(IDesignTimeControlSite *DesignTimeControlSite);
    STDMETHOD(get_DesignTimeControlSet)(BSTR * DesignTimeControlSet);
    STDMETHOD(OnGetChoices)(Choices * Choices);
    STDMETHOD(OnRebind)(Choices * Choices);
    STDMETHOD(OnChoiceConflict)(Choice * Choice, VARIANT_BOOL Conflict);
    STDMETHOD(OnChoiceChange)(ChoiceSink * ChoiceSink, dtcChoiceChange Change);
    STDMETHOD(OnHostingChange)(dtcHostingChange change, VARIANT_BOOL * Cancel);
    
  4. Add the methods for IActiveDesigner.
    STDMETHOD(GetRuntimeClassID)(CLSID *pclsid);
    STDMETHOD(GetRuntimeMiscStatusFlags)(DWORD *dwMiscFlags);
    STDMETHOD(QueryPersistenceInterface)(REFIID riid);
    STDMETHOD(SaveRuntimeState)(REFIID riidItf, REFIID riidObj, void *pObj);
    STDMETHOD(GetExtensibilityObject)(IDispatch **ppvObjOut);
    
  5. For the project, set the path for the DTC SDK include files.

How to   From the Project menu, choose Settings. On the C/C++ tab, set the category to Preprocessor. In the Additional Include Directories box, add the path for the SDK include directories.

  1. In both the control's and the project's source files (.cpp), initialize the IActiveDesigner GUIDs as shown below in the sample.

    Note   Place the #include after any system include files and before any project include files. If additional source files reference this control's header file, you need to add these declarations to them as well.

    #include "stdafx.h"
    #include <designer.h>
    #include <dtc60.h>
    #include "ATLSample.h"
    #include "SinkDTC.h"
    

Implement the Design-time Control Interface

Sharing information through choices requires that the control implements the IDesignTimeControl interface. For more information about the interface, see IDesignTimeControl.

To implement the DesignTimeCotnrolSite property

  1. In the control's class definition (.h), declare the site.
    private: 
    CComPtr<IDesignTimeControlSite>    m_spSite;
    };
    
  2. In the control's source file (.cpp), specify the get and putref methods to implement a site.
    STDMETHODIMP CSinkDTC::get_DesignTimeControlSite(IDesignTimeControlSite ** ppSite)
    {
    (*ppSite) = m_spSite.p;
    (*ppSite)->AddRef();
    return S_OK;
    }
    
    STDMETHODIMP CSinkDTC::putref_DesignTimeControlSite(IDesignTimeControlSite * pSite)
    {
    HRESULT        hr = S_OK;
    m_spSite = pSite;
    return hr;
    }
    

Define and Implement the Remaining Methods

Make sure that each of the interface methods are in the program file.

To add remaining methods for the ATL sample

Create a ChoiceSink

Using a ChoiceSink Object, you can bind to a static choice. For more information about binding to choices, see Subscribing to a Choice.

To create a ChoiceSink

  1. In the control's class definition (.h), declare the ChoiceSink.
    private: 
    CComPtr<IDesignTimeControlSite>    m_spSite;
    CComPtr<ChoiceSink>            m_spChoiceSink;
    };
    
  2. In the source file (.cpp), use the putref method of DesignTimeControlSite to create a ChoiceSink and to check for choices of a specific type.

    For example, in ATLSample.cpp, the DTC subscribes to choices of the type, FruitChoice.

    STDMETHODIMP
    CSinkDTC::putref_DesignTimeControlSite(IDesignTimeControlSite * pSite)
    {
    HRESULT        hr = S_OK;
    
    m_spSite = pSite;
    if (pSite != NULL)
    {
    CComPtr<ChoiceSinks>    spChoiceSinks;
    CComVariant                vType(LPCOLESTR(L"FruitChoice"));
    VARIANT                    vText;
    vText.vt = VT_ERROR;
    hr = pSite->get_ChoiceSinks(&spChoiceSinks);
    if (hr)
    return hr;
    hr = spChoiceSinks->AddChoiceSink(vType, vText, &m_spChoiceSink);
    if (hr)
    return hr;
    }
    return hr;
    }
    

Handle Changes to the Published Choice

When the choice changes, the DTC needs to update the user interface and notify the hosting editor that the run-time text has changed as well.

  1. In the control's class definition (.h), declare the a string and methods to handle the choice information.
    private: 
    CComPtr<IDesignTimeControlSite>    m_spSite;
    CComPtr<ChoiceSink>            m_spChoiceSink;
    CComBSTR                    m_sSelectedFruit;
    
    public:
    STDMETHOD(get_SelectedFruit)(/*[out, retval]*/ BSTR *pVal);
    STDMETHOD(put_SelectedFruit)(/*[in]*/ BSTR newVal);
    
  2. In the control's source file, specify the OnChoiceChange method to handle new choice information published by the publisher DTC.

    This sample code verifies that the choice is the correct ChoiceSink, checks for a change to the BoundChoice property of the ChoiceSink, and notifies the container to update itself.

    STDMETHODIMP CSinkDTC::OnChoiceChange(ChoiceSink * pCS, dtcChoiceChange change)
    {
    HRESULT    hr = S_OK;
    
    if (pCS == m_spChoiceSink)
    {
    CComPtr<Choice>    spChoice;
    
    hr = pCS->get_BoundChoice(&spChoice);
    if (hr)
    return hr;
    if (spChoice != NULL)
    {
    spChoice->get_Description(&m_sSelectedFruit);
    }
    else
    {
    m_sSelectedFruit.Empty();
    }
    
    SetDirty(true);
    SendOnDataChange(0);// Notify the container.
    FireViewChange();                // Display the change.
    }
    
    return hr;
    }
    
  3. In property map of the control's header file, declare a new property to store the string for the selected choice.
    PROP_ENTRY("SelectedFruit", 1, CLSID_NULL)
    

Add Drawing Code

Your interfaces are likely to be more complex. When creating a DTC, use property pages instead of inline user interface for setting a DTC's properties. An inline user interface is the graphical representation of the DTC within the hosting document such as a form or other object with controls that accept user input. Although Visual InterDev 6.0 supports inline user interface, this is not a guarantee that all DTC hosts are required to support it now or in the future.

In the OnDraw function of the control's source file, make sure the string saved gets painted to the screen. By default the value is stored as UNICODE so you need to convert it into ASCII, then paint that string.

This sample shows how to specify the choice, see bold below.

Note   To display a string, be sure to convert it from UNICODE to ANSI. In the sample below, the string m_sSelectedFruit is UNICODE but the control is compiled in ANSI therefore, the string must be converted.

HRESULT 
CSinkDTC::OnDraw(ATL_DRAWINFO& di)
{
   RECT& rc = *(RECT*)di.prcBounds;
   char      achBuf[256];

   ::SetTextAlign(di.hdcDraw, TA_BASELINE);
   achBuf[0] = '\0';
   ::WideCharToMultiByte(CP_ACP, 0, m_sSelectedFruit, -1, achBuf, sizeof (achBuf), 0, 0);
   ::ExtTextOut(di.hdcDraw,
      rc.left + (rc.right - rc.left) / 10, 
      (rc.top + rc.bottom) / 2, 
      ETO_OPAQUE,
      &rc,
      achBuf, 
      lstrlen(achBuf),
      NULL);

   return S_OK;
}

Specify the Run-timeText

You need to specify the text you want the DTC to generate.

Why is it different from the Visual Basic sample?   You may notice that run-time text is handled differently in the Visual Basic sample than it is in the ATL sample. Visual C++ can use IPersistTextStream interface, but Visual Basic doesn't. To allow this for the Visual Basic author, DTCs created in Visual Basic implement the IProvideRunTimeText interface. This interface provides the communication between the control and Visual Basic in order to handle the strings and generate the appropriate output.

To specify the run-time text

  1. In the GetRuntimeClassID and GetRuntimeMiscStatusFlags methods, specify that there are no run-time objects for this class.
    STDMETHODIMP CSinkDTC::GetRuntimeClassID(CLSID *pclsid)
    {
    return E_NOTIMPL;
    }
    
    STDMETHODIMP CSinkDTC::GetRuntimeMiscStatusFlags(DWORD * pdw)
    {
    if (pdw == NULL)
    return E_INVALIDARG;
    *pdw = 0;
    return S_OK;
    }
    
  2. In the QueryPersistenceInterface method, specify that a text stream be used when asking for the run-time text.
    STDMETHODIMP CSinkDTC::QueryPersistenceInterface(REFIID iid)
    {
    if (iid == IID_IPersistTextStream)
    return S_OK;
    
    return S_FALSE;
    }
    
  3. In the SaveRuntimeState method, specify the content to write into the stream.
    STDMETHODIMP CSinkDTC::SaveRuntimeState(REFIID iidPersist, REFIID iidObjStgMed, void * pObjStgMed)
    {
    HRESULT            hr = S_OK;
    IStream *        pStm;
    ULONG            cbWritten;
    CComBSTR        sRuntimeText;
    
    if (iidPersist != IID_IPersistTextStream)
    return E_INVALIDARG;
    if (iidObjStgMed != IID_IStream)
    return E_INVALIDARG;
    
    pStm = static_cast<IStream *>(pObjStgMed);
    
    sRuntimeText =  L"<B>This is the chosen fruit: ";
    sRuntimeText += m_sSelectedFruit;
    sRuntimeText += "</B><P>";
    
    hr = pStm->Write(
    sRuntimeText.m_str,
    sRuntimeText.Length() * sizeof (OLECHAR),
    &cbWritten);
    
    return hr;
    }
    
  4. In the GetExtensibilityObject method, specify that it returns its own IDispatch interface.
    STDMETHODIMP CSinkDTC::GetExtensibilityObject(IDispatch ** ppDisp)
    {
    return QueryInterface(IID_IDispatch, (void **) ppDisp);
    }
    

Persist Choice Information in a Property Bag

The control wizard automatically implements IPersistStreamInit as the persistence interface. IPersistPropertyBag is more appropriate as a persistence interface when a DTC is being edited by Visual InterDev and other HTML editors.

To persist choice information in a property bag

  1. Specify that you are going to use a property bag for persistence.
    public IPersistStreamInitImpl<CSinkDTC>
    public IPersistPropertyBagImpl<CSinkDTC>,
  2. Substitute the interface in the COM interface map.
    COM_INTERFACE_ENTRY(IPersistStreamInit)
    COM_INTERFACE_ENTRY(IPersistPropertyBag)
  3. Add the property to the property map.
BEGIN_PROP_MAP(CSinkDTC)
PROP_DATA_ENTRY("_cx", m_sizeExtent.cx, VT_UI4)
PROP_DATA_ENTRY("_cy", m_sizeExtent.cy, VT_UI4)
PROP_ENTRY("SelectedFruit", 1, CLSID_NULL)
END_PROP_MAP()
  1. In the Get and Put methods for the choice, save the selected value to persist it.
    STDMETHODIMP CSinkDTC::get_SelectedFruit(BSTR *pVal)
    {
    m_sSelectedFruit.CopyTo(pVal);
    return S_OK;
    }
    
    STDMETHODIMP CSinkDTC::put_SelectedFruit(BSTR newVal)
    {
    m_sSelectedFruit = newVal;
    return S_OK;
    }
    

Identify the Control as a DTC and Compile It

You can identify the control as a DTC by specifying the GUID in the control's registry settings. Without this GUID, the control does not appear in the Design-time Control tab when customizing the Toolbox of the Visual InterDev editor.

To finish the DTC

  1. In the object's registry file (.rgs), specify registry settings to identify the control as a DTC.

How to   In the ATL project, open the object's .rgs file. This file is located under Resource and contains the registration information for the control. Add the text in boldface.

ForceRemove 'Programmable'
ForceRemove 'Implemented Categories'
{
'{73CEF3DD-AE85-11CF-A406-00AA00C00940}'
}
InprocServer32 = s '%MODULE%'
  1. In Visual C++, build your DTC.

Test Your DTC

You are now ready to test the DTC in Visual InterDev. This subscribing DTC works with the publishing DTC created in Visual Basic. You need to add the publishing DTC to test your DTC. You can register the Visual Basic sample, VBSample.ocx to use the SrcDTC provided with it. For more information, see the Code Sample Summary.

To test your DTC code

  1. In Visual InterDev 6.0, create or open a Web page.

  2. Add your control to the toolbox, then drag it to the Web page.

    How to   Right-click the toolbox and choose Customize Toolbox. In the dialog box, choose the Design Time Controls tab and locate your DTC. In the HTML Editor, choose the Source tab, and drag your control from the toolbox into your file and test it. If you can't locate the control, be sure that you have specified the GUID in the control's registry file.

  3. In the editor, test the functionality of your DTC.

  4. Verify the DTC's run-time output.

How to   From the View menu, choose View Run-Time Text.