虚拟 TileLayout

本文介绍了一种基于虚拟数据加载的TileLayout实现方案,通过自定义数据接口IVirtualDataProvider动态加载数据,实现高效的大数据量展示。文章详细阐述了虚拟TileLayout的设计思路与核心代码,并提供了效果展示与Demo下载。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

当某些结果需要像文件图标显示一样的时候, 结果超多上万条甚至更多的时候,虚拟的容器非常重要!

一、虚拟 TileLayout 思路

1, 数据是动态加载的,需要一个提供数据的虚基类 IVirtualDataProvider, 用于用户自定义数据接口类型添加到容器中;

2,滚动条是由容器中的 Item 数量决定的  Range , 因此需要重写 ProcessScrollBar 函数 和 SetPos ;

3,数据动态获取是根据滚动条的改变而动态获取的,因此需要重写SetScrollPos 函数, 在这个函数中去从IVirtualDataProvider * 加载数据到容器中;

4,SetPos 中需要重写 设置Item 位置的部分代码;

5,通过滚动条的位置去确定当前需要显示的数据在哪一行,应该取IVirtualDataProvider * 中的哪一部分数据。int GetTopElementIndex(int &bottom); 接口用来确定当前显示区域第一行数据的索引;

6,增加 Item 类, 用于把它做成 List 一样,CVirTileItemUI 类 相当于 CListItemUI,增加SetIndex, GetIndex接口, 用来记录显示的数据索引;

7,需要一个缓存TileLayout 每一行控件最大高度的 map ,用来计算 Range ,需要一个 默认的子项高度m_nOwnerElementHeight, 如果没有缓存的行, 计算滚动条位置所在的行时使用该子项高度m_nOwnerElementHeight计算;

 

二、代码



/**
* @brief 虚拟列表接口类
* 提供开发者对子项数据管理个性化定制
*/
class IVirtualDataProvider
{
public:
	/**
	* @brief 创建一个子项
	* @return 返回创建后的子项指针
	*/
	virtual CControlUI* CreateElement() abstract;

	/**
	* @brief 填充指定子项
	* @param[in] control 子项控件指针
	* @param[in] index 索引
	* @return 返回创建后的子项指针
	*/
	virtual void FillElement(CControlUI *pControl, int index) abstract;

	/**
	* @brief 获取子项总数
	* @return 返回子项总数
	*/
	virtual int GetElementtCount() abstract;

// 	/**
// 	* @brief 删除指定项
// 	* @param[in] index 子项索引
// 	* @return 无
// 	*/
// 	virtual void RemoveAt(int index) abstract;
};

class CVirTileItemUI : public CContainerUI
{
public:
	CVirTileItemUI();
	~CVirTileItemUI();

	void SetIndex(const int & iIndex);
	int GetIndex();

private:
	int m_iIndex;

};

class CVirTileLayoutUI : public CTileLayoutUI
{
public:
	CVirTileLayoutUI();
	~CVirTileLayoutUI();

	/**
	* @brief 设置数据代理对象
	* @param[in] pProvider 开发者需要重写 IVirtualDataProvider 的接口来作为数据代理对象
	* @return 无
	*/
	virtual void SetDataProvider(IVirtualDataProvider * pDataProvider);

	virtual IVirtualDataProvider * GetDataProvider();

	/**
	* @brief 设置子项高度
	* @param[in] nHeight 高度值
	* @return 无
	*/
	virtual void SetElementHeight(int nHeight);

	/**
	* @brief 初始化子项
	* @param[in] nMaxItemCount 初始化数量,默认 50
	* @return 无
	*/
	virtual void InitElement(int nMaxItemCount = 50);

	/**
	* @brief 刷新列表
	* @return 无
	*/
	virtual void Refresh();

	/**
	* @brief 删除所有子项
	* @return 无
	*/
	virtual void RemoveAll() override;

	/**
	* @brief 设置是否强制重新布局
	* @param[in] bForce 设置为 true 为强制,否则为不强制
	* @return 无
	*/
	void SetForceArrange(bool bForce);

	/**
	* @brief 获取当前所有可见控件的索引
	* @param[out] collection 索引列表
	* @return 无
	*/
	void GetDisplayCollection(std::vector<int>& collection);

	//重写 SetPos 旨在重写 ProcessScrollBar 重设滚动条 Range
	virtual void SetPos(RECT rc, bool bNeedInvalidate = true);

	/// 重写父类接口,提供个性化功能
	virtual void SetScrollPos(SIZE szPos, bool bMsg = true);

	/**
	* @brief 重新布局子项
	* @param[in] bForce 是否强制重新布局
	* @return 无
	*/
	void ReArrangeChild(bool bForce);

	void SetColumns(int nCols);

// 	/************************************************************************
// 	*函数名称:ReSizeColumn
// 	*参      数:RECT rcSize
// 	*返 回  值:
// 	*函数功能:当容器大小改变的时候 重新计算列的大小
// 	************************************************************************/
// 	void ReSizeColumn(RECT rcSize);

protected:

	enum ScrollDirection
	{
		kScrollUp = -1,
		kScrollDown = 1
	};


	//range 会跟随大小改变而改变  //默认按照最大的列表高度设置 range
	virtual void ProcessScrollBar(RECT rc, int cxRequired, int cyRequired);

	//设置实际需要的滚动条位置
	void SetPosInternally(RECT rc);


	/**
	* @brief 创建一个子项
	* @return 返回创建后的子项指针
	*/
	CControlUI* CreateElement();

	/**
	* @brief 填充指定子项
	* @param[in] control 子项控件指针
	* @param[in] index 索引
	* @return 返回创建后的子项指针
	*/
	void FillElement(CControlUI *pControl, int iIndex);

	/**
	* @brief 获取元素总数
	* @return 返回元素总指数
	*/
	int GetElementCount();

	/**
	* @brief 使用默认布局
	* @return 成功返回 true,否则返回 false
	*/
	bool UseDefaultLayout();

	/**
	* @brief 得到n个元素对应的高度和,
	* @param[in] nCount 要得到多少元素的高度,-1表示全部元素
	* @return 返回指定数量元素的高度和
	*/
	int CalcElementsHeight(int nCount);

	/**
	* @brief 得到可见范围内第一个元素的前一个元素索引
	* @param[out] bottom 返回上一个元素的 bottom 值
	* @return 返回上一个元素的索引
	*/
	int GetTopElementIndex(int &bottom);

	/**
	* @brief 判断某个元素是否在可见范围内
	* @param[in] iIndex 元素索引
	* @return 返回 true 表示可见,否则为不可见
	*/
	bool IsElementDisplay(int iIndex);

	/**
	* @brief 判断是否要重新布局
	* @param[out] direction 向上滚动还是向下滚动
	* @return true 为需要重新布局,否则为 false
	*/
	bool NeedReArrange(ScrollDirection &direction);

	/**
	* @brief 设置虚拟 Item 位置
	* @param[In] CVirTileItemUI* pItem 需要设置位置的Item
	* @void
	*/
	void SetVirItemPos(CVirTileItemUI* pItem);

	/**
	* @brief 计算滚动条需要的 范围
	* @int  
	*/
	int CalScrollNeedRange();

	/**
	* @brief 设置虚拟 Item 位置
	* @param[Out] int& nItemWidth Item宽度
	* @POINT 返回起始位置点
	*/
	POINT GetItemStartPos(int& nItemWidth);

protected:
	std::map<int, int > m_mapRowHeight;//保存高度  m_nColumns 改变需要清空

	int m_iTopIndex = 0;

	IVirtualDataProvider*  m_pDataProvider;

	int m_nOwnerElementHeight;	// 每个项的高度	
	int m_nOwnerItemCount;	// 列表真实控件数量上限  
	int m_nMaxRow;

	int m_nOldYScrollPos;
	bool m_bArrangedOnce;
	bool m_bForceArrange;	// 强制布局标记

	bool m_bScrollProcess = false;//防止SetPos 重复调用, 导致死循环

	int m_iSelIndex = -1;
	std::vector<int> m_vSelIndex;//选中索引
};

三、效果图如下:

 

四、Demo

由于一个人改写, 只花了大概一天多一点的时间, 可能存在不少BUG , Demo 下面下载链接:

https://download.youkuaiyun.com/download/qwerdf10010/11178760

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值