Click here to Skip to main content
Click here to Skip to main content

ATL Splitter ActiveX control

, 16 Nov 1999 CPOL
Rate this:
Please Sign up or sign in to vote.
A port of my splitter ActiveX control with MFC to ATL.

Sample Image

Introduction

After publishing my article about splitter ActiveX control with MFC I have got some messages asking about the possibility of implementing the same control with ATL. Quite recently I had some free time and decided to try myself in ATL. The results of my experiments are here.

Since ATL does not have any splitter window class like MFC I had to implement some functionality of such window myself. I did not try to create anything cool so if you find my implementation ugly I would be happy to get your code. Here I will just provide some small comments since nothing very important has been added. You might want to download my previous article from here to get more comments about the source code.

How to use the control

You can read my previous article mentioned above to see how to use the control in ActiveX control containers. This version works the same way as the previous one. From the user's point of view this control just has different property names. The SplitterPercentPosition property describes the percent (not absolute) position of the splitter bar. Possible values of this property vary from 0 to 100. The control has been implemented to support windowless activation, although it still uses hidden window to process WM_TIMER messages. Why I needed a timer I will describe later. If you know how to avoid the need of the window, I would be glad if you sent me your solution.

This version supports IPerPropertyBrowsing interface so you can make control bindings in the Properties window of the container. Although the only container which works ok with this interface (my own opinion) is VB6 (I did not try VB5).

Implementation

Since I had to draw the splitter window myself I had to port some code from MFC. In Splitter.cpp file you will find several functions to do some graphics. Several methods are used to calculate the coordinates of panes when control is resized. There is nothing tricky in the code and moreover it is not very good.

To track the splitter bar I had to process mouse messages like WM_LBUTTONDOWN, WM_MOUSEMOVE and WM_LBUTTONUP. To set appropriate mouse cursor when user moves mouse over the splitter bar I had to process WM_SETCURSOR message. While processing these messages I did not rely on the parameter describing mouse position. Instead I used my own method GetMouseCursorPosInContainerCoords to avoid differences when the control is placed in container, which does not support windowless activation.

Methods like GetExtendedName and GetOtherControlsOnTheContainer were just ported from the previous version. The differences are minor and not important. There is a very little difference in the PlaceControlOnPane method. But it is very important since it produces better results. I should use it in the MFC version too. When changing the position of a pane control, instead of calling SetObectRects, I use now OnPosRectChange method of the container's site object. Here is a portion of code, which does this.

void CSplitter::PlaceControlOnPane(const BSTR strControlName,const RECT& rectPane) 
  { 
      //Detect if there any control with such name on the form 
      int nPos=m_arrayControls.IndexOf(strControlName); 
      if(nPos!=-1) 
      { 
          //Get the pointer to the control 
          IOleObjectPtr pOleObject=m_arrayControls[nPos].m_pOleObject; 
          
          //Get container's site for the control 
          IOleClientSitePtr pSite; 
          pOleObject->GetClientSite(&pSite); 
          //Ask for in-place site pointer and notify the container about changes 
          IOleInPlaceSitePtr pIPSite=pSite; 
          pIPSite->OnPosRectChange(&rectPane); 
          
          //Change the control's extents 
          SIZE size; 
          size.cx=rectPane.right-rectPane.left; 
          size.cy=rectPane.bottom-rectPane.top; 
          
          HWND hwndContainer=GetContainerWindow(m_spClientSite); 
          HDC hdc=::GetDC(hwndContainer); 
          DPtoHIMETRIC(hdc, &size); 
          ::ReleaseDC(hwndContainer,hdc); 
          pOleObject->SetExtent(DVASPECT_CONTENT,&size); 
      }
  } 
  

When it is needed to place the child controls into panes - AdjustControlsToPanes is called. But this method is called indirectly from DelayAdjustControlsToPanes. I needed this because the control container does not know about my tricks and can perform some changes when the splitter control has already placed the child controls in its panes. Moreover, in some situations the child controls might not even exist, for instance when the container is loading and creating the controls. When reposition is needed we just inform the splitter control that we want to do the reposition by calling DelayAdjustControlsToPanes. At appropriate time the reposition will be performed by calling AdjustControlsToPanes. From experiments I have found that appropriate time can be a processing WM_TIMER message since it has very little priority and all transition effects are gone. Our splitter control can be activated without a window, so to process WM_TIMER messages I created a hidden one, which the control stores in the m_wndHidden member. The DelayAdjustControlsToPanes method does nothing more than setting timer for the hidden window.

void CSplitter::DelayAdjustControlsToPanes() 
  { 
      if(!m_wndHidden.m_hWnd) 
      { 
          RECT rect; 
          rect.left=rect.top=rect.right=rect.bottom=0; 
          m_wndHidden.Create(GetContainerWindow(m_spClientSite),rect); 
      } 
      ::SetTimer(m_wndHidden.m_hWnd,1,55,NULL); 
  }
  

The hidden window processes WM_TIMER message by calling the actual reposition method.

    
  LRESULT CHiddenWindow::OnTimer(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) 
  { 
      KillTimer(wParam); 
      if(m_pSplitter) 
          m_pSplitter->AdjustControlsToPanes();
      bHandled=TRUE; 
      return 0; 
  } 
  

Having implemented IPerPropertyBrowsing interface, besides improving the binding capability, I have also solved the problem of passing the list of controls on the form to the property page, thus making the ugly _pointer property from the previous MFC version useless.

//Filling comboboxes 
if(m_nObjects>0) 
{ 
    CComQIPtr<IPerPropertyBrowsing, &IID_IPerPropertyBrowsing> pPPB(m_ppUnk[0]); 
    CALPOLESTR* pCALPOLESTR; 
    CADWORD* pCADWORD; 
    pCALPOLESTR=(CALPOLESTR*)CoTaskMemAlloc(sizeof(CALPOLESTR)); 
    pCADWORD=(CADWORD*)CoTaskMemAlloc(sizeof(CADWORD)); 
    if(SUCCEEDED(pPPB->GetPredefinedStrings(DISPID_FIRST_CONTROL_NAME, 
        pCALPOLESTR, 
        pCADWORD)))
    { 
        for(int i=0; i<pcalpolestr->cElems; i++) 
        { 
            TCHAR* lpstrElem=OLE2T(pCALPOLESTR->pElems[i]);
            CoTaskMemFree(pCALPOLESTR->pElems[i]); 
            SendDlgItemMessage(IDC_COMBO_FIRST, 
                CB_ADDSTRING, 
                0, 
                LPARAM(lpstrElem)); 
            
            SendDlgItemMessage(IDC_COMBO_SECOND, 
                CB_ADDSTRING, 
                0, 
                LPARAM(lpstrElem));
        } 
        
        CoTaskMemFree(pCALPOLESTR->pElems); 
        CoTaskMemFree(pCADWORD->pElems); 
    } 
    if(pCADWORD) 
        CoTaskMemFree(pCADWORD);
    if(pCALPOLESTR) 
        CoTaskMemFree(pCALPOLESTR);
} 
</pcalpolestr->

Here is how the interface has been implemented on the control. Notice please that this implementation is bad, because it does not check any success on memory allocation. According to the documentation, in any failure I should have freed all successfully allocated memory and return E_FAIL. I did not do this because it happens VERY rarely and just to save my time. You might check MFC implementation of this interface for COleControl class.

STDMETHODIMP CSplitter::GetPredefinedStrings(DISPID dispID, CALPOLESTR *pCaStringsOut,CADWORD *pCaCookiesOut) 
{ 
    ATLTRACE2(atlTraceControls, 2, 
        _T("IPerPropertyBrowsingImpl::GetPredefinedStrings\n"));
    
    if (pCaStringsOut == NULL || pCaCookiesOut == NULL) 
        return E_POINTER;
    
    pCaStringsOut->cElems = 0; 
    pCaStringsOut->pElems = NULL; 
    pCaCookiesOut->cElems = 0; 
    pCaCookiesOut->pElems = NULL; 
    
    if(dispID==DISPID_FIRST_CONTROL_NAME || dispID==DISPID_SECOND_CONTROL_NAME) 
    { 
        GetOtherControlsOnTheContainer(); 
        if(m_arrayControls.size()>0) 
        { 
            pCaStringsOut->cElems=pCaCookiesOut->cElems=m_arrayControls.size(); 
            pCaStringsOut->pElems=(LPOLESTR*)CoTaskMemAlloc(pCaStringsOut->cElems*sizeof(LPOLESTR)); 
            pCaCookiesOut->pElems=(DWORD*)CoTaskMemAlloc(pCaCookiesOut->cElems*sizeof(DWORD)); 
            for(int i=0; i<m_arrayControls.size(); i++) 
            { 
                pCaCookiesOut->pElems[i]=i; 
                CControlInfo& ci=m_arrayControls[i]; 
                pCaStringsOut->pElems[i]=(LPOLESTR)CoTaskMemAlloc((ci.m_strName.Length()+1)*sizeof(OLECHAR)); 
                wcscpy(pCaStringsOut->pElems[i],ci.m_strName.m_str); 
            }
        }
    } 
    
    return S_OK;
} 

STDMETHODIMP CSplitter::GetPredefinedValue(DISPID dispID, DWORD dwCookie, VARIANT* pVarOut) 
{ 
    if(dispID==DISPID_FIRST_CONTROL_NAME || dispID==DISPID_SECOND_CONTROL_NAME) 
    { 
        if(dwCookie>=0 && dwCookie<m_arrayControls.size()) 
        { 
            V_VT(pVarOut)=VT_BSTR; 
            V_BSTR(pVarOut)=m_arrayControls[dwCookie].m_strName.Copy(); 
            return S_OK; 
        }
    } 
    return E_FAIL; 
} 

I hope you will like this control. If you have any improvements please send me you code.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Andrew Garbuzov

United States United States
No Biography provided

Comments and Discussions

 
GeneralUse Splitter Control In Composite ATL Control Pinmemberbatuyp5-Oct-05 17:46 
GeneralAssertion failure about spClientSite Pinsussshawndu1-Feb-05 22:43 
GeneralCompiling error PinmemberHing20-Apr-04 17:31 
GeneralRe: Compiling error Pinmembergalroy30-Jul-07 3:45 
GeneralControls combo box Pinmemberokigan20-Nov-03 23:59 
GeneralWhen program starts i get this ATL error PinmemberTheTempest4-Oct-03 6:16 
GeneralControls not visibles Pinmemberbibilm28-Mar-02 2:51 
GeneralATL 3.0 Beginner question PinmemberSergio25-Nov-00 2:45 
GeneralSplitter Control In Composite ATL Control PinsussTom McAnnally13-Jun-00 11:56 
GeneralRe: Splitter Control In Composite ATL Control PinsussTom McAnnally13-Jun-00 11:58 
QuestionHow to make it word for Frame control in visual basic? Pinsusschong16-May-00 4:41 
AnswerRe: How to make it word for Frame control in visual basic? PinsussAdi7-Jul-00 3:13 
AnswerRe: How to make it word for Frame control in visual basic? PinmemberHakanErd7-Mar-03 11:47 
One should rewrite it using a QI to IOleControlSite,
in GetOtherControlsOnTheContainer(), like stated
in the MSDN under Q141414.
 
This should even overcome the nasty resource leaks when you place
the splitter control on a VB UserControl...
 
BTW, builtin controls do expose an IDispatch interface but...
GeneralDynamically setting ActiveX controls Pinsussdchamp8-May-00 5:45 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.1411028.1 | Last Updated 17 Nov 1999
Article Copyright 1999 by Andrew Garbuzov
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid