常规的GDI自绘控件仅支持BMP图片,不支持png图片。png图片体积小,支持透明色,可以做圆角界面。自绘控件想要支持PNG图片,需要在MFC工程引入GDI+。需要注意的是MFC工程默认是不支持GDI+,需要手工引入相关的库与头文件,具体方法见我的博客相关的文章。
自绘列表框的关键是重载OnPaint(),绘制背景框图,重载DrawItem()函数,绘画条目文本,切换条目的选中状态。
第二个关键点是重载PreSubclassWindow()函数,在其中定义按键的风格,其中必须定义的风格是LBS_OWNERDRAWVARIABLE、LBS_NOINTEGRALHEIGHT、WS_EX_TRANSPARENT,需要去除WS_BORDER风格,其它的风格酌情选用。
第三个关键点是重载MeasureItem()、CompareItem()函数,定义条目的高度与排序规则。
如果想实现控件叠加的效果,必须拦截WM_ERASEBKGND消息,因为CButton默认擦除背景的操作是复制对话框的背景色,而不是下面的控件的皮肤。
头文件:
#pragma once
#include <afxwin.h>
class CPngListBox : public CListBox
{
public:
CPngListBox();
~CPngListBox();
BOOL Init(UINT resID, int itemHeigh, int edgeSpace);
void SetColor(COLORREF normalColor, COLORREF selectColor, COLORREF edgeColor);
private:
COLORREF m_NormalColor;
COLORREF m_selectColor;
COLORREF m_EdgeColor;
int m_EdgeSpace;
int m_ItemHeigh;
Image* m_imgNormal;
Image* ImageFromRes(UINT uImgID, LPCTSTR lpType);
void ImageFree();
BOOL ShowImage(CDC* pDC, Image* pImage);
//重载
public:
virtual void DrawItem(LPDRAWITEMSTRUCT lpDIT);
virtual void MeasureItem(LPMEASUREITEMSTRUCT lpMIT);
virtual void PreSubclassWindow();
virtual int CompareItem(LPCOMPAREITEMSTRUCT lpCIT);
DECLARE_MESSAGE_MAP()
afx_msg void OnPaint();
afx_msg BOOL OnEraseBkgnd(CDC* pDC);
};
.cpp文件
#include "pch.h"
#include "CPngListBox.h"
CPngListBox::CPngListBox()
{
m_NormalColor = RGB(250, 250, 250); //白色
m_selectColor = RGB(126, 206, 244); //设置为默认天蓝色
m_EdgeColor = GetSysColor(COLOR_BTNFACE); //边框颜色为按钮灰
m_EdgeSpace = 5; //上下边距默认5
m_ItemHeigh = 37;
}
CPngListBox::~CPngListBox()
{
ImageFree();
}
BOOL CPngListBox::Init(UINT resID,int itemHeigh,int edgeSpace)
{
m_imgNormal = ImageFromRes(resID, "PNG");
if (m_imgNormal == NULL) {
TRACE("ImageFromResource fail[%d]\n", GetLastError());
ImageFree();
return FALSE;
}
m_ItemHeigh = itemHeigh;
m_EdgeSpace = edgeSpace;
SetWindowPos(NULL, 0, 0, m_imgNormal->GetWidth(), m_imgNormal->GetHeight(), SWP_NOACTIVATE | SWP_NOMOVE);
return TRUE;
}
void CPngListBox::SetColor(COLORREF normalColor, COLORREF selectColor, COLORREF edgeColor)
{
m_NormalColor = normalColor;
m_selectColor = selectColor;
m_EdgeColor = edgeColor;
}
void CPngListBox::DrawItem(LPDRAWITEMSTRUCT lpDIT)
{
// TODO: 添加您的代码以绘制指定项
ASSERT(lpDIT->CtlType == ODT_LISTBOX);
/*LPCTSTR lpszText = (LPCTSTR)lpDIT->itemData;
if (lpszText == NULL) {
TRACE("CPngListBox::DrawItem() text=NULL\n");
return;
}*/
CString strText;
GetText(lpDIT->itemID, strText);
if (strText.GetLength() <= 0) {
TRACE("CPngListBox::DrawItem() text=NULL\n");
return;
}
CDC dc;
dc.Attach(lpDIT->hDC);
COLORREF crOldTextColor = dc.GetTextColor();
COLORREF crOldBkColor = dc.GetBkColor();
CRgn rgn1;
RECT r = lpDIT->rcItem;
RECT tr = { r.left + m_EdgeSpace, r.top + m_EdgeSpace, r.right - m_EdgeSpace, r.bottom};
rgn1.CreateRectRgnIndirect(&tr);
//窗口被选中 或者为活动
if ((lpDIT->itemAction | ODA_SELECT) && (lpDIT->itemState & ODS_SELECTED))
{
dc.SetTextColor(::GetSysColor(COLOR_HIGHLIGHTTEXT));//字体颜色白色
dc.SetBkColor(m_selectColor);//::GetSysColor(COLOR_HIGHLIGHT)
CBrush brush1(m_selectColor);
dc.FillRgn(&rgn1, &brush1);
brush1.DeleteObject();
}
else//窗口未被选中
{
//内嵌窗体颜色绘制
CBrush brush1(m_NormalColor);
dc.FillRgn(&rgn1, &brush1);
//内嵌窗体边框绘制
CBrush brush2(m_EdgeColor);
dc.FrameRgn(&rgn1, &brush2, 2, 2);
brush1.DeleteObject();
brush2.DeleteObject();
}
// 选中某一条目时使其边框高亮
/*if ((lpDIT->itemAction | ODA_FOCUS) && (lpDIT->itemState & ODS_FOCUS))
{
CBrush br(RGB(0, 0, 128));
dc.FrameRect(&lpDIT->rcItem, &br);
}*/
lpDIT->rcItem.left += 5;
TEXTMETRICA textInfo;
dc.GetTextMetrics(&textInfo);
textInfo.tmHeight = 16;
dc.GetTextMetrics(&textInfo);
// Draw the text.
dc.DrawText(strText, &tr, DT_SINGLELINE | DT_VCENTER | DT_CENTER);
dc.SetTextColor(crOldTextColor);
dc.SetBkColor(crOldBkColor);
dc.Detach();
rgn1.DeleteObject();
}
void CPngListBox::MeasureItem(LPMEASUREITEMSTRUCT lpMIT)
{
// TODO: 添加您的代码以确定指定项的大小
ASSERT(lpMIT->CtlType == ODT_LISTBOX);
//LPCTSTR lpszText = (LPCTSTR)lpMIT->itemData;
//ASSERT(lpszText != NULL);
CString strText;
GetText(lpMIT->itemID, strText);
if (strText.GetLength() <= 0) {
TRACE("CPngListBox::DrawItem() text=NULL\n");
return;
}
CSize sz;
CDC* pDC = GetDC();
sz = pDC->GetTextExtent(strText);
ReleaseDC(pDC);
int height = 2 * sz.cy;
lpMIT->itemHeight = (m_ItemHeigh > 0) ? m_ItemHeigh : height;
}
void CPngListBox::PreSubclassWindow()
{
// TODO: 在此添加专用代码和/或调用基类
ModifyStyle(0, LBS_OWNERDRAWVARIABLE | LBS_NOINTEGRALHEIGHT | WS_EX_TRANSPARENT);
ModifyStyle(LBS_SORT | WS_BORDER | WS_VSCROLL, 0);
CListBox::PreSubclassWindow();
}
int CPngListBox::CompareItem(LPCOMPAREITEMSTRUCT lpCIT)
{
// TODO: 添加您的代码以确定指定项的排序顺序
// 返回 -1 表示项 1 排在项 2 之前
// 返回 0 表示项 1 和项 2 顺序相同
// 返回 1 表示项 1 排在项 2 之后
return -1;
}
BOOL CPngListBox::ShowImage(CDC* pDC, Image* pImage)
{
bool bSuc = false;
if (pImage != NULL)
{
CRect rcButton;
rcButton = CRect(0, 0, pImage->GetWidth(), pImage->GetHeight());
Graphics graph(pDC->GetSafeHdc());
graph.DrawImage(pImage, 0, 0, rcButton.left, rcButton.top, rcButton.Width(), rcButton.Height(), UnitPixel);
graph.ReleaseHDC(pDC->GetSafeHdc());
bSuc = true;
}
return bSuc;
}
Image* CPngListBox::ImageFromRes(UINT uImgID, LPCTSTR lpType)
{
HINSTANCE hInstance = AfxGetResourceHandle();
HRSRC hResInfo = ::FindResource(hInstance, MAKEINTRESOURCE(uImgID), lpType);
if (hResInfo == NULL)
return NULL; //fail
DWORD dwSize;
dwSize = SizeofResource(hInstance, hResInfo); //get resource size(bytes)
HGLOBAL hResData;
hResData = ::LoadResource(hInstance, hResInfo);
if (hResData == NULL)
return NULL; //fail
HGLOBAL hMem;
hMem = ::GlobalAlloc(GMEM_MOVEABLE, dwSize);
if (hMem == NULL) {
::FreeResource(hResData);
return NULL;
}
LPVOID lpResData, lpMem;
lpResData = ::LockResource(hResData);
lpMem = ::GlobalLock(hMem);
::CopyMemory(lpMem, lpResData, dwSize); //copy memory
::GlobalUnlock(hMem);
::FreeResource(hResData); //free memory
IStream* pStream;
HRESULT hr;
hr = ::CreateStreamOnHGlobal(hMem, TRUE, &pStream);//create stream object
Image* pImage = NULL;
if (SUCCEEDED(hr)) {
pImage = Image::FromStream(pStream);//get GDI+ pointer
pStream->Release();
}
::GlobalFree(hMem);
return pImage;
}
void CPngListBox::ImageFree()
{
if (m_imgNormal){
delete m_imgNormal;
m_imgNormal = NULL;
}
}
BEGIN_MESSAGE_MAP(CPngListBox, CListBox)
ON_WM_PAINT()
ON_WM_ERASEBKGND()
END_MESSAGE_MAP()
void CPngListBox::OnPaint()
{
CPaintDC dc(this); // device context for painting
ShowImage(&dc, m_imgNormal);
int curSel = GetCurSel();
int count = GetCount();
SetCurSel(-1);
for (int i = 0; i < count; i++) {
SetCurSel(i);
}
SetCurSel(curSel);
}
BOOL CPngListBox::OnEraseBkgnd(CDC* pDC)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
//return CListBox::OnEraseBkgnd(pDC);
return TRUE;
}