前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >再谈duilib控件自绘

再谈duilib控件自绘

作者头像
大菊观
发布2021-09-14 15:33:44
1.1K0
发布2021-09-14 15:33:44
举报

之前写过一篇duilib自绘,其中主要介绍了PostPaint。这个由于机制原因,会导致一直绘制在本窗体最上层(当然还是比子窗口级别低的),这样有时候切换tab页之类的,会出现自绘的东西还在,不能完美跟随控件。下面重新介绍下duilib的绘制流程。

先介绍Paint,DoPaint,PaintBkcolor,PaintBkImage,PaintStatusImage,PaintText,PaintBorder,DoPostPaint 这些paint函数的大致作用以及调用的时机。

Paint函数是源头,从整体来看,整个布局的绘制起始就是PaintManager里面的MessageHandler,MessageHandler里面的WM_PAINT,WM_PAINT里面的m_pRoot->Paint(m_hDcOffscreen, rcPaint, NULL);。从单个控件看,也可以认为该控件绘制时最先调用Paint,因此如果自定义控件重写了Paint函数,在里面没有调用DoPaint等,那么DoPaint是不会被调用的(PostPaint另外再说)。调用流程是这样的:Paint内部调用DoPaint,DoPaint内部调用PaintBkcolor,PaintBkImage,PaintStatusImage,PaintText,PaintBorder。结合调用流程可以知道,控件自身的绘制的层次是这样的:先绘制bkcolor,在bkcolor上层绘制bkimage,再在上层绘制statusimage(其中statusimage中还包含foreimage),再在上层绘制text,再在上层绘制border。

再来说DoPostPaint,注意看源头也是在PaintManager里面的MessageHandler,MessageHandler里面的WM_PAINT,WM_PAINT里面在m_pRoot->Paint(m_hDcOffscreen, rcPaint, NULL);整个走完后,包括有本地窗口的比如activex、browser这些都绘制走完后,下方有个

代码语言:javascript
复制
for( int i = 0; i < m_aPostPaintControls.GetSize(); i++ ) 
{
    CControlUI* pPostPaintControl = static_cast<CControlUI*>(m_aPostPaintControls[i]);
    pPostPaintControl->DoPostPaint(m_hDcOffscreen, rcPaint);
}

遍历所有需要postpaint的控件来顺序调用他们各自的DoPostPaint。这样绘制层次就会在所有控件的上层,同时多个Dopostpaint的层次是根据m_aPostPaintControls里面的顺序来的,顺序是可以自行调整的。

以上绘制流程搞清楚后,再来根据需要进行自绘。

自绘的方式有(DoPostPaint单独再说):

1.自定义控件,从原有的控件上继承一个出来,然后根据需要重写下Dopaint或者PaintBkcolor、PaintBkImage、PaintStatusImage、PaintText、PaintBorder等。这样自绘的层次是严格遵守上述流程中介绍的层次的。

2.使用OnPaint成员变量,其实是duilib提供的一个委托机制,在

代码语言:javascript
复制
bool CControlUI::Paint(HDC hDC, const RECT& rcPaint, CControlUI* pStopControl)
{
	if (pStopControl == this) return false;
	if( !::IntersectRect(&m_rcPaint, &rcPaint, &m_rcItem) ) return true;
	if( OnPaint ) {
		if( !OnPaint(this) ) return true;
	}
	if (!DoPaint(hDC, rcPaint, pStopControl)) return false;
    if( m_pCover != NULL ) return m_pCover->Paint(hDC, rcPaint);
    return true;
}

这个代码里可以看到,如果OnPaint不为null,那么就调用OnPaint的函数绘制,同时还可以在OnPaint中通过返回值控制是否继续控件的DoPaint等(返回false就不再继续DoPaint)。以button为例,比如想在外部控制某个button控件的绘制,自己绘制点东西进去,那么可以这样写

代码语言:javascript
复制
bool OnPaintMybtn(void* pVoid)
{
	CButtonUI* pBtn = (CButtonUI*)pVoid;
	pBtn->PaintBkImage(pBtn->GetManager()->GetOffscreenDC());	

	RECT rc = pBtn->GetPos();
	Rect rcPlus(rc.left+3,rc.top+3,154,154);
	Graphics graphics(g_pMainWnd->m_PM.GetOffscreenDC());
	Pen color(0xFF2C95FF, 6);
	graphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);
	graphics.SetPixelOffsetMode(PixelOffsetModeHighQuality);
	graphics.DrawArc(&color,rcPlus, 270, 90);

    pBtn->PaintText(pBtn->GetManager()->GetOffscreenDC());

	return false;
}

void CMainWnd::MyFunc()
{
    CButtonUI* pBtn = static_cast<CButtonUI*>(m_PM.FindControl(L"my_btn"));
    pBtn->OnPaint += MakeDelegate(OnPaintMybtn);
}

随时随地可以给指定的控件my_btn添加个OnPaint,甚至还可以根据需要再次赋值成其他的绘制函数。然后自己实现一个OnPaintMybtn,在里面写绘制代码,可以看到我这里是调用了PaintBkImage绘制了背景图,然后gdi+绘制了一段圆弧线,然后又绘制了文本,最后返回false。没有去绘制bkcolor和border,以及statusimage。这样实际呈现出来的就是my_btn按钮,没有被绘制背景色,先绘制了背景图,背景图上边缘有个四分之一的圆弧,圆弧宽度是6, 然后在上层又绘制了文本,没有绘制边框。之后不再走控件本身的DoPaint来避免再走一遍该控件的PaintBkcolor、PaintBkImage、PaintStatusImage、PaintText、PaintBorder。这样就实现了不用自定义控件,但是能精确的对某个控件进行自定义绘制的需求。

再说DoPostPaint。

由上面介绍的流程可知,DoPostPaint本身绘制是需要自己去手动向PaintManager的m_aPostPaintControls数组中添加的,方法为m_PM.AddPostPaint(CControlUI* pControl)。然后在全部控件绘制完毕后,根据m_aPostPaintControls的顺序来调用控件的DoPostPaint。

同样想要使用DoPostPaint,也有两种法子:1.派生子控件,重载DoPostPaint函数。然后再自行AddPostPaint。2.使用委托,类似上面的例子pBtn->OnPostPaint += MakeDelegate(OnPostPaintMybtn);

duilib控件的自绘就先介绍到这里,大家可以根据实际需要来选择,而不用每次都去派生子控件。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2021-09-10 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档