464 lines
10 KiB
C++
464 lines
10 KiB
C++
// Copyright 2017, NetEase (Hangzhou) Network Co., Ltd. All rights reserved.
|
||
//
|
||
// zqw
|
||
// 2015/7/22
|
||
//
|
||
// VirtualListBox class
|
||
#include "stdafx.h"
|
||
#include "VirtualListBox.h"
|
||
|
||
namespace ui
|
||
{
|
||
|
||
CSize VirtualLayout::ArrangeChild(const std::vector<Control*>& items, UiRect rc)
|
||
{
|
||
CSize sz(rc.GetWidth(), 0);
|
||
|
||
VirtualListBox *pList = dynamic_cast<VirtualListBox*>(m_pOwner);
|
||
ASSERT(pList);
|
||
|
||
if (pList->UseDefaultLayout()) {
|
||
sz = VLayout::ArrangeChild(items, rc);
|
||
} else {
|
||
sz.cy = pList->CalcElementsHeight(-1);
|
||
pList->ReArrangeChild(false);
|
||
}
|
||
|
||
return sz;
|
||
}
|
||
|
||
VirtualListBox::VirtualListBox(ui::Layout* pLayout) :
|
||
ui::ListBox(pLayout),
|
||
m_pDataProvider(nullptr),
|
||
m_nElementHeight(0),
|
||
m_nMaxItemCount(0),
|
||
m_nOldYScrollPos(0),
|
||
m_bArrangedOnce(false),
|
||
m_bForceArrange(false)
|
||
{
|
||
|
||
}
|
||
|
||
void VirtualListBox::SetDataProvider(VirtualListInterface *pProvider)
|
||
{
|
||
ASSERT(pProvider);
|
||
m_pDataProvider = pProvider;
|
||
}
|
||
|
||
void VirtualListBox::SetElementHeight(int nHeight)
|
||
{
|
||
m_nElementHeight = nHeight;
|
||
}
|
||
|
||
void VirtualListBox::InitElement(int nMaxItemCount)
|
||
{
|
||
ASSERT(m_pDataProvider);
|
||
ASSERT(m_nElementHeight);
|
||
m_nMaxItemCount = nMaxItemCount;
|
||
|
||
int nCount = GetElementCount();
|
||
if (nCount > nMaxItemCount)
|
||
nCount = nMaxItemCount;
|
||
|
||
for (int i = 0; i < nCount; i++) {
|
||
Control *pControl = CreateElement();
|
||
this->Add(pControl);
|
||
FillElement(pControl, i);
|
||
}
|
||
}
|
||
|
||
void VirtualListBox::Refresh()
|
||
{
|
||
int nElementCount = GetElementCount();
|
||
int nItemCount = GetCount();
|
||
|
||
if (nItemCount > nElementCount) {
|
||
int n = nItemCount - nElementCount;
|
||
for (int i = 0; i < n; i++)
|
||
this->RemoveAt(0);
|
||
}
|
||
else if (nItemCount < nElementCount) {
|
||
int n = 0;
|
||
if (nElementCount <= m_nMaxItemCount) {
|
||
n = nElementCount - nItemCount;
|
||
}
|
||
else {
|
||
n = m_nMaxItemCount - nItemCount;
|
||
}
|
||
|
||
for (int i = 0; i < n; i++) {
|
||
Control *pControl = CreateElement();
|
||
this->Add(pControl);
|
||
}
|
||
}
|
||
|
||
if (UseDefaultLayout()) {
|
||
// 刚从虚拟列表转换到普通模式时,存在布局错误的情况(虚拟列表滚动条接近底部,
|
||
// 然后数据减少,变成普通模式)
|
||
if (nItemCount == m_nMaxItemCount) {
|
||
this->GetLayout()->ArrangeChild(m_items, m_rcItem);
|
||
}
|
||
|
||
for (int i = 0; i < (int)m_items.size(); i++)
|
||
FillElement(m_items[i], i);
|
||
}
|
||
else {
|
||
ASSERT(nElementCount);
|
||
if (nElementCount <= 0)
|
||
return;
|
||
|
||
ReArrangeChild(true);
|
||
Arrange();
|
||
}
|
||
}
|
||
|
||
void VirtualListBox::RemoveAll()
|
||
{
|
||
__super::RemoveAll();
|
||
|
||
if (m_pVerticalScrollBar)
|
||
m_pVerticalScrollBar->SetScrollPos(0);
|
||
|
||
m_nOldYScrollPos = 0;
|
||
m_bArrangedOnce = false;
|
||
m_bForceArrange = false;
|
||
}
|
||
|
||
void VirtualListBox::SetForceArrange(bool bForce)
|
||
{
|
||
m_bForceArrange = bForce;
|
||
}
|
||
|
||
void VirtualListBox::GetDisplayCollection(std::vector<int>& collection)
|
||
{
|
||
collection.clear();
|
||
|
||
if (GetCount() == 0)
|
||
return;
|
||
|
||
UiRect rcThis = this->GetPos(false);
|
||
|
||
int min = GetScrollPos().cy / m_nElementHeight;
|
||
int max = min + (rcThis.GetHeight() / m_nElementHeight);
|
||
int nCount = GetElementCount();
|
||
if (max >= nCount)
|
||
max = nCount - 1;
|
||
|
||
for (auto i = min; i <= max; i++)
|
||
collection.push_back(i);
|
||
}
|
||
|
||
void VirtualListBox::EnsureVisible(int iIndex, bool bToTop)
|
||
{
|
||
if (iIndex < 0 || iIndex >= GetElementCount())
|
||
return;
|
||
|
||
int nPos = m_pVerticalScrollBar->GetScrollPos();
|
||
int nTopIndex = nPos / m_nElementHeight;
|
||
int nNewPos = 0;
|
||
|
||
if (bToTop) {
|
||
nNewPos = CalcElementsHeight(iIndex);
|
||
if (nNewPos >= m_pVerticalScrollBar->GetScrollRange())
|
||
return;
|
||
}
|
||
else {
|
||
if (IsElementDisplay(iIndex))
|
||
return;
|
||
|
||
if (iIndex > nTopIndex) {
|
||
// 向下
|
||
int height = CalcElementsHeight(iIndex + 1);
|
||
nNewPos = height - m_rcItem.GetHeight();
|
||
}
|
||
else {
|
||
// 向上
|
||
nNewPos = CalcElementsHeight(iIndex);
|
||
}
|
||
}
|
||
CSize sz(0, nNewPos);
|
||
SetScrollPos(sz);
|
||
}
|
||
|
||
void VirtualListBox::ReArrangeChild(bool bForce)
|
||
{
|
||
ScrollDirection direction = kScrollUp;
|
||
if (!bForce && !m_bForceArrange) {
|
||
if (!NeedReArrange(direction))
|
||
return;
|
||
}
|
||
//max( pos ) = range
|
||
//int range = m_pVerticalScrollBar->GetScrollRange();
|
||
//int pos = m_pVerticalScrollBar->GetScrollPos();
|
||
|
||
int nElementCount = GetElementCount();
|
||
|
||
int nTopIndexBottom = 0;
|
||
int nTopIndex = GetTopElementIndex(nTopIndexBottom);
|
||
|
||
if (direction == kScrollDown) {
|
||
// 向下滚动
|
||
ui::UiRect rcItem = m_rcItem;
|
||
rcItem.bottom = rcItem.top + nTopIndexBottom;
|
||
|
||
for (int i = 0; i < (int)m_items.size(); i++) {
|
||
rcItem.top = rcItem.bottom;
|
||
rcItem.bottom = rcItem.top + m_nElementHeight;
|
||
|
||
m_items[i]->SetPos(rcItem);
|
||
|
||
int nElementIndex = nTopIndex + i;
|
||
if (nElementIndex < nElementCount)
|
||
FillElement(m_items[i], nElementIndex);
|
||
}
|
||
}
|
||
else {
|
||
// 向上滚动
|
||
int nDisplayCount = m_rcItem.GetHeight() / m_nElementHeight + 1;
|
||
int nHideCount = (int)m_items.size() - nDisplayCount;
|
||
|
||
// 上半部分
|
||
UiRect rcItem = m_rcItem;
|
||
rcItem.top = m_rcItem.top + nTopIndexBottom;
|
||
for (int i = nHideCount - 1; i >= 0; i--) {
|
||
rcItem.bottom = rcItem.top;
|
||
rcItem.top = rcItem.bottom - m_nElementHeight;
|
||
|
||
m_items[i]->SetPos(rcItem);
|
||
|
||
int nElementIndex = nTopIndex - (nHideCount - i);
|
||
if (nElementIndex >= 0)
|
||
FillElement(m_items[i], nElementIndex);
|
||
}
|
||
|
||
// 下半部分
|
||
rcItem = m_rcItem;
|
||
rcItem.bottom = m_rcItem.top + nTopIndexBottom;
|
||
for (int i = nHideCount; i < (int)m_items.size(); i++) {
|
||
rcItem.top = rcItem.bottom;
|
||
rcItem.bottom = rcItem.top + m_nElementHeight;
|
||
|
||
m_items[i]->SetPos(rcItem);
|
||
|
||
int nElementIndex = nTopIndex + (i - nHideCount);
|
||
if (nElementIndex < nElementCount)
|
||
FillElement(m_items[i], nElementIndex);
|
||
}
|
||
}
|
||
}
|
||
|
||
void VirtualListBox::AddElement(int iIndex)
|
||
{
|
||
int nCount = GetElementCount();
|
||
if (nCount <= m_nMaxItemCount) {
|
||
Control *pControl = CreateElement();
|
||
this->AddAt(pControl, iIndex);
|
||
FillElement(pControl, iIndex);
|
||
} else {
|
||
ReArrangeChild(true);
|
||
Arrange();
|
||
}
|
||
}
|
||
|
||
void VirtualListBox::RemoveElement(int iIndex)
|
||
{
|
||
int nCount = GetElementCount();
|
||
if (nCount < m_nMaxItemCount) {
|
||
this->RemoveAt(iIndex);
|
||
} else {
|
||
ReArrangeChild(true);
|
||
Arrange();
|
||
}
|
||
}
|
||
|
||
void VirtualListBox::SetScrollPos(ui::CSize szPos)
|
||
{
|
||
m_nOldYScrollPos = m_pVerticalScrollBar->GetScrollPos();
|
||
ListBox::SetScrollPos(szPos);
|
||
|
||
if (UseDefaultLayout())
|
||
return;
|
||
|
||
ReArrangeChild(false);
|
||
}
|
||
|
||
void VirtualListBox::HandleMessage(ui::EventArgs& event) {
|
||
if (!IsMouseEnabled() && event.Type > ui::kEventMouseBegin && event.Type < ui::kEventMouseEnd) {
|
||
if (m_pParent != NULL)
|
||
m_pParent->HandleMessageTemplate(event);
|
||
else
|
||
ui::ScrollableBox::HandleMessage(event);
|
||
return;
|
||
}
|
||
|
||
switch (event.Type) {
|
||
case kEventKeyDown: {
|
||
switch (event.chKey) {
|
||
case VK_UP: {
|
||
OnKeyDown(VK_UP);
|
||
return;
|
||
}
|
||
case VK_DOWN: {
|
||
OnKeyDown(VK_DOWN);
|
||
return;
|
||
}
|
||
case VK_HOME:
|
||
SetScrollPosY(0);
|
||
return;
|
||
case VK_END: {
|
||
int range = m_pVerticalScrollBar->GetScrollRange();
|
||
SetScrollPosY(range);
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
case kEventKeyUp: {
|
||
switch (event.chKey) {
|
||
case VK_UP: {
|
||
OnKeyUp(VK_UP);
|
||
return;
|
||
}
|
||
case VK_DOWN: {
|
||
OnKeyUp(VK_DOWN);
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
__super::HandleMessage(event);
|
||
}
|
||
|
||
void VirtualListBox::SetPos(UiRect rc)
|
||
{
|
||
bool bChange = false;
|
||
if (!m_rcItem.Equal(rc))
|
||
bChange = true;
|
||
|
||
ListBox::SetPos(rc);
|
||
|
||
if (bChange) {
|
||
if (UseDefaultLayout())
|
||
return;
|
||
|
||
ReArrangeChild(true);
|
||
}
|
||
}
|
||
|
||
ui::Control* VirtualListBox::CreateElement()
|
||
{
|
||
if (m_pDataProvider)
|
||
return m_pDataProvider->CreateElement();
|
||
|
||
return nullptr;
|
||
}
|
||
|
||
void VirtualListBox::FillElement(Control *pControl, int iIndex)
|
||
{
|
||
if (m_pDataProvider)
|
||
m_pDataProvider->FillElement(pControl, iIndex);
|
||
}
|
||
|
||
int VirtualListBox::GetElementCount()
|
||
{
|
||
if (m_pDataProvider)
|
||
return m_pDataProvider->GetElementtCount();
|
||
|
||
return 0;
|
||
}
|
||
|
||
bool VirtualListBox::UseDefaultLayout()
|
||
{
|
||
return GetElementCount() <= GetCount();
|
||
}
|
||
|
||
int VirtualListBox::CalcElementsHeight(int nCount)
|
||
{
|
||
if (nCount < 0)
|
||
nCount = GetElementCount();
|
||
|
||
return nCount * m_nElementHeight;
|
||
}
|
||
|
||
int VirtualListBox::GetTopElementIndex(int &bottom)
|
||
{
|
||
int nPos = m_pVerticalScrollBar->GetScrollPos();
|
||
|
||
int iIndex = nPos / m_nElementHeight;
|
||
bottom = iIndex * m_nElementHeight;
|
||
|
||
return iIndex;
|
||
}
|
||
|
||
bool VirtualListBox::IsElementDisplay(int iIndex)
|
||
{
|
||
if (iIndex < 0)
|
||
return false;
|
||
|
||
int nPos = m_pVerticalScrollBar->GetScrollPos();
|
||
int nElementPos = CalcElementsHeight(iIndex);
|
||
if (nElementPos >= nPos) {
|
||
int nHeight = this->GetHeight();
|
||
if (nElementPos + m_nElementHeight <= nPos + nHeight)
|
||
return true;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
bool VirtualListBox::NeedReArrange(ScrollDirection &direction)
|
||
{
|
||
direction = kScrollUp;
|
||
if (!m_bArrangedOnce) {
|
||
m_bArrangedOnce = true;
|
||
return true;
|
||
}
|
||
|
||
int nCount = GetCount();
|
||
if (nCount <= 0)
|
||
return false;
|
||
|
||
if (GetElementCount() <= nCount)
|
||
return false;
|
||
|
||
UiRect rcThis = this->GetPos();
|
||
if (rcThis.GetWidth() <= 0)
|
||
return false;
|
||
|
||
int nPos = m_pVerticalScrollBar->GetScrollPos();
|
||
UiRect rcItem;
|
||
|
||
// 补救措施
|
||
// 情况一:通讯录列表,一开始不可见,切换后可见,如果提前布局,
|
||
// 则Element宽度为0,因此,必须重新布局;
|
||
// 情况二:写信窗口联系人列表,列表宽度会不断变化,因此,需要在宽度变化后
|
||
// 重新布局,否则,导致最终Element布局时的宽度不正确
|
||
rcItem = m_items[0]->GetPos();
|
||
// modified by zqw, 2016/10/12
|
||
// 针对情况二,解决方法是,列表宽度变化 或者 拖动写信窗口右侧,列表position改变,
|
||
// 此时,在SetPos中强制重新布局
|
||
//if (u.GetWidth() != rect.GetWidth()) {
|
||
// return true;
|
||
//}
|
||
|
||
if (nPos >= m_nOldYScrollPos) {
|
||
// 下
|
||
rcItem = m_items[nCount - 1]->GetPos();
|
||
int nSub = (rcItem.bottom - rcThis.top) - (nPos + rcThis.GetHeight());
|
||
if (nSub < 0) {
|
||
direction = kScrollDown;
|
||
return true;
|
||
}
|
||
} else {
|
||
// 上
|
||
rcItem = m_items[0]->GetPos();
|
||
if (nPos < (rcItem.top - rcThis.top)) {
|
||
direction = kScrollUp;
|
||
return true;
|
||
}
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
} |