65.9K
CodeProject 正在变化。 阅读更多。
Home

ATL 分割器 ActiveX 控件

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.67/5 (6投票s)

1999年11月17日

CPOL

4分钟阅读

viewsIcon

157776

downloadIcon

2375

将我的 MFC 分割器 ActiveX 控件移植到 ATL。

Sample Image

引言

在我发布了关于 MFC 分割器 ActiveX 控件的文章后,我收到了一些关于用 ATL 实现相同控件可能性的消息。最近我有一些空闲时间,决定尝试一下 ATL。我的实验结果在这里。

由于 ATL 没有像 MFC 那样任何分割窗口类,我不得不自己实现一些这样的窗口功能。我没有尝试创建任何很酷的东西,所以如果你觉得我的实现很丑,我很乐意收到你的代码。这里我只提供一些小的注释,因为没有添加什么非常重要的东西。你可能想从 这里 下载我之前的文章,以获取关于源代码的更多注释。

如何使用该控件

你可以阅读我上面提到的上一篇文章,了解如何在 ActiveX 控件容器中使用该控件。此版本与前一个版本的工作方式相同。从用户的角度来看,此控件只是具有不同的属性名称。SplitterPercentPosition 属性描述了分割条的百分比(而非绝对)位置。此属性的可能值从 0 到 100 不等。该控件已实现为支持无窗口激活,尽管它仍然使用隐藏窗口来处理 WM_TIMER 消息。我稍后会说明为什么需要定时器。如果你知道如何避免窗口的需要,我将非常乐意收到你的解决方案。

此版本支持 IPerPropertyBrowsing 接口,因此你可以在容器的属性窗口中进行控件绑定。虽然(依我看来)唯一可以很好地使用此接口的容器是 VB6(我没有尝试 VB5)。

实现

由于我不得不自己绘制分割窗口,我不得不从 MFC 移植一些代码。在 Splitter.cpp 文件中,你会找到几个用于进行图形绘制的函数。有几个方法用于在控件大小调整时计算窗格的坐标。代码中没有棘手的部分,而且它也不是非常好。

为了跟踪分割条,我不得不处理鼠标消息,如 WM_LBUTTONDOWN、WM_MOUSEMOVE 和 WM_LBUTTONUP。为了在用户将鼠标移到分割条上时设置适当的鼠标光标,我不得不处理 WM_SETCURSOR 消息。在处理这些消息时,我没有依赖描述鼠标位置的参数。相反,我使用了我自己的方法 GetMouseCursorPosInContainerCoords,以避免在控件放置在不支持无窗口激活的容器中的差异。

GetExtendedName 和 GetOtherControlsOnTheContainer 等方法只是从前一个版本移植过来的。差异很小,并不重要。PlaceControlOnPane 方法只有很小的差异。但它很重要,因为它产生了更好的结果。我也应该在 MFC 版本中使用它。在更改窗格控件的位置时,我不再调用 SetObectRects,而是使用容器站点对象的 OnPosRectChange 方法。这是一段执行此操作的代码。

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); 
      }
  } 
  

当需要将子控件放置在窗格中时,会调用 AdjustControlsToPanes。但是,此方法是从 DelayAdjustControlsToPanes 间接调用的。我需要这样做是因为控件容器不知道我的技巧,并且可能会在分割控件已经将子控件放置在其窗格中时执行一些更改。此外,在某些情况下,子控件甚至可能不存在,例如当容器正在加载和创建控件时。当需要重新定位时,我们只需通过调用 DelayAdjustControlsToPanes 来通知分割控件我们要进行重新定位。在适当的时间,将通过调用 AdjustControlsToPanes 来执行重新定位。通过实验,我发现适当的时间可以是处理 WM_TIMER 消息,因为它优先级很低,并且所有过渡效果都已消失。我们的分割控件可以在没有窗口的情况下激活,因此为了处理 WM_TIMER 消息,我创建了一个隐藏窗口,该窗口存储在 m_wndHidden 成员中。DelayAdjustControlsToPanes 方法除了为隐藏窗口设置定时器外,不做其他事情。

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); 
  }
  

隐藏窗口通过调用实际的重新定位方法来处理 WM_TIMER 消息。

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

实现了 IPerPropertyBrowsing 接口后,除了提高绑定能力外,我还解决了将窗体上的控件列表传递给属性页的问题,从而使前一个 MFC 版本中丑陋的 _pointer 属性变得无用。

//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; icElems; 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);
} 

以下是接口在控件上的实现方式。请注意,此实现很糟糕,因为它不检查内存分配的任何成功。根据文档,在任何失败时,我都应该释放所有成功分配的内存并返回 E_FAIL。我没有这样做,因为这种情况发生的几率非常非常低,只是为了节省我的时间。你可以检查 COleControl 类对该接口的 MFC 实现。

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; 
} 

我希望你会喜欢这个控件。如果你有任何改进,请发送你的代码给我。

© . All rights reserved.