Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

ATL Splitter ActiveX control

0.00/5 (No votes)
16 Nov 1999 2  
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 has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here