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.
The main tasks for subscribing to a choice are:
In Visual C++, you can use the wizards to create the project and the control.
To create the project
For example, the sample included in this SDK is a project name ATLSample.
Note This creates a new .DLL that does not support MFC.
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."
CSinkDTC()
{
m_sizeExtent.cx = 4000; // 100'ths of millimeters
m_sizeExtent.cy = 1500; // 100'ths of millimeters
m_sizeNatural = m_sizeExtent;
}
The IActiveDesigner interface provides the methods for using choices. The IDesignTimeControl provides the methods for generating the run-time text.
To add the interfaces
public IActiveDesigner,
public IDesignTimeControl
COM_INTERFACE_ENTRY_IID(IID_IActiveDesigner, IActiveDesigner)
COM_INTERFACE_ENTRY(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);
STDMETHOD(GetRuntimeClassID)(CLSID *pclsid);
STDMETHOD(GetRuntimeMiscStatusFlags)(DWORD *dwMiscFlags);
STDMETHOD(QueryPersistenceInterface)(REFIID riid);
STDMETHOD(SaveRuntimeState)(REFIID riidItf, REFIID riidObj, void *pObj);
STDMETHOD(GetExtensibilityObject)(IDispatch **ppvObjOut);
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.
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"
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
private:
CComPtr<IDesignTimeControlSite> m_spSite;
};
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;
}
Make sure that each of the interface methods are in the program file.
To add remaining methods for the ATL sample
Note You can find the IActiveDesigner definitions in Designer.h, and those for IDesignTimeControl in DTC60.h.
STDMETHODIMP CSinkDTC::get_DesignTimeControlSet(BSTR * pbstr)
{
(*pbstr) = NULL; // No-op
return S_OK;
}
STDMETHODIMP CSinkDTC::OnGetChoices(Choices *)
{
return S_OK; // No-op.
}
STDMETHODIMP CSinkDTC::OnRebind(Choices *)
{
return S_OK; // No-op.
}
STDMETHODIMP CSinkDTC::OnChoiceConflict(Choice *, VARIANT_BOOL)
{
return S_OK; // No-op.
}
STDMETHODIMP CSinkDTC::OnHostingChange(dtcHostingChange change, VARIANT_BOOL *)
{
return S_OK;
}
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
private:
CComPtr<IDesignTimeControlSite> m_spSite;
CComPtr<ChoiceSink> m_spChoiceSink;
};
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;
}
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.
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);
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;
}
PROP_ENTRY("SelectedFruit", 1, CLSID_NULL)
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;
}
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
STDMETHODIMP CSinkDTC::GetRuntimeClassID(CLSID *pclsid)
{
return E_NOTIMPL;
}
STDMETHODIMP CSinkDTC::GetRuntimeMiscStatusFlags(DWORD * pdw)
{
if (pdw == NULL)
return E_INVALIDARG;
*pdw = 0;
return S_OK;
}
STDMETHODIMP CSinkDTC::QueryPersistenceInterface(REFIID iid)
{
if (iid == IID_IPersistTextStream)
return S_OK;
return S_FALSE;
}
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;
}
STDMETHODIMP CSinkDTC::GetExtensibilityObject(IDispatch ** ppDisp)
{
return QueryInterface(IID_IDispatch, (void **) ppDisp);
}
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
public IPersistStreamInitImpl<CSinkDTC>
public IPersistPropertyBagImpl<CSinkDTC>,
COM_INTERFACE_ENTRY(IPersistStreamInit)
COM_INTERFACE_ENTRY(IPersistPropertyBag)
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()
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;
}
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
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%'
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
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.
How to From the View menu, choose View Run-Time Text.