1.简介
CVerticalLayoutUI,其所有的直接子控件都是从上到下,垂直分布的,故称为垂直布局。垂直布局继承于 CContainerUI,其中大部分方法 CContainerUI 已经实现。
CContainerUI为所有容器类的基础类,其实现了 IContainerUI 接口中的所有方法。如果想要实现新的布局类型,可以继承此方法,并重写其中的 SetPos() 函数。
本节将介绍 IContainerUI 接口函数在 CContainerUI 中的实现 和 CVerticalLayoutUI 中 SetPos() 的实现。
2. IContainerUI 接口
class IContainerUI
{
public:
virtual CControlUI* GetItemAt(int iIndex) const = 0;
virtual int GetItemIndex(CControlUI* pControl) const = 0;
virtual bool SetItemIndex(CControlUI* pControl, int iIndex) = 0;
virtual int GetCount() const = 0;
virtual bool Add(CControlUI* pControl) = 0;
virtual bool AddAt(CControlUI* pControl, int iIndex) = 0;
virtual bool Remove(CControlUI* pControl) = 0;
virtual bool RemoveAt(int iIndex) = 0;
virtual void RemoveAll() = 0;
};
3. CContainerUI 对 IContainerUI 接口的实现
3.1 获取第i个子控件:GetItemAt()
CControlUI* CContainerUI::GetItemAt(int iIndex) const
{
if( iIndex < 0 || iIndex >= m_items.GetSize() )
return NULL;
return static_cast<CControlUI*>(m_items[iIndex]);
}
3.2 获取子控件的序号:GetItemIndex()
int CContainerUI::GetItemIndex(CControlUI* pControl) const
{
for( int it = 0; it < m_items.GetSize(); it++ )
{
if( static_cast<CControlUI*>(m_items[it]) == pControl )
{
return it;
}
}
//不存在返回-1
return -1;
}
3.3 为子控件设置序号:SetItemIndex()
bool CContainerUI::SetItemIndex(CControlUI* pControl, int iIndex)
{
for( int it = 0; it < m_items.GetSize(); it++ )
{
if( static_cast<CControlUI*>(m_items[it]) == pControl )
{
//重新排序的话需要更新子控件位置
NeedUpdate();
m_items.Remove(it);
return m_items.InsertAt(iIndex, pControl);
}
}
return false;
}
3.4 获取子控件个数:GetCount()
int CContainerUI::GetCount() const
{
return m_items.GetSize();
}
3.5 添加子控件(尾部):Add()
bool CContainerUI::Add(CControlUI* pControl)
{
if( pControl == NULL) return false;
if( m_pManager != NULL )
{
//设pControl的父控件为this
m_pManager->InitControls(pControl, this);
}
if( IsVisible() )
{
//位置需要更新
NeedUpdate();
}
else
{
pControl->SetInternVisible(false);
}
return m_items.Add(pControl);
}
3.6 在特定位置添加子控件:AddAt()
bool CContainerUI::AddAt(CControlUI* pControl, int iIndex)
{
if( pControl == NULL) return false;
if( m_pManager != NULL )
{
//设pControl的父控件为this
m_pManager->InitControls(pControl, this);
}
if( IsVisible() )
NeedUpdate();
else
pControl->SetInternVisible(false);
return m_items.InsertAt(iIndex, pControl);
}
3.7 删除某个子控件:Remove()
默认情况下,删除的子控件会被 delete 掉。
bool CContainerUI::Remove(CControlUI* pControl)
{
if( pControl == NULL) return false;
for( int it = 0; it < m_items.GetSize(); it++ )
{
if( static_cast<CControlUI*>(m_items[it]) == pControl )
{
//位置需要更新
NeedUpdate();
if( m_bAutoDestroy ) //是否销毁,默认true
{
if( m_bDelayedDestroy && m_pManager )
m_pManager->AddDelayedCleanup(pControl);
else
delete pControl;
}
return m_items.Remove(it);
}
}
return false;
}
3.8 根据序号删除某个子控件:RemoveAt()
bool CContainerUI::RemoveAt(int iIndex)
{
CControlUI* pControl = GetItemAt(iIndex);
if (pControl != NULL)
{
return CContainerUI::Remove(pControl); //
}
return false;
}
3.9 移除所有子控件:RemoveAll()
下面的实现方式比循环调用 RemoveAt() 更加高效。
void CContainerUI::RemoveAll()
{
for( int it = 0; m_bAutoDestroy && it < m_items.GetSize(); it++ )
{
if( m_bDelayedDestroy && m_pManager )
m_pManager->AddDelayedCleanup(static_cast<CControlUI*>(m_items[it]));
else
delete static_cast<CControlUI*>(m_items[it]);
}
m_items.Empty();
NeedUpdate();
}
4. CVerticalLayoutUI 中 SetPos() 实现
void CVerticalLayoutUI::SetPos(RECT rc)
{
CControlUI::SetPos(rc);
rc = m_rcItem;
// 为了内间距和垂直水平滑动条留出空间
// 此时 rc 为用于显示子控件的空间位置及大小
rc.left += m_rcInset.left;
rc.top += m_rcInset.top;
rc.right -= m_rcInset.right;
rc.bottom -= m_rcInset.bottom;
if( m_pVerticalScrollBar && m_pVerticalScrollBar->IsVisible() )
rc.right -= m_pVerticalScrollBar->GetFixedWidth();
if( m_pHorizontalScrollBar && m_pHorizontalScrollBar->IsVisible() )
rc.bottom -= m_pHorizontalScrollBar->GetFixedHeight();
// 没有子控件就没必须继续了
if( m_items.GetSize() == 0)
{
ProcessScrollBar(rc, 0, 0);
return;
}
// 垂直布局的情况下,如果有水平滚动条,水平显示空间会相应增加
SIZE szAvailable = { rc.right - rc.left, rc.bottom - rc.top };
if( m_pHorizontalScrollBar && m_pHorizontalScrollBar->IsVisible() )
szAvailable.cx += m_pHorizontalScrollBar->GetScrollRange();
// 循环遍历子控件,计算所需总空间高度及可调整和显示元素个数
int nAdjustables = 0; // 可调高度的子控件个数
int cyFixed = 0; // 总体需要的空间高度(未包含可调高度的子控件高度)
int nEstimateNum = 0; // 显示且非float子控件个数
for( int it1 = 0; it1 < m_items.GetSize(); it1++ )
{
CControlUI* pControl = static_cast<CControlUI*>(m_items[it1]);
if( !pControl->IsVisible() ) continue;
if( pControl->IsFloat() ) continue;
SIZE sz = pControl->EstimateSize(szAvailable);
if( sz.cy == 0 )
{
nAdjustables++;
}
else
{
if( sz.cy < pControl->GetMinHeight() )
sz.cy = pControl->GetMinHeight();
if( sz.cy > pControl->GetMaxHeight() )
sz.cy = pControl->GetMaxHeight();
}
cyFixed += sz.cy + pControl->GetPadding().top + pControl->GetPadding().bottom;
nEstimateNum++;
}
cyFixed += (nEstimateNum - 1) * m_iChildPadding;
// 计算可调高度的子控件的设置高度
// 子控件的高度并不一定设置为此(当子控件设置了最小高度属性时)
int cyExpand = 0;
if( nAdjustables > 0 )
cyExpand = MAX(0, (szAvailable.cy - cyFixed) / nAdjustables);
// 设置画布的初始位置
int iPosY = rc.top;
if( m_pVerticalScrollBar && m_pVerticalScrollBar->IsVisible() )
{
iPosY -= m_pVerticalScrollBar->GetScrollPos();
}
int iPosX = rc.left;
if( m_pHorizontalScrollBar && m_pHorizontalScrollBar->IsVisible() )
{
iPosX -= m_pHorizontalScrollBar->GetScrollPos();
}
// 循环为各个子控件设置位置及大小
int cyNeeded = 0; //
SIZE szRemaining = szAvailable; // 剩余空间大小
int iAdjustable = 0; // 第几个可调高度子控件
int cyFixedRemaining = cyFixed; // 仍需要的高度空间
for( int it2 = 0; it2 < m_items.GetSize(); it2++ )
{
CControlUI* pControl = static_cast<CControlUI*>(m_items[it2]);
if( !pControl->IsVisible() ) continue;
if( pControl->IsFloat() )
{
SetFloatPos(it2);
continue;
}
RECT rcPadding = pControl->GetPadding();
szRemaining.cy -= rcPadding.top;
SIZE sz = pControl->EstimateSize(szRemaining);
// 设置子控件高度
if( sz.cy == 0 )
{
// 设置可调高度子控件的高度
iAdjustable++;
sz.cy = cyExpand;
if( iAdjustable == nAdjustables )
{
sz.cy = MAX(0, szRemaining.cy - rcPadding.bottom - cyFixedRemaining);
}
if( sz.cy < pControl->GetMinHeight() )
sz.cy = pControl->GetMinHeight();
if( sz.cy > pControl->GetMaxHeight() )
sz.cy = pControl->GetMaxHeight();
// 由于在预估高度的时候未计算该高度,故不减去
// cyFixedRemaining -= sz.cy;
}
else
{
// 设置不可调子控件的高度
if( sz.cy < pControl->GetMinHeight() )
sz.cy = pControl->GetMinHeight();
if( sz.cy > pControl->GetMaxHeight() )
sz.cy = pControl->GetMaxHeight();
// 由于在预估高度的时候计算了该高度,在此减去
cyFixedRemaining -= sz.cy + rcPadding.top + rcPadding.bottom;
}
// 设置子控件宽度
sz.cx = pControl->GetFixedWidth();
if( sz.cx == 0 )
sz.cx = szAvailable.cx - rcPadding.left - rcPadding.right;
if( sz.cx < 0 )
sz.cx = 0;
if( sz.cx < pControl->GetMinWidth() )
sz.cx = pControl->GetMinWidth();
if( sz.cx > pControl->GetMaxWidth() )
sz.cx = pControl->GetMaxWidth();
// 设置子控件的位置及大小
RECT rcCtrl = { iPosX + rcPadding.left,
iPosY + rcPadding.top,
iPosX + rcPadding.left + sz.cx,
iPosY + sz.cy + rcPadding.top + rcPadding.bottom };
pControl->SetPos(rcCtrl);
// 更新画布的 Y 坐标
iPosY += sz.cy + m_iChildPadding + rcPadding.top + rcPadding.bottom;
// 累计所需要的 Y 的空间高度
cyNeeded += sz.cy + rcPadding.top + rcPadding.bottom;
// 控件剩余可视控件大小
szRemaining.cy -= sz.cy + m_iChildPadding + rcPadding.bottom;
}
cyNeeded += (nEstimateNum - 1) * m_iChildPadding;
//刷新滚动条
ProcessScrollBar(rc, 0, cyNeeded);
}
大致步骤如下:
1. 计算可用于显示子控件的空间位置及大小;
2. 循环遍历子控件,计算出所需整体空间高度、可调高度子控件个数;
3. 根据滚动条的位置,初始化画布的位置坐标;
4. 循环为各个子控件设置位置及大小:
1. 如果为可调高度子控件:设置为 a.扩展后的高度 b.最小高度与最大高度之间
2. 如果为不可调高度子控件:直接根据子控件的预设高度进行设置
5. 刷新滚动条的滚动范围。
5.介绍
当然水平布局跟垂直布局也很相似,不解释了。