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

ATL Splitter ActiveX control

By , 16 Nov 1999
 
  • Download demo project - 93.6 Kb
  • 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);
    } 
    

    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

    About the Author

    Andrew Garbuzov
    United States United States
    Member
    No Biography provided

    Sign Up to vote   Poor Excellent
    Add a reason or comment to your vote: x
    Votes of 3 or less require a comment

    Comments and Discussions

     
    You must Sign In to use this message board.
    Search this forum  
        Spacing  Noise  Layout  Per page   
    GeneralUse Splitter Control In Composite ATL Controlmemberbatuyp5 Oct '05 - 16:46 
    How do I use this Splitter in a Composite ATL Control ?
    Is there any thing like this?
    GeneralAssertion failure about spClientSitesussshawndu1 Feb '05 - 21:43 
    I wrote a dll control in ATL,i put the control in a richedit as ole object,
    but when a doubleclick the control ,it brought a assersion failure about m_spClientSite.what's wrong with my code ?what is the causes?
    GeneralCompiling errormemberHing20 Apr '04 - 16:31 
    Dear,
     
    While I compile your projects, it gives the following error:
     
    error C2668: 'InlineIsEqualGUID' : ambiguous call to overloaded function
     
    Hoping you can help me.
     
    Best regards,
    Hing
    GeneralRe: Compiling errormembergalroy30 Jul '07 - 2:45 
    Try
    ::ATL::InlineIsEqualGUID(*arr[i], riid)
    GeneralControls combo boxmemberokigan20 Nov '03 - 22:59 
    Where did he/she get the controls combo box (shown in the screenshot).
    I dont see any code to do that.
     

    Anybody has any ideas?
    GeneralWhen program starts i get this ATL errormemberTheTempest4 Oct '03 - 5:16 
    Hi,
     
    I added your ATL splitter control using ATL v3.0 and then i right clicked in my dialog editor and added it. I set the child panes with IDC_TREE1 and IDC_LIST1, just after making a tree/list view in the window.
     
    I posisioned the panes correctly (even thought u can't see them any more) and then when i compiled and executed the program. I got this error:
     
    ----------------------------------
    "Debug Assertion failed!".
     
    atlbase.h
    Line: 469
    Expression: p==0
    ----------------------------------
     
    And I am kind of new to ATL, i checked the line's and i have no idea what i did wrong. I also noticed that i can't see the listview or treeview. Any ideas?
    GeneralControls not visiblesmemberbibilm28 Mar '02 - 1:51 
    In VB 6.0, when I want select the FirstControl or the SecondControl in the Property Windows, I can't see any control.
     
    laurent
    GeneralATL 3.0 Beginner questionmemberSergio25 Nov '00 - 1:45 
    Hello, my name is Sergio and i am a beginner of ATL programming. I have
    Visual Studio 6, and ATL 3.0.
    I want write an ATL full control, and there is an initial problem; i explain:
    i create an ATL full control, and add WM_LBUTTONDOWN with the macro
    MESSAGE_HANDLER, in order to processing the virtual function OnLButtonDown.
    In that funcion my code is as follow:
     

    LRESULT CMyCobtrol::OnLButtonDown(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
    {
    if(m_bWndLess)
    {
    m_spInPlaceSite->SetFocus(TRUE);
    m_spInPlaceSite->SetCapture(TRUE);
    }

    if(!m_bWndLess)
    {
    SetFocus();
    SetCapture();
    }
    return 0;
    }
     

    I compile the project, and insert it in to two proof containers:
    a form of VB6 and a dialog of a MFC project and i note that in the
    first (VB6 form) m_bWndLwss is TRUE, while in the second (MFC dialog)
    m_bWndless is FALSE.
    In both containers, i insert a commandbutton standard.
    Compile both the proof project. In the VB project, when i click on
    my control, note that the commandbutton standard, change its style
    from BS_DEFPUSHBUTTON to BS_PUSHBUTTON, and it loose the dot rectangle
    of the focus. It is OK. In the MFC project when i click on my control,
    the commandbutton standard loose the focus rectangle, but it does not
    change its style from BS_DEFPUSHBUTTON to BS_PUSHBUTTON: tis style remain
    in BS_DEFPUSHBUTTON, even if the commandbutton standard does not the
    DEFAULT button.
    Have you any suggest for this problem?

    P.S. Excuse me for my bad english; my e-mail address is: sdavi@libero.it
     
    -Sergio-


    GeneralSplitter Control In Composite ATL ControlsussTom McAnnally13 Jun '00 - 10:56 
    Should this control work in an ATL based composite control.
     
    I would like to get something that looks like this...
     
    --------------
    | | |
    | | |
    | | |
    | | |
    --------------
    | |
    --------------
     
    Thanks,
    Tom McAnnally
    mcannall@yahoo.co
    GeneralRe: Splitter Control In Composite ATL ControlsussTom McAnnally13 Jun '00 - 10:58 
    My text was squished, but I would like to use two splitters, one for the bottom, and one for the middle of the two controls on the top. Kind of an explorer look.
     
    -To
    QuestionHow to make it word for Frame control in visual basic?susschong16 May '00 - 3:41 
    Just tried it last night. Works great in visual basic.
    One thing I noticed is it is working only for ActiveX
    control added to the toobox. It didn't work for Frame
    or other build in visual basic control such as Frame
    ,button, textbox ... Any ideas?
     
    thanks for the great work.
    AnswerRe: How to make it word for Frame control in visual basic?sussAdi7 Jul '00 - 2:13 
    Yes, I've read, that the buildin tools aren't exposing IDispatch. Perhaps Microsoft Forms (fm20.dll) which comes with office (I've not tried it)

    AnswerRe: How to make it word for Frame control in visual basic?memberHakanErd7 Mar '03 - 10: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 controlssussdchamp8 May '00 - 4:45 
    Is there a way to dynamically set the ActiveX controls contained in the splitter

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

    Permalink | Advertise | Privacy | Mobile
    Web04 | 2.6.130523.1 | Last Updated 17 Nov 1999
    Article Copyright 1999 by Andrew Garbuzov
    Everything else Copyright © CodeProject, 1999-2013
    Terms of Use
    Layout: fixed | fluid