Link to home
Start Free TrialLog in
Avatar of IainHere
IainHere

asked on

ActiveX property array

I want my control (MFC) to expose a property for use in VB like this:

MyControl1.Caption(1).Font = Whatever

Where there are lots of captions, and each caption itself can have several properties.  I've seen this in commercial controls (without source code), but can't work out how to do it.
Avatar of Meir Rivkin
Meir Rivkin
Flag of Israel image

here is a wrapper class for VB-like property listbox:

//header file
#if !defined(AFX_PROPERTYLIST_H__74205380_1B56_11D4_BC48_00105AA2186F__INCLUDED_)
#define AFX_PROPERTYLIST_H__74205380_1B56_11D4_BC48_00105AA2186F__INCLUDED_

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
// PropertyList.h : header file
//

#define PIT_COMBO      0  //PIT = property item type
#define PIT_EDIT      1
#define PIT_COLOR      2
#define PIT_FONT      3
#define PIT_FILE      4

#define IDC_PROPCMBBOX   712
#define IDC_PROPEDITBOX  713
#define IDC_PROPBTNCTRL  714


/////////////////////////////////////////////////////////////////////////////
//CPropertyList Items
class CPropertyItem
{
// Attributes
public:
      CString m_propName;
      CString m_curValue;
      int m_nItemType;
      CString m_cmbItems;

public:
      CPropertyItem(CString propName, CString curValue,
                          int nItemType, CString cmbItems)
      {
            m_propName = propName;
            m_curValue = curValue;
            m_nItemType = nItemType;
            m_cmbItems = cmbItems;
      }
};

/////////////////////////////////////////////////////////////////////////////
// CPropertyList window

class CPropertyList : public CListBox
{
// Construction
public:
      CPropertyList();

// Attributes
public:

// Operations
public:
      int AddItem(CString txt);
      int AddPropItem(CPropertyItem* pItem);

// Overrides
      // ClassWizard generated virtual function overrides
      //{{AFX_VIRTUAL(CPropertyList)
      public:
      virtual void MeasureItem(LPMEASUREITEMSTRUCT lpMeasureItemStruct);
      virtual void DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct);
      protected:
      virtual BOOL PreCreateWindow(CREATESTRUCT& cs);
      virtual void PreSubclassWindow();
      //}}AFX_VIRTUAL

// Implementation
public:
      virtual ~CPropertyList();

      // Generated message map functions
protected:
      //{{AFX_MSG(CPropertyList)
      afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
      afx_msg void OnSelchange();
      afx_msg void OnLButtonUp(UINT nFlags, CPoint point);
      afx_msg void OnKillFocus(CWnd* pNewWnd);
      afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
      afx_msg void OnMouseMove(UINT nFlags, CPoint point);
      //}}AFX_MSG
      afx_msg void OnKillfocusCmbBox();
      afx_msg void OnSelchangeCmbBox();
      afx_msg void OnKillfocusEditBox();
      afx_msg void OnChangeEditBox();
      afx_msg void OnButton();

      DECLARE_MESSAGE_MAP()

      void InvertLine(CDC* pDC,CPoint ptFrom,CPoint ptTo);
      void DisplayButton(CRect region);

      CComboBox m_cmbBox;
      CEdit m_editBox;
      CButton m_btnCtrl;
      CFont m_SSerif8Font;
      
      int m_curSel,m_prevSel;
      int m_nDivider;
      int m_nDivTop;
      int m_nDivBtm;
      int m_nOldDivX;
      int m_nLastBox;
      BOOL m_bTracking;
      BOOL m_bDivIsSet;
      HCURSOR m_hCursorArrow;
      HCURSOR m_hCursorSize;
};

/////////////////////////////////////////////////////////////////////////////

//{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional declarations immediately before the previous line.

#endif // !defined(AFX_PROPERTYLIST_H__74205380_1B56_11D4_BC48_00105AA2186F__INCLUDED_)




//cpp file
// PropertyList.cpp : implementation file
//

#include "stdafx.h"
//#include "PropListBox.h"
#include "PropertyList.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

/////////////////////////////////////////////////////////////////////////////
// CPropertyList

CPropertyList::CPropertyList()
{
}

CPropertyList::~CPropertyList()
{
}


BEGIN_MESSAGE_MAP(CPropertyList, CListBox)
      //{{AFX_MSG_MAP(CPropertyList)
      ON_WM_CREATE()
      ON_CONTROL_REFLECT(LBN_SELCHANGE, OnSelchange)
      ON_WM_LBUTTONUP()
      ON_WM_KILLFOCUS()
      ON_WM_LBUTTONDOWN()
      ON_WM_MOUSEMOVE()
      //}}AFX_MSG_MAP
      ON_CBN_KILLFOCUS(IDC_PROPCMBBOX, OnKillfocusCmbBox)
      ON_CBN_SELCHANGE(IDC_PROPCMBBOX, OnSelchangeCmbBox)
      ON_EN_KILLFOCUS(IDC_PROPEDITBOX, OnKillfocusEditBox)
      ON_EN_CHANGE(IDC_PROPEDITBOX, OnChangeEditBox)
      ON_BN_CLICKED(IDC_PROPBTNCTRL, OnButton)
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CPropertyList message handlers

BOOL CPropertyList::PreCreateWindow(CREATESTRUCT& cs)
{
      if (!CListBox::PreCreateWindow(cs))
            return FALSE;

      cs.style &= ~(LBS_OWNERDRAWVARIABLE | LBS_SORT);
      cs.style |= LBS_OWNERDRAWFIXED;

      m_bTracking = FALSE;
      m_nDivider = 0;
      m_bDivIsSet = FALSE;

      return TRUE;
}

void CPropertyList::MeasureItem(LPMEASUREITEMSTRUCT lpMeasureItemStruct)
{
      lpMeasureItemStruct->itemHeight = 20; //pixels
}


void CPropertyList::DrawItem(LPDRAWITEMSTRUCT lpDIS)
{
      CDC dc;
      dc.Attach(lpDIS->hDC);
      CRect rectFull = lpDIS->rcItem;
      CRect rect = rectFull;
      if (m_nDivider==0)
            m_nDivider = rect.Width() / 2;
      rect.left = m_nDivider;
      CRect rect2 = rectFull;
      rect2.right = rect.left - 1;
      UINT nIndex = lpDIS->itemID;

      if (nIndex != (UINT) -1)
      {
            //draw two rectangles, one for each row column
            dc.FillSolidRect(rect2,RGB(192,192,192));
            dc.DrawEdge(rect2,EDGE_SUNKEN,BF_BOTTOMRIGHT);
            dc.DrawEdge(rect,EDGE_SUNKEN,BF_BOTTOM);

            //get the CPropertyItem for the current row
            CPropertyItem* pItem = (CPropertyItem*) GetItemDataPtr(nIndex);

            //write the property name in the first rectangle
            dc.SetBkMode(TRANSPARENT);
            dc.DrawText(pItem->m_propName,CRect(rect2.left+3,rect2.top+3,
                                                                  rect2.right-3,rect2.bottom+3),
                              DT_LEFT | DT_SINGLELINE);

            //write the initial property value in the second rectangle
            dc.DrawText(pItem->m_curValue,CRect(rect.left+3,rect.top+3,
                                                                  rect.right+3,rect.bottom+3),
                              DT_LEFT | DT_SINGLELINE);
      }
      dc.Detach();
}

int CPropertyList::AddItem(CString txt)
{
      int nIndex = AddString(txt);
      return nIndex;
}

int CPropertyList::AddPropItem(CPropertyItem* pItem)
{
      int nIndex = AddString(_T(""));
      SetItemDataPtr(nIndex,pItem);
      return nIndex;
}

int CPropertyList::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
      if (CListBox::OnCreate(lpCreateStruct) == -1)
            return -1;

      m_bDivIsSet = FALSE;
      m_nDivider = 0;
      m_bTracking = FALSE;

      m_hCursorSize = AfxGetApp()->LoadStandardCursor(IDC_SIZEWE);
      m_hCursorArrow = AfxGetApp()->LoadStandardCursor(IDC_ARROW);

      m_SSerif8Font.CreatePointFont(80,_T("MS Sans Serif"));

      return 0;
}

void CPropertyList::OnSelchange()
{
      CRect rect;
      CString lBoxSelText;

      //m_curSel = GetCurSel();

      GetItemRect(m_curSel,rect);
      rect.left = m_nDivider;

      CPropertyItem* pItem = (CPropertyItem*) GetItemDataPtr(m_curSel);

      if (m_btnCtrl)
            m_btnCtrl.ShowWindow(SW_HIDE);

      if (pItem->m_nItemType==PIT_COMBO)
      {
            //display the combo box.  If the combo box has already been
            //created then simply move it to the new location, else create it
            m_nLastBox = 0;
            if (m_cmbBox)
                  m_cmbBox.MoveWindow(rect);
            else
            {      
                  rect.bottom += 100;
                  m_cmbBox.Create(CBS_DROPDOWNLIST | CBS_NOINTEGRALHEIGHT | WS_VISIBLE | WS_CHILD | WS_BORDER,
                                          rect,this,IDC_PROPCMBBOX);
                  m_cmbBox.SetFont(&m_SSerif8Font);
            }

            //add the choices for this particular property
            CString cmbItems = pItem->m_cmbItems;
            lBoxSelText = pItem->m_curValue;
            
            m_cmbBox.ResetContent();
            m_cmbBox.AddString("");            
            int i,i2;
            i=0;
            while ((i2=cmbItems.Find('|',i)) != -1)
            {
                  m_cmbBox.AddString(cmbItems.Mid(i,i2-i));
                  i=i2+1;
            }

            m_cmbBox.ShowWindow(SW_SHOW);
            m_cmbBox.SetFocus();

            //jump to the property's current value in the combo box
            int j = m_cmbBox.FindStringExact(0,lBoxSelText);
            if (j != CB_ERR)
                  m_cmbBox.SetCurSel(j);
            else
                  m_cmbBox.SetCurSel(0);
      }
      else if (pItem->m_nItemType==PIT_EDIT)
      {
            //display edit box
            m_nLastBox = 1;
            m_prevSel = m_curSel;
            rect.bottom -= 3;
            if (m_editBox)
                  m_editBox.MoveWindow(rect);
            else
            {      
                  m_editBox.Create(ES_LEFT | ES_AUTOHSCROLL | WS_VISIBLE | WS_CHILD | WS_BORDER,
                                          rect,this,IDC_PROPEDITBOX);
                  m_editBox.SetFont(&m_SSerif8Font);
            }

            lBoxSelText = pItem->m_curValue;

            m_editBox.ShowWindow(SW_SHOW);
            m_editBox.SetFocus();
            //set the text in the edit box to the property's current value
            m_editBox.SetWindowText(lBoxSelText);
      }
      else
            DisplayButton(rect);
}

void CPropertyList::DisplayButton(CRect region)
{
      //displays a button if the property is a file/color/font chooser
      m_nLastBox = 2;
      m_prevSel = m_curSel;

      if (region.Width() > 25)
            region.left = region.right - 25;
      region.bottom -= 3;

      if (m_btnCtrl)
            m_btnCtrl.MoveWindow(region);
      else
      {      
            m_btnCtrl.Create("...",BS_PUSHBUTTON | WS_VISIBLE | WS_CHILD,
                                    region,this,IDC_PROPBTNCTRL);
            m_btnCtrl.SetFont(&m_SSerif8Font);
      }

      m_btnCtrl.ShowWindow(SW_SHOW);
      m_btnCtrl.SetFocus();
}

void CPropertyList::OnKillFocus(CWnd* pNewWnd)
{
      //m_btnCtrl.ShowWindow(SW_HIDE);

      CListBox::OnKillFocus(pNewWnd);
}

void CPropertyList::OnKillfocusCmbBox()
{
      m_cmbBox.ShowWindow(SW_HIDE);

      Invalidate();
}

void CPropertyList::OnKillfocusEditBox()
{
      CString newStr;
      m_editBox.ShowWindow(SW_HIDE);

      Invalidate();
}

void CPropertyList::OnSelchangeCmbBox()
{
      CString selStr;
      if (m_cmbBox)
      {
            m_cmbBox.GetLBText(m_cmbBox.GetCurSel(),selStr);
            CPropertyItem* pItem = (CPropertyItem*) GetItemDataPtr(m_curSel);
            pItem->m_curValue = selStr;
      }
}

void CPropertyList::OnChangeEditBox()
{
      CString newStr;
      m_editBox.GetWindowText(newStr);
      
      CPropertyItem* pItem = (CPropertyItem*) GetItemDataPtr(m_curSel);
      pItem->m_curValue = newStr;
}

void CPropertyList::OnButton()
{
      CPropertyItem* pItem = (CPropertyItem*) GetItemDataPtr(m_curSel);

      //display the appropriate common dialog depending on what type
      //of chooser is associated with the property
      if (pItem->m_nItemType == PIT_COLOR)
      {
            COLORREF initClr;
            CString currClr = pItem->m_curValue;
            //parse the property's current color value
            if (currClr.Find("RGB") > -1)
            {
                  int j = currClr.Find(',',3);
                  CString bufr = currClr.Mid(4,j-4);
                  int RVal = atoi(bufr);
                  int j2 = currClr.Find(',',j+1);
                  bufr = currClr.Mid(j+1,j2-(j+1));
                  int GVal = atoi(bufr);
                  int j3 = currClr.Find(')',j2+1);
                  bufr = currClr.Mid(j2+1,j3-(j2+1));
                  int BVal = atoi(bufr);
                  initClr = RGB(RVal,GVal,BVal);
            }
            else
                  initClr = 0;
            
            CColorDialog ClrDlg(initClr);
            
            if (IDOK == ClrDlg.DoModal())
            {
                  COLORREF selClr = ClrDlg.GetColor();
                  CString clrStr;
                  clrStr.Format("RGB(%d,%d,%d)",GetRValue(selClr),
                                    GetGValue(selClr),GetBValue(selClr));
                  m_btnCtrl.ShowWindow(SW_HIDE);

                  pItem->m_curValue = clrStr;
                  Invalidate();
            }
      }
      else if (pItem->m_nItemType == PIT_FILE)
      {
            CString SelectedFile;
            CString Filter("Gif Files (*.gif)|*.gif||");
      
            CFileDialog FileDlg(TRUE, NULL, NULL, NULL,
                  Filter);
            
            CString currPath = pItem->m_curValue;
            FileDlg.m_ofn.lpstrTitle = "Select file";
            if (currPath.GetLength() > 0)
                  FileDlg.m_ofn.lpstrInitialDir = currPath.Left(
                        currPath.GetLength() - currPath.ReverseFind('\\'));

            if(IDOK == FileDlg.DoModal())
            {
                  SelectedFile = FileDlg.GetPathName();
                  
                  m_btnCtrl.ShowWindow(SW_HIDE);
                  
                  pItem->m_curValue = SelectedFile;
                  Invalidate();
            }
      }
      else if (pItem->m_nItemType == PIT_FONT)
      {      
            CFontDialog FontDlg(NULL,CF_EFFECTS | CF_SCREENFONTS,NULL,this);
            
            if(IDOK == FontDlg.DoModal())
            {
                  CString faceName = FontDlg.GetFaceName();
                  
                  m_btnCtrl.ShowWindow(SW_HIDE);
                  
                  pItem->m_curValue = faceName;
                  Invalidate();
            }
      }
}

void CPropertyList::OnLButtonUp(UINT nFlags, CPoint point)
{
      if (m_bTracking)
      {
            //if columns were being resized then this indicates
            //that mouse is up so resizing is done.  Need to redraw
            //columns to reflect their new widths.
            
            m_bTracking = FALSE;
            //if mouse was captured then release it
            if (GetCapture()==this)
                  ::ReleaseCapture();

            ::ClipCursor(NULL);

            CClientDC dc(this);
            InvertLine(&dc,CPoint(point.x,m_nDivTop),CPoint(point.x,m_nDivBtm));
            //set the divider position to the new value
            m_nDivider = point.x;

            //redraw
            Invalidate();
      }
      else
      {
            BOOL loc;
            int i = ItemFromPoint(point,loc);
            m_curSel = i;
            CListBox::OnLButtonUp(nFlags, point);
      }
}

void CPropertyList::OnLButtonDown(UINT nFlags, CPoint point)
{
      if ((point.x>=m_nDivider-5) && (point.x<=m_nDivider+5))
      {
            //if mouse clicked on divider line, then start resizing

            ::SetCursor(m_hCursorSize);

            CRect windowRect;
            GetWindowRect(windowRect);
            windowRect.left += 10; windowRect.right -= 10;
            //do not let mouse leave the list box boundary
            ::ClipCursor(windowRect);
            
            if (m_cmbBox)
                  m_cmbBox.ShowWindow(SW_HIDE);
            if (m_editBox)
                  m_editBox.ShowWindow(SW_HIDE);

            CRect clientRect;
            GetClientRect(clientRect);

            m_bTracking = TRUE;
            m_nDivTop = clientRect.top;
            m_nDivBtm = clientRect.bottom;
            m_nOldDivX = point.x;

            CClientDC dc(this);
            InvertLine(&dc,CPoint(m_nOldDivX,m_nDivTop),CPoint(m_nOldDivX,m_nDivBtm));

            //capture the mouse
            SetCapture();
      }
      else
      {
            m_bTracking = FALSE;
            CListBox::OnLButtonDown(nFlags, point);
      }
}

void CPropertyList::OnMouseMove(UINT nFlags, CPoint point)
{      
      if (m_bTracking)
      {
            //move divider line to the mouse pos. if columns are
            //currently being resized
            CClientDC dc(this);
            //remove old divider line
            InvertLine(&dc,CPoint(m_nOldDivX,m_nDivTop),CPoint(m_nOldDivX,m_nDivBtm));
            //draw new divider line
            InvertLine(&dc,CPoint(point.x,m_nDivTop),CPoint(point.x,m_nDivBtm));
            m_nOldDivX = point.x;
      }
      else if ((point.x >= m_nDivider-5) && (point.x <= m_nDivider+5))
            //set the cursor to a sizing cursor if the cursor is over the row divider
            ::SetCursor(m_hCursorSize);
      else
            CListBox::OnMouseMove(nFlags, point);
}

void CPropertyList::InvertLine(CDC* pDC,CPoint ptFrom,CPoint ptTo)
{
      int nOldMode = pDC->SetROP2(R2_NOT);
      
      pDC->MoveTo(ptFrom);
      pDC->LineTo(ptTo);

      pDC->SetROP2(nOldMode);
}

void CPropertyList::PreSubclassWindow()
{
      m_bDivIsSet = FALSE;
      m_nDivider = 0;
      m_bTracking = FALSE;
      m_curSel = 1;

      m_hCursorSize = AfxGetApp()->LoadStandardCursor(IDC_SIZEWE);
      m_hCursorArrow = AfxGetApp()->LoadStandardCursor(IDC_ARROW);

      m_SSerif8Font.CreatePointFont(80,_T("MS Sans Serif"));
}




example of usage:
in your dialog add a listbox and change its type to - CPropertyList      m_propList;

also change its properties as follow:
selection: single
Owner draw: variable

the follow properties to to be checked:
Has strings,
Notify,
Border,
Vertical scroll,
No integral Height,
Modal frame.


add the follow data members to your CDialog:

      CPropertyItem *propItem1,*propItem2,*propItem3,*propItem4,*propItem5;
      CPropertyItem *propItem6,*propItem7,*propItem8,*propItem9,*propItem10;
      CPropertyItem *propItem11,*propItem12,*propItem13,*propItem14,*propItem15;
      CPropertyItem *propItem16,*propItem17;


and add them in the CYourDialog::OnInitDialog() :

      //Add properties to the property list box

      propItem1 = new CPropertyItem("ToolTip Text","",PIT_EDIT,"");
      m_propList.AddPropItem(propItem1);

      propItem2 = new CPropertyItem("Enabled","true",PIT_COMBO,"true|false|");
      m_propList.AddPropItem(propItem2);

      propItem3 = new CPropertyItem("Visible","true",PIT_COMBO,"true|false|");
      m_propList.AddPropItem(propItem3);

      propItem4 = new CPropertyItem("Fore. Color","",PIT_COLOR,"");
      m_propList.AddPropItem(propItem4);
      
      propItem5 = new CPropertyItem("Back. Color","",PIT_COLOR,"");
      m_propList.AddPropItem(propItem5);
      
      propItem6 = new CPropertyItem("Opaque","false",PIT_COMBO,"true|false|");
      m_propList.AddPropItem(propItem6);
      
      propItem7 = new CPropertyItem("Auto. Scroll","true",PIT_COMBO,"true|false|");
      m_propList.AddPropItem(propItem7);
      
      propItem8 = new CPropertyItem("Double Buffered","true",PIT_COMBO,"true|false|");
      m_propList.AddPropItem(propItem8);
      
      propItem9 = new CPropertyItem("Font","",PIT_FONT,"");
      m_propList.AddPropItem(propItem9);

      propItem10 = new CPropertyItem("Text","",PIT_EDIT,"");
      m_propList.AddPropItem(propItem10);

      propItem11 = new CPropertyItem("Horiz. Align","CENTER",PIT_COMBO,"CENTER|LEFT|RIGHT|");
      m_propList.AddPropItem(propItem11);
      
      propItem12 = new CPropertyItem("Vert. Align","CENTER",PIT_COMBO,"CENTER|TOP|BOTTOM|");
      m_propList.AddPropItem(propItem12);

      propItem13 = new CPropertyItem("Icon","",PIT_FILE,"");
      m_propList.AddPropItem(propItem13);

      propItem14 = new CPropertyItem("Border Painted","",PIT_COMBO,"true|false|");
      m_propList.AddPropItem(propItem14);

      propItem15 = new CPropertyItem("Fill Content Area","",PIT_COMBO,"true|false|");
      m_propList.AddPropItem(propItem15);

      propItem16 = new CPropertyItem("Focus Painted","",PIT_COMBO,"true|false|");
      m_propList.AddPropItem(propItem16);

      propItem17 = new CPropertyItem("Horiz. Text Pos.","RIGHT",PIT_COMBO,"RIGHT|LEFT|CENTER|LEADING|TRAILING|");
      m_propList.AddPropItem(propItem17);


also in your dialog's destructor add this:
      delete propItem1;
      delete propItem2;
      delete propItem3;
      delete propItem4;
      delete propItem5;
      delete propItem6;
      delete propItem7;
      delete propItem8;
      delete propItem9;
      delete propItem10;
      delete propItem11;
      delete propItem12;
      delete propItem13;
      delete propItem14;
      delete propItem15;
      delete propItem16;
      delete propItem17;


good luck


Avatar of IainHere
IainHere

ASKER

sedgwick,

That's a lot of info, and I might have missed it in there, but I'm looking for help with a VB interface for an MFC ActiveX control.

When I've dropped my control on the VB form, I want one of the properties to be an array of objects that contain several other properties.  Referring to my example above

MyControl1.Caption(1).Font = Whatever

there would be, say, 10 Captions available, and this would be the method for setting each of their Fonts, sizes, colo[u]rs etc.

Thanks for your help.
The general idea:
You expose each Caption as instance of another "Caption" COM object. Caption cannot created by a CLSID (i.e. it's not listed in the IDL's "library section", and deliberately removed from the place where items <are listed for GetClassObject - I know only in ATL); rather, Caption can only be created by the Listbox object.

You object holds a list of Caption objects, and the Caption array property just returns the requested one.

Further thing to take care of: Expose the "Captions" property as collection (dunno how in MFC), so client can "for each Caption in MyControl.Captions".

Peter
Excellent - I think I understand now:  the property that I want to be an array has itself to be a COM object.  Do you know of any links to example code / explanations of how to do it? (In ATL would be fine, because I think I'll use ATL for them)

Thanks.
ASKER CERTIFIED SOLUTION
Avatar of peterchen092700
peterchen092700

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
That's simply wonderful.  Thanks.