SOUI中ListCtrl的自绘

一些知识的补充:

/*
2018-12-14 14:32:42
单项选择设置背景色 ListCtrl
*/
通过重写ListCtrl加入部分属性

详细代码参照ListCtrlex
核心思想:
通过记录设置的几行使用什么颜色(没有设置颜色则使用缺省参数)
通过数据结构map来组织
设置一次颜色相当于在map中插入一条数据
擦除颜色相当于在map中删除一条数据
最后整理好的数据结构在 OnPaint中进行处理

Tip:删除完数据后 记得需要Invalid 刷新一下控件


扩展 LLPoint结构 可以用CPoint数组来代替
//列表中的行的线
CPoint pts[2];
pts[0] = { rcItem.left,  rcItem.top };
pts[1] = { rcItem.right, rcItem.top };
pRT->DrawLines(pts, 2);

/*
2018-12-17 14:47:26
记录在ListCtrl中提供的一个特殊的坐标值
*/
CPoint          m_ptOrigin;
一个原点坐标
在列表没有滚动条的时候 m_ptOrigin = {0,0}
当滚动条进行滑动的时候 这个时候原点坐标也会改变
详细计算过程见 SListCtrl::UpdateScrollBar()

在重新实现了左键按下的消息事件的时候 m_nSelectItem 这个值将为-1
也就是选中哪一行 这个需要自己实现。实现过程和选中哪一列类似

需求:设置多个行选项的背景色,加上框线 列表单元格可编辑

设计思想:

背景色思路:
确定背景的绘制在OnPaint中 通过确认几行需要绘制 通过一些数据结构的处理 很容易在OnPaint中进行实现

编辑功能思路:
通过控件SListCtrl中可以很轻松的得到 宽 高 行 列 这些参数 然后可以知道点击的每个单元格的Rect
在单元格需要编辑的地方双击 通过new SEdit 来进行编辑框的输入 最后将输入的值写入ListCtrl控件
当Edit失去焦点的时候就是写入值的时候 并销毁SEdit

代码设计如下

SListCtrlEx.h

#pragma once
/*
2018-12-14 14:30:30
拓展ListCtrl控件 增加背景色 和 编辑功能
*/
#include <map>
namespace SOUI
{
#define COLORRED		RGBA(255, 0, 0, 155)
	class ListCtrlExEdit;
	class SListCtrlEx : public SListCtrl
	{
		SOUI_CLASS_NAME(SListCtrlEx,L"listctrlex")

	public:
		SListCtrlEx();
		~SListCtrlEx();

	public:
		void SetRowBackGndColor(int iRow, COLORREF clr = COLORRED);	//设置某行的背景色
		void EraseRowBackGndColor(int iRow);	//去掉某行设置的颜色 iRow从0开始
		void OnSetEditText();

	protected:
		void OnLButtonDown(UINT nFlags, CPoint point);
		void OnLButtonDblClk(UINT nFlags, CPoint point);
		void OnPaint(IRenderTarget * pRT);
		void DrawItem(IRenderTarget *pRT, CRect rcItem, int nItem);
		

		SOUI_MSG_MAP_BEGIN()
			MSG_WM_PAINT_EX(OnPaint)
			MSG_WM_LBUTTONDBLCLK(OnLButtonDblClk)
			MSG_WM_LBUTTONDOWN(OnLButtonDown)
			SOUI_MSG_MAP_END()

	private:
		std::map<int, COLORREF> m_maplistexRow;
		ListCtrlExEdit *m_edit;
		int m_iRow;
		int m_iCol;

	};

	class ListCtrlExEdit : public SEdit
	{
	public:
		SOUI_CLASS_NAME(ListCtrlExEdit, L"listctrlexedit")

		ListCtrlExEdit(SListCtrlEx* pOwner) :m_ListCtrlEx(pOwner) {}

		void OnKillFocus(SWND wndFocus)
		{
			__super::OnKillFocus(wndFocus);
			m_ListCtrlEx->OnSetEditText();
		}

		SOUI_MSG_MAP_BEGIN()
			MSG_WM_KILLFOCUS_EX(OnKillFocus)
		SOUI_MSG_MAP_END()

		virtual void OnFinalRelease()
		{
			delete this;
		}
	private:
		SListCtrlEx * m_ListCtrlEx;
	};

}

SListCtrlEx.cpp

#include "stdafx.h"
#include "SListCtrlEx.h"

namespace SOUI
{
	SListCtrlEx::SListCtrlEx():m_iRow(0),m_iCol(0), m_edit(nullptr)
	{
		m_maplistexRow.swap(std::map<int, COLORREF>());
	}

	SListCtrlEx::~SListCtrlEx()
	{
		if (m_maplistexRow.size() > 0)
		{
			m_maplistexRow.swap(std::map<int, COLORREF>());
		}

		if (m_edit != nullptr)
		{
			m_edit->Release();
			m_edit = nullptr;
		}
	}

	void SListCtrlEx::SetRowBackGndColor(int iRow, COLORREF clr)
	{
		if (iRow > GetItemCount())
		{
			return;
		}
		bool bflag = false;
		for (auto value : m_maplistexRow)
		{
			if (value.first == iRow && value.second == clr)
			{
				bflag = true;
				break;
			}
		}

		if (!bflag)
		{
			m_maplistexRow.insert(std::pair<int, COLORREF>(iRow, clr));
		}
	}

	void SListCtrlEx::EraseRowBackGndColor(int iRow)
	{
		auto pValue = m_maplistexRow.find(iRow);
		if (pValue != m_maplistexRow.end())
		{
			m_maplistexRow.erase(pValue);
		}
		Invalidate();
	}

	void SListCtrlEx::OnLButtonDown(UINT nFlags, CPoint point)
	{
		if (m_edit != nullptr)
		{
			m_edit->KillFocus();
		}
	}

	void SListCtrlEx::OnLButtonDblClk(UINT nFlags, CPoint point)
	{
		//计算鼠标点击的位置 在 列表中的是哪一行哪一列   行高:m_nItemHeight
		m_iRow = 0;
		m_iCol = 0;
		CRect rclist = GetListRect();
		CPoint pt{ 0,0 };
		pt.x = point.x - rclist.left + m_ptOrigin.x;
		pt.y = point.y - rclist.top + m_ptOrigin.y;
		int WidthHead = 0;
		int WidthBack = 0;

		//判断第几行 在重写了左键按下事件后 就无法使用基类中给出的选中第几行的那个变量了
		for (int i = 0; i < GetItemCount(); ++i)
		{
			WidthBack = WidthHead + m_nItemHeight;
			if (pt.y > WidthHead && pt.y < WidthBack)
			{
				m_iRow = i;
				break;
			}
			WidthHead = WidthBack;
		}

		WidthHead = 0;
		WidthBack = 0;
		//获取第几列
		for (int i = 0; i < GetColumnCount(); ++i)
		{
			//获取列的宽度  然后计算坐标 与 得到的坐标进行比较 判断区域
			WidthBack = WidthHead + m_pHeader->GetItemWidth(i);
			if (pt.x > WidthHead && pt.x < WidthBack)
			{
				m_iCol = i;
				break;
			}
			WidthHead = WidthBack;
		}

		//SMessageBox(NULL, SStringT().Format(L"%d,%d", m_iRow,m_iCol), L"", NULL);
		//return;

		//得到了行列 就可以得到区域
		CRect ItemRect = GetItemRect(m_iRow, m_iCol);

		wchar_t szEditAttr[] = L"<listctrlexedit transparent=\"1\" align=\"left\" mouseRelay=\"1\" colorBkgnd=\"#FFFFFF\" colorText=\"#000000\" />";
		pugi::xml_document xmlDoc;
		xmlDoc.load_buffer(szEditAttr, sizeof(szEditAttr));
		if (m_edit == nullptr)
		{
			m_edit = new ListCtrlExEdit(this);
		}
		
		InsertChild(m_edit);
		m_edit->InitFromXml(xmlDoc.first_child());
		ItemRect.left += 4;
		m_edit->Move(ItemRect);	//将窗口移动到指定位置
		SStringT strText = GetSubItemText(m_iRow, m_iCol);
		m_edit->SetWindowTextW(strText);
		m_edit->SetFocus();
	}

	void SListCtrlEx::OnPaint(IRenderTarget * pRT)
	{
		SPainter painter;
		BeforePaint(pRT, painter);
		CRect rcList = GetListRect();
		int nTopItem = GetTopIndex();
		pRT->PushClipRect(&rcList);
		CRect rcItem(rcList);
		pRT->DrawRectangle(rcList);

		rcItem.bottom = rcItem.top;
		int ilistitem = GetItemCount();
		rcItem.OffsetRect(0, -(m_ptOrigin.y%m_nItemHeight));
		for (int nItem = nTopItem; nItem <= (nTopItem + GetCountPerPage(TRUE)) && nItem < GetItemCount(); nItem++)
		{
			rcItem.bottom = rcItem.top + m_nItemHeight;

			DrawItem(pRT, rcItem, nItem);

			//列表中的行的线
			CPoint pts[2];
			pts[0] = { rcItem.left,  rcItem.top };
			pts[1] = { rcItem.right, rcItem.top };
			pRT->DrawLines(pts, 2);

			for (auto value : m_maplistexRow)
			{
				if (nItem == value.first)
				{
					pRT->FillSolidRect(rcItem, value.second);	//设置变色的行
					break;
				}
			}

			rcItem.top = rcItem.bottom;
		}

		pRT->PopClip();
		AfterPaint(pRT, painter);
	}

	void SListCtrlEx::DrawItem(IRenderTarget * pRT, CRect rcItem, int nItem)
	{
		BOOL bTextColorChanged = FALSE;
		int nBgImg = 0;
		COLORREF crOldText = RGBA(0xFF, 0xFF, 0xFF, 0xFF);
		COLORREF crItemBg = m_crItemBg;
		COLORREF crText = m_crText;
		DXLVITEM lvItem = m_arrItems[nItem];
		CRect rcIcon, rcText;

		if (nItem % 2)
		{
			if (CR_INVALID != m_crItemBg2)
			{
				crItemBg = m_crItemBg2;
			}
		}

		if (lvItem.checked)
		{
			if (m_pItemSkin != NULL)
			{
				nBgImg = 2;
			}
			else if (CR_INVALID != m_crItemSelBg)
			{
				crItemBg = m_crItemSelBg;
			}

			if (CR_INVALID != m_crSelText)
			{
				crText = m_crSelText;
			}
		}
		else if (m_bHotTrack && nItem == m_nHoverItem)
		{
			if (m_pItemSkin != NULL)
			{
				nBgImg = 1;
			}
			else if (CR_INVALID != m_crItemHotBg)
			{
				crItemBg = m_crItemHotBg;
			}

			if (CR_INVALID != m_crSelText)
			{
				crText = m_crSelText;
			}
		}

		//绘制背景
		if (CR_INVALID != crItemBg)//先画背景
		{
			pRT->FillSolidRect(rcItem, crItemBg);
		}

		if (m_pItemSkin != NULL)//有skin,则覆盖背景
		{
			m_pItemSkin->Draw(pRT, rcItem, nBgImg);
		}


		//  左边加上空白
		rcItem.left += 4;

		if (CR_INVALID != crText)
		{
			bTextColorChanged = TRUE;
			crOldText = pRT->SetTextColor(crText);
		}

		CRect rcCol(rcItem);
		rcCol.right = rcCol.left;
		rcCol.OffsetRect(-m_ptOrigin.x, 0);

		for (int nCol = 0; nCol < GetColumnCount(); nCol++)
		{
			CRect rcVisiblePart;

			SHDITEM hdi;
			hdi.mask = SHDI_WIDTH | SHDI_ORDER;
			m_pHeader->GetItem(nCol, &hdi);
			rcCol.left = rcCol.right;
			rcCol.right = rcCol.left + hdi.cx.toPixelSize(GetScale());

			rcVisiblePart.IntersectRect(rcItem, rcCol);

			if (rcVisiblePart.IsRectEmpty())
			{
				continue;
			}

			// 绘制 checkbox
			if (nCol == 0 && m_bCheckBox && m_pCheckSkin)
			{
				CSize sizeSkin = m_pCheckSkin->GetSkinSize();
				int nOffsetX = 3;
				int nOffsetY = (m_nItemHeight - sizeSkin.cy) / 2;
				CRect rcCheck;
				rcCheck.SetRect(0, 0, sizeSkin.cx, sizeSkin.cy);
				rcCheck.OffsetRect(rcCol.left + nOffsetX, rcCol.top + nOffsetY);
				m_pCheckSkin->Draw(pRT, rcCheck, lvItem.checked ? 4 : 0);

				rcCol.left = sizeSkin.cx + 6 + rcCol.left;
			}

			DXLVSUBITEM& subItem = lvItem.arSubItems->GetAt(hdi.iOrder);

			if (subItem.nImage != -1 && m_pIconSkin)
			{
				int nOffsetX = m_ptIcon.x;
				int nOffsetY = m_ptIcon.y;
				CSize sizeSkin = m_pIconSkin->GetSkinSize();
				rcIcon.SetRect(0, 0, sizeSkin.cx, sizeSkin.cy);

				if (m_ptIcon.x == -1)
				{
					nOffsetX = m_nItemHeight / 6;
				}

				if (m_ptIcon.y == -1)
				{
					nOffsetY = (m_nItemHeight - sizeSkin.cy) / 2;
				}

				rcIcon.OffsetRect(rcCol.left + nOffsetX, rcCol.top + nOffsetY);
				m_pIconSkin->Draw(pRT, rcIcon, subItem.nImage);
			}

			UINT align = DT_SINGLELINE;
			rcText = rcCol;

			if (m_ptText.x == -1)
			{
				rcText.left = rcIcon.Width() > 0 ? rcIcon.right + m_nItemHeight / 6 : rcCol.left;
			}
			else
			{
				rcText.left = rcCol.left + m_ptText.x;
			}

			if (m_ptText.y == -1)
			{
				align |= DT_VCENTER;
			}
			else
			{
				rcText.top = rcCol.top + m_ptText.y;
			}

			pRT->DrawText(subItem.strText, subItem.cchTextMax, rcText, align);

			//划线 列的线段
			CPoint pt[2];
			pt[0] = { rcText.left,rcText.top };
			pt[1] = { rcText.left,rcText.bottom };
			pRT->DrawLines(pt, 2);
		}

		if (bTextColorChanged)
		{
			pRT->SetTextColor(crOldText);
		}
		CPoint pt[2];
		pt[0] = { rcText.left,rcText.top };
		pt[1] = { rcText.left,rcText.bottom };
		pRT->DrawLines(pt, 2);
	}

	void SListCtrlEx::OnSetEditText()
	{
		if (m_edit != nullptr)
		{
			SStringT strEdit = m_edit->GetWindowTextW();
			SetSubItemText(m_iRow, m_iCol, strEdit);
			m_edit->Release();
			RemoveChild(m_edit);
			m_edit = nullptr;
		}
	}
}

测试代码:

BOOL TestDlg::OnInitDialog(HWND wnd, LPARAM lInitParam)
{
	SListCtrlEx *plist = FindChildByName2<SListCtrlEx>(L"list_testlist");
	SASSERT(plist);

	int pos = 0;
	for (int i = 0; i < 10; ++i)
	{
		pos = plist->InsertItem(i, L"");
		plist->SetSubItemText(pos, 0, L"Test");
		plist->SetSubItemText(pos, 1, L"Test");
		plist->SetSubItemText(pos, 2, L"Test");
		plist->SetSubItemText(pos, 3, L"Test");
		plist->SetSubItemText(pos, 4, L"Test");
		plist->SetSubItemText(pos, 5, L"Test");
		plist->SetSubItemText(pos, 6, L"Test");
	}

	plist->SetRowBackGndColor(1);

	plist->SetRowBackGndColor(3);

	plist->SetRowBackGndColor(5);

	plist->SetRowBackGndColor(7);

	plist->SetRowBackGndColor(9);

	return 0;
}

XML :

<listctrlex pos="16,40" size="562, 200" hotTrack="1" itemHeight="30" headerHeight="30" name="list_testlist" margin="1,1" colorBorder="#000000">
				<!-- colorBkgnd="#F8E4EA" -->
				<header align="left" itemSwapEnable="1" fixWidth="1">
					<items>
						<item width="150">name</item>
						<item width="150">age</item>
						<item width="150">score</item>
						<item width="150">age</item>
						<item width="150">score</item>
						<item width="150">age</item>
						<item width="150">score</item>
						<item width="100" />
					</items>
				</header>
			</listctrlex>

最后实现的效果:

列表控件实现效果
实现效果

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值