Link to home
Start Free TrialLog in
Avatar of daniel_bigham
daniel_bigham

asked on

ATL COM Question: Array Properties

I'm just starting to dabble with ATL/COM in VC++.

I'd like to add a property which is an array.

For instance, with the VB listbox control, you can do:

MyListBox.List(5) = "Hey!"

How can I add an array property to my control using VC++ ATL/COM?

Avatar of daniel_bigham
daniel_bigham

ASKER

Just a clarification: I'm creating a DLL that you include via VB's References, not an ActiveX Control.
You can use SAFEARRAY for passing an array to an ATL COM component.

Here is an article
http://www.codeproject.com/dll/ctovbarray_passing.asp - Passing an array from VC++ DLL to VB

Good Luck
I've read that article, but it sounds like they're doing something different. I don't want to pass an array.
Ooops sorry, You mean control array...??
Thatz the property of VB right?
Actually no, it's not a control array.

List1.List(5) = "blah"

This code sets the text of the item of index 5 in listbox "List1" to be "blah". It's not an array of control. It's a property of a list box that acts as an array.
I have to check.
What you're trying to achieve is known as a default property.

MyListBox.List(5) = "Hey!"
is equivalent to
MyListBox.List.Item(5) = "Hey!"

where Item is the actual function name.
[Could be Items or another variation thereof. I'm not Vb-esque].

Default properties are only supported by automation clients, so even if you do implement it (and it is possible in COM), you still have to implement a non automation aware variation for those clients - typically C++.

Better to just create an Item method and have it accept a VARIANT argument, just as VB does. That way you can index items using ordinals or Ids. And it'll also be accesible by both automation and non-automation clients.
Great. Thanks for your help. So I want to look into creating methods which use VARIANT arguments?

Will "Item" be a method, not a property? What will be its return type and parameters?
ASKER CERTIFIED SOLUTION
Avatar of _ys_
_ys_

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Wow. Awesome response.

I'll take this code and see if I can get it to work. I'm just playing at this point wanting to write a COM component so that I can say "I've done that" incase someone asks "Have you ever", but I'm busy learning other things as well and I don't want to devote the time to go out, buy a book and read it. Besides, COM is an older technology that .NET ... I think... supercedes for the most part.

But like I said, I'd like to have some familiarity with it.

Thanks so much for your help. I was beginning to think my little COM endeavor was a dead end but you've given me what I need... !!
Just incase you're interested what I'm attempting to do with COM, here's the VB code that would make use of it. My component would be called "Table".

Dim t as Table
Set t = New Table

t.Rows = 2
t.Columns = 4

t.Width = 100
t.CellPadding = 0

t.Row(1).HAlign = tblCenter
t.Row(1).BackColor = RGB(128, 128, 128)
t.Row(1).TextColor = RGB(255, 255, 255)

t.Column(1).Width = 25
t.Column(2).Width = 25
t.Column(3).Width = 25
t.Column(4).Width = 25

t.SetCell 1, 1
t.SetPrintDirection tblHorizontal
t.Print "A"
t.Print "B"
t.Print "C"
t.Print "D"

t.Cell(2,1) = "Hello, world!"
t.Cell(2,1).NoWrap = True

t.Draw PictureBox1.HDC

----

And this would draw in the picture box the equivalent as Internet Explorers table defined by:

<table width="100%" cellspacing="0" cellpadding="0">
<tr align="center">
  <td width="25%" bgcolor="#888888"><font color="#ffffff">A</font></td>
  <td width="25%" bgcolor="#888888"><font color="#ffffff">B</font></td>
  <td width="25%" bgcolor="#888888"><font color="#ffffff">C</font></td>
  <td width="25%" bgcolor="#888888"><font color="#ffffff">D</font></td>
</tr>
<tr>
  <td width="25%" nowrap>Hello, world!</td>
  <td width="25%"></td>
  <td width="25%"></td>
  <td width="25%"></td>
</tr>

----

The idea to be able to create nicely formatted tables of information with the same flexability you have in IE, and to be able to display them in a standard Windows application.

Notice the line:

t.Cell(2,1) = "Hello, world!"

That is the sort of thing I want to do that caused me to ask the question.

Also,

t.Column(1).Width = 25

The whole "array" thing. Anyway, I'll see if I can make it work.
Good luck with your endeavors. No doubt we'll meet again.
Ok, I wasn't as close to victory as I thought.

For instance, I need to implement the methods like get__NewEnum, but I don't have any clue what code it needs to contain.
Maybe it would be easiest for you to email me a ZIP file containing a simple example of an implementation that has three objects:

Table, Rows, Row

IRows = IWhatevers
IRow = IWhatever

And IRow would have once property, BackColor.

So from VB you could do:

Dim MyTable as Table
Set MyTable = New Table
For Each Row r in MyTable.Rows
    r.BackColor = RGB(255,0,0)
Next

I think that would be enough to get me going... your response above fills in a lot of the details but I'm left with all sorts of questions without being able to see an implementation.

If it's really simple to do, that would be great. If implementing those objects is actually a fair bit of work, then I'll just have to keep plugging.

My email address is:
daniel.bigham@bigham.ca
np.

Give me a while to compose this for you.
In line with knowledge sharing, and since it's related to the question, I'll post it here, rather then zipping and emailing it.

Besides it's not too long ...


The sample code demonstrates the core functionality required to reproduce a collection class, which is then usable from non automation-only clients. i.e *NOT SCRIPTING LANGUAGES*

-------------------------------------------------------------------------

A common implementation is the IEnumXxxx suite of interfaces.

You'll have your CRow class.
A class CRows, a facade to an underlying collection.
A class CColRows, the real collection of rows.

class CRow : public IRow
{
...
};

class CRows : public IRows
{
public:
    CRows() : m_colRows(0) { m_pColRows = new CColRows({context}); }

    ~CRows() { delete m_pColRows; }
...
public:
//  IRows methods
    STDMETHOD(get__NewEnum) (IUnknown**);
    STDMETHOD(Item) (VARIANT, IRow**);
    STDMETHOD(get_Count) (long*);
    STDMETHOD(EnumRows) (IID*, void**);

private:
    CColRows* m_pColRows; // the underlying collection
};

class CColRows : public IEnumRow
{
    CColRows({context}) : m_lCount(0), m_lIndex(0) { ... }
...
public:
//  IEnumRow methods
    STDMETHOD(Next) (ULONG, IRow**, ULONG*);
    STDMETHOD(Skip) (ULONG);
    STDMETHOD(Reset) (void);
    STDMETHOD(Clone) (IEnumRow**);

private:
    {representation of collection ... std::map etc..}
    unsigned long m_lCount;
    long m_lIndex;
};

-------------------------------------------------------------------------

// Delegate to EnumRows - since they do the same thing
HRESULT CRows::get__NewEnum(IUnknown** ppEnum)
{
    if (NULL == ppEnum) return E_POINTER;

    return EnumRows(const_cast<IID*>(&IID_IRow), reinterpret_cast<void**>(ppEnum));
}

// Delegate to the underlying collection
HRESULT CRows::Item(VARIANT index, IRow** ppRow)
{
    if (NULL == ppRow) return E_POINTER;

    return m_pColRows->Item(index, *pRow);
}

HRESULT CColRows::Item(VARIANT &index, IRow* &pRow)
{
    switch(index.vt)
    {
    //  If the VARIANT is a BSTR, let's look up the Row by identifer!
        case VT_BSTR:
            return this->GetRow(std::wstring(index.bstrVal), pRow);

    //  If the VARIANT is a number, let's look up the Row by index!
        case VT_I4:
            return this->GetRow(index.lVal, pRow);
        case VT_I4 | VT_BYREF:
            return this->GetRow(*(index.plVal), pRow);
    case VT_I2:
            return this->GetRow(index.lVal, pRow);
        case VT_I2 | VT_BYREF:
            return this->GetRow(*(index.plVal), pRow);

    //  If the VARIANT is neither of those, we'll fail.
        default:
            return (pRow = NULL), E_FAIL;
    }
}

//  Delegate to the underlying collection
HRESULT CRows::get_Count(long* lCount)
{
    if (NULL == lCount) return E_POINTER;

    return m_pColRows->get_Count(*lCount);
}

HRESULT CColRows::get_Count(long &lCount)
{
      lCount = ...
      return S_OK;
}

//  Create a new snapshot of the collection.
//  Every enumeration _has_ to be distinct.
//  - Wouldn't be much use if all 'for each' statements iterated the _same instance_ !!
HRESULT CRows::EnumRows(IID* piid, void** ppEnum)
{
    if (NULL == ppEnum) return E_POINTER;

    CColRows* pRows = new CColRows({context});
    pRows->AddRef();
    HRESULT hr = pRows->QueryInterface(piid, ppEnum);
    pRows->Release();

    return hr;
}


BTW, IEnumRow looks like:
[
    object,
    uuid(xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx),
    helpstring("IEnumRow Interface"),
    pointer_default(unique),
    restricted
]
interface IEnumRow : IUnknown
{
    [helpstring("method Next")]
    HRESULT Next([in] ULONG celt, [out, size_is(celt), length_is(*pcFetched)] IRow** rgptr, [out] ULONG* pcFetched);

    [helpstring("method Skip")]
    HRESULT Skip([in] ULONG celt);

    [helpstring("method Reset")]
    HRESULT Reset(void);

    [helpstring("method Clone")]
    HRESULT Clone([out] IEnumRow** ppEnum);
};


HRESULT CColRows::Next(ULONG celt, IRow** rgptr, ULONG* pcFetched)
{
    if ((1 < celt) && (NULL == pcFetched)) return E_INVALIDARG;

    if (pcFetched) *pcFetched = 0;

    HRESULT hr = S_OK;
    unsigned long ulCount = 0;

    for (unsigned long i = 0; i < celt; ++i)
    {
        ++m_lIndex;

        if ((unsigned long)m_lIndex > m_lCount)
            break;

        if (NULL == rgptr) return E_POINTER;

    //  place an CRow [/IRow pointer] into *(rgptr++)
        if (FAILED(hr = this->NextRow(*(rgptr++))))
            break;

        ++ulCount;
    }

//  celt != *pcFetched
    if (celt != ulCount)
        hr = S_FALSE;

    if (NULL != pcFetched)
        *pcFetched = ulCount;

    return hr;
}

HRESULT CColRows::Skip(ULONG celt)
{
    HRESULT hr = S_OK;

    m_lIndex += celt;

    if ((unsigned long)m_lIndex > m_lCount)
    {
    //  c elt not skipped
        m_lIndex = m_lCount;
        hr = S_FALSE;
    }

    return hr;
}

HRESULT CColRows::Reset()
{
    m_lIndex = -1;
    return S_OK;
}

HRESULT CColRows::Clone(IEnumRow** ppEnum)
{
//  Repeat of CRows::EnumRows - create a new instance and QI etc.
}

-------------------------------------------------------------------------

A VB client could use this as:

Dim colRows As IRows
Set colRows = ...

For Each IRow In colRows
...


This is equivalent to the VC++ clients:

IRows* pRows = ...;

HRESULT hr;
IEnumRow* pEnumRow = NULL;
hr = pRows->EnumRows(const_cast<IID*>(&IID_IEnumRow), reinterpret_cast<void**>(&pEnumRow));

if (SUCCEEDED(hr))
{
    IRow* pRow[1] = {NULL};

    while (SUCCEEDED(hr) && (S_FALSE != hr))
        pEnumRow->Next(1, &pRow[0], NULL);

    pEnumRow->Release();
}


Enjoy !!
Will this code compile, or are there things within it that need to be implemented yet?
Some things need replaced:

uuid(xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)
all elipses
contextual comments {representation of collection ... std::map etc..}, {context}

You'll also need to provide an implementation for CColRows::GetRow.

HRESULT CColRows::GetRow(int iIndex, IRow* &pRow);
HRESULT CColRows::GetRow(std::wstring &sIndex, IRow* &pRow);

These just find a concrete CRow, having the index provided, and assign it to pRow - if your using std::map as your _real_ collection, just rip it from here.


If I was to do everything, you wouldn't learn as much.
Ok, thanks a bunch for your help. I'll see if I can "make it go"... this COM stuff sure is mysterious when you haven't read a book on it.

Mysterious, but not unreachable.