一些知识的补充:
/*
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>
最后实现的效果:
