求教duilib 聊天窗口窗口内嵌WebBrow

今天看啥 热点:
轻轻的我走了,正如我轻轻的来——Duilib无焦点窗口的实现,轻轻的我走了duilib
&&&&&&&& 在Windows编程中,我们已经习惯了一个窗口从创建到显示并获得焦点。我们总感觉一个窗口创建出来获得焦点是理所理所当然的,一个窗口只要显示就必须获得焦点。一个新窗口显示时,会收到到WM_SETFOCUS消息,然后旧的窗口会收到WM_KILLFOCUS消息。可能我们并不关心焦点的切换,因为从视觉角度来看,没有任何影响,但是键盘类的消息却在焦点变化时不断切换响应窗口。我们都知道,鼠标消息到来时,决定哪个窗口响应的是鼠标的坐标点,指哪里打哪里;键盘消息到来时,是谁来决定这个消息由哪个窗口响应呢?答案就是焦点。焦点是键盘消息的“舞台”,任何一个窗口只要登上焦点这个“舞台”,所有的键盘类消息自然会分配到该窗口的窗口过程函数。
&&&&&&&& 不过话又说回来了,在什么情况下的窗口不需要获得焦点呢?我总结了一下,有两种情况:1.提示类窗口,像我们常见的tooltipWindow,酷狗的音乐列表上的提示窗口,还有我们每天都在使用的QQ,把鼠标移到你的头像上,出现的提示信息窗口,当QQ收到消息时,鼠标指到QQ的任务栏图标上时出现的消息列表提示窗口,等等。当然,你有可能会问了,提示窗口和焦点有什么关系,提示窗口获得焦点又怎样?其实表面上影响不是很大,但是仔细想想这是不符合原生的Window控件机制的,WM_MOUSEMOVE消息只是个过客,键盘消息的“舞台”不能受鼠标移动干扰。比如我们打开QQ正在聊天时,或者正在填写一个比较繁琐的表格时,就晃了晃鼠标,光标没了,是不是很蛋疼?我们还得重新找到原来的编辑区,点一下刚才输入的位置让光标重新出现才能继续输入。提示类窗口的创建和销毁往往是由WM_MOUSEMOVE来控制,所以呢这类窗口不能获得焦点。2.菜单窗口,Windows原生菜单是不获取焦点的,可能你还不信,其实测试一下很容易就能看出来,用Visual
Studio建一个带有菜单的Win32项目,在窗口过程函数中捕获WM_KILLFOCUS消息,当菜单弹出时,WM_KILLFOCUS是不会响应的。这里肯定你还会问,菜单的弹出和焦点有毛关系,谁规定了菜单必须是无焦点的?当然没有人规定这些,但是遵循原生的机制绝对是没错的。首先,在自己使用窗口模拟菜单时,焦点的切换是一个很大的障碍,当然,有人还利用了这个特性,用WM_KILLFOCUS消息来控制菜单的窗口的销毁,这点在只有一级的菜单中还能体现出优势,但是在做级联菜单时,问题又来了,子菜单出来,主菜单就会失去焦点,这无形当中增添了不少麻烦。各级菜单的耦合度变高,维护起来相当复杂。其次,焦点的不断切换会影响美观,我一提这个,肯定还会有不少人产生疑问,这从何说起啊?焦点在菜单上看不见摸不着,怎么能影响外观?之前有人用Duilib写过一个MenuDemo,应该很多人都在用,效果看起来也不错。但是我个人觉得还是有问题,可能大家也不会把这个当成问题。当鼠标在主菜单上滑行时,子菜单会轮流弹出,如果这时碰巧你在这个窗口的某个编辑里开启输入法输完东西时,输入法的工具框还保留当前窗口,由于子菜单的切换显示,会导致输入法的工具框不断闪烁,我个人感觉是影响美观,不知道大家的看法。原因是由于焦点的切换,导致输入法消息的响应窗口不断切换,从而导致其闪烁。酷我音乐盒应该大家都用过,如果没猜错的话,用的就是Duilib的MenuDemo来实现的菜单。你可以试一下,切换成中文在搜索框里输入几个字,然后右击弹出菜单,用鼠标在主菜单上滑行,让子菜单轮流弹出,看看那个输入法工具框是不是在不停闪烁。还有酷我音乐盒的所有提示框都是带焦点的,在菜单弹出时,不得不屏蔽某些控件的MouseHover事件,防止提示框的弹出抢走焦点,导致菜单销毁。我想这应该是由于Duilib目前不支持无焦点窗口的原因,为了解决这个问题,Skilla在Duilib的菜单上花了不少时间,因为网上这方面的资料真心太少了,还好最后有了成果,所以还有机会和大家一起分享。
&&&&&& 其实,弄一个无焦点的窗口还是比较简单的,1.在窗口显示时,使用ShowWindow(SW_SHOWNOACTIVE),这样窗口显示时就不会获得焦点了,但是不能点击,点击完还是会获得焦点。 2.在窗口过程中截获WM_MOUSEACTIVATE消息,返回MA_NOACTIVE,这样就完美了,这时在客户区任凭你把鼠标左键右键全点烂了这个窗口也不会获得焦点,但是不能点标题栏,这也无妨,在Duilib中我们的窗口一般是不带原生标题栏的,把标题栏去掉就是了,这时只要你不主动去SetFocus,窗口是永远不会获得焦点的。
&&&&&& 如果仅用Win32或者MFC编程,上面两步就够了,但我们还使用了Duilib框架,关于窗口焦点的控制参与的不仅仅是CWindowWnd,CPaintManagerUI也做了大量的::SetFocus操作,如果不做处理窗口还是照样会获得焦点。这时我们需要对源码做一下小小的修改。1.给CPaintManagerUI添加一个布尔类型属性bool m_bUnfocusPaintW来区分所绘制窗口是否为无焦点窗口,在构造时初始化为false,并添加Get、Set方法,让外界能访问到。2.在CPaintManagerUI的cpp文件中查找所有的::SetFocus操作(注意前面的两个点),给所有的::SetFocus操作加上if(!m_bUnfocusPaintWindow)的判断。这时我们仅需要在将m_bUnfocusPaintWindow设置为true即可防止CPaintManager抢走焦点。这样既不影响原来的功能,又达到了我们想要的效果。3.为了方便使用在CDialogBuilder的cpp文件的219行加上一个判断
else if(_tcscmp(pstrName, _T(&unfocus&)) == 0)
&& &&& &&& &&& &&& &{
&& &&& &&& &&& &&& &&& &if(_tcscmp(pstrName,_T(&true&)))
&& &&& &&& &&& &&& &&& &pManager-&SetUnfocusPaintWindow(true);
&& &&& &&& &&& &&& &}
这样,我们就可以在xml里面设置窗口是否为无焦点窗口了,只需在Window标签上加一个unfocus=&true&的属性就可以了。例如
&Window size=&400,240&& caption=&0,0,0,40&& roundcorner=&5,5& unfocus=&true&&
&&&&&&& 这只是无焦点窗口在TipWindow上的应用,关于菜单的实现我会尽快写好Demo来和大家一起分享。
&&&&&&& 如果大家还有什么不明白的地方,或者对我的修改有什么意见或的法的,可以直接留言,或者联系QQ:(Skilla)
相关搜索:
相关阅读:
相关频道:
&&&&&&&&&&&&&&&&
WEB编程教程最近更新今天看啥 热点:
duilib开发基础:创建自定义控件的过程,duilib控件
转载请说明原出处,谢谢~·http://blog.csdn.net/zhuhongshu/article/details/
& & & &用Duilib开发界面时,很多情况下库自带的控件不满足需求,就需要基于Duilib建立自定义控件(自绘新的控件,或者用来封装win32的子窗体,来显示视频、网页等)。
& & & &在群里经常会有刚接触Duilib的朋友问题怎么建立自己的自定义控件,或者建立的控件无法正常创建出来。我简单写一篇博客,把创建自定义控件的完整过程,和一些注意事项说明一下。另外说一下如果把win32的子窗体封装为控件,希望能有帮助。
& & & &创建自定义控件包含两个过程:
& & & &1、继承现有的控件类创建新的控件类
& & & &2、让程序识别新的控件并可以在xml中使用
创建新的控件类:
& & & &首先从的现有的Duilib控件中选择一个最合适的控件类,继承他然后重写几个接口。
& & & &我这里拿仿酷狗的换肤窗体中的一个自绘控件做例子(原文地址:http://blog.csdn.net/zhuhongshu/article/details/)
& & & &仿酷狗音乐盒源代码:http://blog.csdn.net/zhuhongshu/article/details/
& & & 为了做出换肤控件,首先选择CButtonUI为父类,因为CButtonUI控件本身就已经包含了normal、hot、pushed等状态,同时包含单击事件。
& & &#ifndef SKIN_PICKER_PICTURE_ITEM_H
#define SKIN_PICKER_PICTURE_ITEM_H
//xml sample:&SkinPikerPictureItem name=&& width=&118& height=&70& bkimage=&UI\BKImage\1small.jpg& bkname=&测试& author=&Redrain& /&
//类名和接口名,在CreateControl函数中会用到
const TCHAR kSkinPickerPictureItemClassName[] = _T(&SkinPikerPictureItemUI&);
const TCHAR kSkinPickerPictureItemInterface[] = _T(&SkinPikerPictureItem&);
//黑色的前景图的位置
const TCHAR kSkinPickerPictureItemForeImage[] = _T(&file='UI\\LeftTab\\listitem\\ListBk.png' fade='150'&);
//边框的颜色、图片名称的文字颜色、作者信息的文字颜色
const DWORD kBorderColor
= 0xFF64B0FA;
const DWORD kBkNameColor
= 0xFFFFFFFF;
const DWORD kAuthorColor
= 0xFFAAAAAA;
class CSkinPikerPictureItemUI : public CButtonUI
CSkinPikerPictureItemUI();
LPCTSTR GetClass()
LPVOID GetInterface(LPCTSTR pstrName);
void SetAttribute(LPCTSTR pstrName, LPCTSTR pstrValue);
void PaintStatusImage(HDC hDC);
CDuiString m_BkN
CDuiString m_A
#endif // SKIN_PICKER_PICTURE_ITEM_H
CSkinPikerPictureItemUI::CSkinPikerPictureItemUI()
m_Author = _T(&作者:&);
LPCTSTR CSkinPikerPictureItemUI::GetClass() const
return kSkinPickerPictureItemClassN
LPVOID CSkinPikerPictureItemUI::GetInterface(LPCTSTR pstrName)
if( _tcscmp(pstrName, kSkinPickerPictureItemInterface) == 0 ) return static_cast&CSkinPikerPictureItemUI*&(this);
return CButtonUI::GetInterface(pstrName);
void CSkinPikerPictureItemUI::SetAttribute(LPCTSTR pstrName, LPCTSTR pstrValue)
if( _tcscmp(pstrName, _T(&bkname&)) == 0 ) m_BkName = pstrV
else if( _tcscmp(pstrName, _T(&author&)) == 0 ) m_Author += pstrV
CButtonUI::SetAttribute(pstrName, pstrValue);
void CSkinPikerPictureItemUI::PaintStatusImage(HDC hDC)
CButtonUI::PaintStatusImage(hDC);
if( IsFocused() ) m_uButtonState |= UISTATE_FOCUSED;
else m_uButtonState &= ~ UISTATE_FOCUSED;
if( !IsEnabled() ) m_uButtonState |= UISTATE_DISABLED;
else m_uButtonState &= ~ UISTATE_DISABLED;
if( (m_uButtonState & UISTATE_PUSHED) != 0 || (m_uButtonState & UISTATE_HOT) != 0) {
DrawImage(hDC, kSkinPickerPictureItemForeImage) ;
//计算作者信息文字和背景图片名字文字的显示位置,这里是用了硬编码,请使用者自己修改
RECT rcBkName = m_rcI
LONG nTextPadding = (m_rcItem.right - m_rcItem.left
- CRenderEngine::GetTextSize(hDC, GetManager(),\
m_BkName.GetData(), m_iFont, m_uTextStyle).cx) / 2;
rcBkName.left += nTextP
rcBkName.right -= nTextP
rcBkName.top += 15;
rcBkName.bottom = rcBkName.top + 20;
RECT rcAuthor = m_rcI
nTextPadding = (m_rcItem.right - m_rcItem.left - CRenderEngine::GetTextSize(hDC, GetManager(),\
m_Author.GetData(), m_iFont, m_uTextStyle).cx) / 2;
rcAuthor.left += nTextP
rcAuthor.right -= nTextP
rcAuthor.top += 40;
rcAuthor.bottom = rcAuthor.top + 20;
CRenderEngine::DrawText(hDC, m_pManager, rcBkName, m_BkName, kBkNameColor, m_iFont, m_uTextStyle);
CRenderEngine::DrawText(hDC, m_pManager, rcAuthor, m_Author, kAuthorColor, m_iFont, m_uTextStyle);
CRenderEngine::DrawRect(hDC, m_rcItem, 2, kBorderColor);
& & & &新的控件名为CSkinPickerPictureItemUI。一般来说,建立新控件后,最先应该重写的两个函数是GetClass和GetInterface。他们后用来区分控件的类型的虚函数,用于动态识别控件类型和做控件的类型转换。
& & & &从Duilib的自带控件上可以看出,比如当前的自定义控件类名为CSkinPickerPictureItemUI,那么GetClass函数返回的字符串SkinPickerPictureItemUI。而GetInterface函数是根据传入的参数,是否与自身的字符串匹配,来决定能否把自己转换为需要的控件类型。GetInterface中用来匹配的字符串,应该与xml中的对应的控件的标签名称一直,这里应该是SkinPickerPictureItem。
& & & 比如CButtonUI类,GetClass对应ButtonUI,GetInterface对应Button。这不是强制的,但是保持这个风格很重要!
& & & 理论上,完成这两个接口就创建好最基本的自定义控件了。但是为了让自定义控件的行为和外观更丰富,就需要重写更多的函数了,我这里把经常会重写的函数说明一下!
virtual void DoEvent(TEventUI& event);
virtual void DoPaint(HDC hDC, const RECT& rcPaint);
virtual void PaintBkColor(HDC hDC);
virtual void PaintBkImage(HDC hDC);
virtual void PaintStatusImage(HDC hDC);
virtual void PaintText(HDC hDC);
virtual void PaintBorder(HDC hDC);
virtual void DoInit();
virtual void SetAttribute(LPCTSTR pstrName, LPCTSTR pstrValue);
virtual bool IsVisible()
virtual void SetVisible(bool bVisible = true);
virtual void SetInternVisible(bool bVisible = true); // 仅供内部调用,有些UI拥有窗口句柄,需要重写此函数
virtual void SetPos(RECT rc);
& & & &以上列出的函数,是最常被重写的。
& & & &DoEvent函数:控件的核心函数,他是消息处理函数,用来处理Duilib封装过的各个消息,比如鼠标的移入移出、出现的悬停、单击双击、右击、获取焦点、设置光标等等。所以如果你的控件需要修改这些行为,必须重写这个函数,具体的处理方法可以参考Duilib现有的控件或者仿酷狗程序。
& & & &DoPaint函数:控件的核心函数,他是控件的绘制处理函数,当Duilib底层要重新绘制这个控件,或者控件自己调用Invalidata函数强制自己刷新时,这个函数就会被触发,在这个函数里完成了各种状态下的背景前景绘制,背景色绘制,文本绘制,边框绘制。而这个函数会调用PaintBkColor、PaintBkImage、PaintStatusImage、PaintText、PaintBorder等函数来完成各个绘制步骤。所以你可以根据需求来决定重写DoPaint或者只重写某几个PaintXXX函数。DoPaint函数经常和DoEvent函数结合使用,DoEvent得到了某个消息后,改变控件的状态,然后调用Invalidate函数让控件重绘。
& & & SetAttribute函数:用于扩展自定义控件的属性,Duilib的控件本身已经包含name、text、bkimage等属性,如果要增加新属性,就需要重写此函数来扩展属性,上面的CSkinPickerPictureItemUI例子中已经有用法了。
& & & DoInit函数:当控件被添加到容器后,由容器调用的函数。在这里,整个Duilib程序框架已经完成,当需要做一些界面的初始操作时可以重写此函数,常见的用法就是在此建立Win32子窗体并且封装它,相关内容我在后面再说。
& & &IsVisible、SetVisible、SetInternelVisible、SetPos:这几个函数同样也是,当控件封装了Win32子窗口后,重写这几个函数来控制子窗口的显示和隐藏、和位置。
& & & 这样就创建完成了自定义控件。
识别新控件:
& & & &自定义控件创建完毕后,需要做的就是让控件可以被xml布局识别出来。为此我们需要完成Duilib的IDialogBuilderCallback接口,重写这个接口中的CreateControl函数。
& & & &通常情况下,可以让窗体类继承IDialogBuilderCallback接口并且重写CreateControl(DuiLib自带的WindowImplBase窗体类已经继承了这个接口,如果是继承WindowImplBase的话就直接重写CreateControl就可以了)。函数处理方法是比较传入的字符串,根据字符串来决定返回什么控件的指针,这个传入的字符串就是xml文件中控件的标签,比如&Button /&中的字符串Button。
CControlUI* CSkinPickerDialog::CreateControl(LPCTSTR pstrClass)
if (_tcsicmp(pstrClass, kSkinPickerPictureItemInterface) == 0)
return new CSkinPikerPictureItemUI();
return NULL;
& & & 习惯上,在xml中自定义控件的标签名称应该和控件的GetInterface中的判断字符串一致,比如这里就是SkinPickerPictureItem。这样,在解析xml过程中,当解析到名为SkinPickerPictureItem的标签时,就会创建出CSkinPickerPictureItemUI控件了。
& & & &实际上,谁来继承IDialogBuilderCallback接口肯定都可以,比如QQDemo和仿酷狗里面,都给自定义控件本身继承了这个接口。
& & & &当程序响应WM_CREATE消息时,会建立一个CDialogBuilder对象,并且调用他的Create方法来解析xml文件。
CControlUI* CDialogBuilder::Create(STRINGorID xml, LPCTSTR type, IDialogBuilderCallback* pCallback,
CPaintManagerUI* pManager, CControlUI* pParent)
& & & & 这个函数 的第一个参数指定为xml文件的路径;第二个参数一般指定为NULL,我这里不详解了;第三个参数,就是识别自定义控件的关键了,这个参数要指定为继承了IDialogBuilderCallback接口的类对象的指针,比如窗体类继承IDialogBuilderCallback,这个参数就填写窗体类对象的指针。只有填写了这个参数,自定义控件才会被识别,经常有人问自己的自定义控件为什么无法被识别。多数情况就是这里没处理好;第四个参数指定CPaintManagerUI类对象指针,这个肯定会伴随着窗体类对象一起存在。最后一个参数一般为NULL。
& & & & 这几步都完成后,你的自定义控件就可以被xml布局正确的识别并创建了。至此,创建自定义控件的基本过程就完成了!如果有不明白的,可以多看看仿酷狗的代码、QQDemo等。
封装Win32控件或者Win32子窗口:
& & & &如果要给Duilib,增加一个视频播放控件,一般来说视频播放库都需要依赖一个子窗口。这时,就应该自定义个控件,并且封装维护一个子窗口了。
& & & &封装的子窗口有三种:第一种比较简单、单纯封装一个子窗口、让视频库一类的库依赖;第二种麻烦一些、封装子窗口、并且处理子窗口的消息;第三种和第二种类似、封装Win32的控件并且处理他的消息。
& & & 单纯封装子窗口:
& & & 这时就需要重写我之前提到的DoInit函数和SetVisbile等函数了。首先在自定义控件内声明HWND类型的m_hWnd成员变量来保存子窗体指针。
& & & 在DoInit函数里,调用CreateWindowEx函数,创建一个win32子窗体,并且用m_hWnd保存句柄。比如:
m_hhWnd = CreateWindow(_T(&#32770&), _T(&WndMediaDisplay&), WS_VISIBLE | WS_CHILD, 0, 0, 0, 0, m_PaintManager.GetPaintWindow(), (HMENU)0, NULL, NULL);
& & & 然后在SetVisible等函数内控制子窗体的显示隐藏;在SetPos函数内控制子窗体的位置、限制在本控件的范围内。
& & &这样就封装好了win32子窗口,然后可以把这个窗体句柄用于视频播放等。
& & &&封装子窗口并处理他的消息:
& & & 这时就比较麻烦了,参见Duilib的CEditUI控件等。我们需要继承CWindowWnd另外封装一个窗体类,窗体类的封装不属于本文范围,我就不细说了。重写窗体类的HandleMessage函数,来响应各种WM_XXX消息。
& & & 然后在我们的自定义控件内,不再声明HWND类型m_hWnd变量了,而是自定刚才的窗体类的对象。然后在DoInit函数内调用这个对应的Create函数函数来创建窗体类。然后同样还是维护这个窗体的显示隐藏、和位置。
& & &关于这种控件的封装,可以参考我写的webkit内核浏览器控件、里面是完整的封装了Win32子窗体、并且处理了他的消息,用于显示webkit内核渲染的网页。地址:http://blog.csdn.net/zhuhongshu/article/details/
& & &&封装Win32控件并处理他的消息:
& & & 这个可以参考CEditUI控件的处理代码,思想上和封装子窗口并处理消息是一样的。同样也可以参考webkit内核浏览器控件代码。不过与之不同的是,我们需要重写两个函数
LPCTSTR GetWindowClassName()
LPCTSTR GetSuperClassName()
& & & 这里最主要的就是处理GetSuperClassName函数,这个函数的作用就是超类化,而封装子窗口并处理消息是子类化,这两个操作恰好相反。在GetSuperClassName函数内,要范围Win32控件对应的类名、Duilib检测到GetSuperClassName函数函数后就会创建Win32控件。这时我们处理HandleMessage函数,就可以处理到Win32控件的消息的。具体的处理逻辑请参见CEditUI控件。
& & & & 差不多就说道这里了,把常见的自定义控件的基本步骤说明了一下,实际开发时还要多看Duilib的源码,才能称心如意的开发控件,希望对刚接触Duilib的朋友有帮助!
Redrain &QQ: &
相关搜索:
相关阅读:
相关频道:
&&&&&&&&&&&&&&&&
WEB编程教程最近更新今天看啥 热点:
duilib 使用图片素材或者算法给窗体增加阴影(源码和demo),duilib图片素材
转载请说明原出处,谢谢:http://blog.csdn.net/zhuhongshu/article/details/
& & & & &之前我写的程序使用阴影时,一直是使用codeproject网站上的WndShadow类,并且把它当作单独的模块来使用,后来觉得使用阴影的情况非常多,所以今天就把这个类改写了一下,让他融入到duilib,并且可以直接使用xml来描述阴影,不需要写任何c++代码。
& & & & 以前的WndShadow类是用算法来计算阴影,灵活性很大,但是缺点就是效果不够理想,所以我另外给他附加了使用图片素材来贴阴影的功能。最终的新类名为CShadowUI。这个类可以单独使用,我把他集成到了自己的库里。为了融合到Duilib我修改了UiLib.h文件、UIDlgBuilder.cpp文件、UIManager.h文件、UIManager.cpp文件。
& & & & 先贴两张效果图,以下是素材阴影和算法阴影的效果图:
& & & & 通过设置xml的Window标签可以添加阴影,阴影的xml属性描述如下:
&Attribute name=&showshadow& default=&false& type=&BOOL& comment=&是否启用窗体阴影&/&
&Attribute name=&shadowimage& default=&& type=&STRING& comment=&阴影图片,使用此属性后自动屏蔽算法阴影(不支持source等属性设置)&/&
&Attribute name=&shadowcorner& default=&0,0,0,0& type=&RECT& comment=&图片阴影的九宫格描述&/&
&Attribute name=&shadowsize& default=&0& type=&BYTE& comment=&算法阴影的宽度(-20到20)&/&
&Attribute name=&shadowsharpness& default=&255& type=&BYTE& comment=&算法阴影的锐度&/&
&Attribute name=&shadowdarkness& default=&255& type=&BYTE& comment=&算法阴影的深度(相当于透明度)&/&
&Attribute name=&shadowpositon& default=&0,0& type=&SIZE& comment=&算法阴影的偏移量&/&
&Attribute name=&shadowcolor& default=&0x000000& type=&DWORD& comment=&算法阴影的颜色,RGB格式,不支持透明度,使用shadowdarkness设置透明度&/&
& & & & 前面的两个效果图的对应xml描述如下:
&!-- 图片阴影 --&
&Window size=&840,600& sizebox=&4,4,4,4& caption=&0,0,0,75& mininfo=&840,600& showshadow=&true& shadowimage=&shadow.png& shadowcorner=&23,13,23,33&&
&!-- 算法阴影 --&
&Window size=&840,600& sizebox=&4,4,4,4& caption=&0,0,0,75& mininfo=&840,600& showshadow=&true& shadowsize=&5& shadowpositon=&1,1& shadowcolor=&#333333&&
& & & &UIShadow.h源码为:
// WndShadow.h : header file
// Version 0.1
// Copyright (c) 2006 Perry Zhu, All Rights Reserved.
// mailto:
// This source file may be redistributed unmodified by any means PROVIDING
// it is NOT sold for profit without the authors expressed written
// consent, and providing that this notice and the author's name and all
// copyright notices remain intact. This software is by no means to be
// included as part of any third party components library, or as part any
// development solution that offers MFC extensions that are sold for profit.
// If the source code is used in any commercial applications then a statement
// along the lines of:
// &Portions Copyright (c) 2006 Perry Zhu& must be included in the &Startup
// Banner&, &About Box& or &Printed Documentation&. This software is provided
// &as is& without express or implied warranty. Use it at your own risk! The
// author accepts no liability for any damage/loss of business that this
// product may cause.
/////////////////////////////////////////////////////////////////////////////
//****************************************************************************
/********************************************************************
UIShadow.h
purpose: DuiLib阴影类,在原WndShadow类的基础上,增加了通过PNG图片设置阴影的功能,并且把代码与DuiLib融合
*********************************************************************/
#ifndef __UISHADOW_H__
#define __UISHADOW_H__
#pragma once
#include &map&
namespace DuiLib
typedef BOOL (WINAPI *pfnUpdateLayeredWindow)(HWND hWnd, HDC hdcDst, POINT *pptDst,
SIZE *psize, HDC hdcSrc, POINT *pptSrc, COLORREF crKey,
BLENDFUNCTION *pblend, DWORD dwFlags);
class UILIB_API CShadowUI
friend class CPaintManagerUI;
CShadowUI(void);
virtual ~CShadowUI(void);
// bShow为真时才会创建阴影
void ShowShadow(bool bShow);
bool IsShowShadow()
// 算法阴影的函数
bool SetSize(int NewSize = 0);
bool SetSharpness(unsigned int NewSharpness = 5);
bool SetDarkness(unsigned int NewDarkness = 200);
bool SetPosition(int NewXOffset = 5, int NewYOffset = 5);
bool SetColor(COLORREF NewColor = 0);
// 图片阴影的函数
bool SetImage(LPCTSTR szImage);
bool SetShadowCorner(RECT rcCorner); // 九宫格方式描述阴影
protected:
// 初始化并注册阴影类
static bool Initialize(HINSTANCE hInstance);
// 创建阴影窗体,由CPaintManagerUI自动调用
void Create(CPaintManagerUI* pPaintManager);
// 子类化父窗体
static LRESULT CALLBACK ParentProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
// 父窗体改变大小,移动,或者主动重绘阴影时调用
void Update(HWND hParent);
// 通过算法计算阴影
void MakeShadow(UINT32 *pShadBits, HWND hParent, RECT *rcParent);
// 计算alpha预乘值
inline DWORD PreMultiply(COLORREF cl, unsigned char nAlpha)
return (GetRValue(cl) * (DWORD)nAlpha / 255) |
(GetGValue(cl) * (DWORD)nAlpha / 255) && 8 |
(GetBValue(cl) * (DWORD)nAlpha / 255) && 16 ;
protected:
enum ShadowStatus
SS_ENABLED = 1,
// Shadow is enabled, if not, the following one is always false
SS_VISABLE = 1 && 1,
// Shadow window is visible
SS_PARENTVISIBLE = 1&& 2 // Parent window is visible, if not, the above one is always false
// 保存已经附加的窗体句柄和与其关联的阴影类,方便在ParentProc()函数中通过句柄得到阴影类
static std::map&HWND, CShadowUI *& *s_S
static bool s_bHasI
CPaintManagerUI *m_pM
// 父窗体的CPaintManagerUI,用来获取素材资源和父窗体句柄
// 阴影窗体的句柄
m_OriParentP // 子类化父窗体
m_bIsImageM // 是否为图片阴影模式
m_bIsShowS // 是否要显示阴影
// 算法阴影成员变量
unsigned char m_nD // Darkness, transparency of blurred area
unsigned char m_nS // Sharpness, width of blurred border of shadow window
signed char m_nS // Shadow window size, relative to parent window size
// The X and Y offsets of shadow window,
// relative to the parent window, at center of both windows (not top-left corner), signed
signed char m_nxO
signed char m_nyO
// Restore last parent window size, used to determine the update strategy when parent window is resized
LPARAM m_WndS
// Set this to true if the shadow should not be update until next WM_PAINT is received
COLORREF m_C // Color of shadow
// 图片阴影成员变量
CDuiString m_sShadowI
m_rcShadowC
#endif //__UISHADOW_H__
& & & &UIShadow.cpp源码为:
#include &StdAfx.h&
#include &UIShadow.h&
#include &math.h&
#include &crtdbg.h&
#include &Core/UIManager.h&
namespace DuiLib
const TCHAR *strWndClassName = _T(&PerryShadowWnd&);
std::map&HWND, CShadowUI *&* CShadowUI::s_Shadowmap = new std::map&HWND, CShadowUI *&;
bool CShadowUI::s_bHasInit = FALSE;
CShadowUI::CShadowUI(void)
: m_hWnd((HWND)NULL)
, m_OriParentProc(NULL)
, m_nDarkness(150)
, m_nSharpness(5)
, m_nSize(0)
, m_nxOffset(0)
, m_nyOffset(0)
, m_Color(RGB(0, 0, 0))
, m_WndSize(0)
, m_bUpdate(false)
, m_bIsImageMode(false)
, m_bIsShowShadow(false)
::ZeroMemory(&m_rcShadowCorner, sizeof(RECT));
CShadowUI::~CShadowUI(void)
bool CShadowUI::Initialize(HINSTANCE hInstance)
if (s_bHasInit)
// Register window class for shadow window
WNDCLASSEX
memset(&wcex, 0, sizeof(wcex));
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style
= CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = DefWindowP
wcex.cbClsExtra
wcex.cbWndExtra
wcex.hInstance
wcex.hIcon
wcex.hCursor
= LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wcex.lpszMenuName = NULL;
wcex.lpszClassName = strWndClassN
wcex.hIconSm
RegisterClassEx(&wcex);
s_bHasInit =
void CShadowUI::Create(CPaintManagerUI* pPaintManager)
if(!m_bIsShowShadow)
// Already initialized
_ASSERT(CPaintManagerUI::GetInstance() != INVALID_HANDLE_VALUE);
_ASSERT(pPaintManager != NULL);
m_pManager = pPaintM
HWND hParentWnd = m_pManager-&GetPaintWindow();
// Add parent window - shadow pair to the map
_ASSERT(s_Shadowmap-&find(hParentWnd) == s_Shadowmap-&end()); // Only one shadow for each window
(*s_Shadowmap)[hParentWnd] =
// Determine the initial show state of shadow according to parent window's state
LONG lParentStyle = GetWindowLong(hParentWnd, GWL_STYLE);
// Create the shadow window
LONG styleValue = lParentStyle & WS_CAPTION;
m_hWnd = CreateWindowEx(WS_EX_LAYERED | WS_EX_TRANSPARENT, strWndClassName, NULL,
/*WS_VISIBLE | */styleValue | WS_POPUPWINDOW,
CW_USEDEFAULT, 0, 0, 0, hParentWnd, NULL, CPaintManagerUI::GetInstance(), NULL);
if(!(WS_VISIBLE & lParentStyle)) // Parent invisible
m_Status = SS_ENABLED;
else if((WS_MAXIMIZE | WS_MINIMIZE) & lParentStyle) // Parent visible but does not need shadow
m_Status = SS_ENABLED | SS_PARENTVISIBLE;
else // Show the shadow
m_Status = SS_ENABLED | SS_VISABLE | SS_PARENTVISIBLE;
::ShowWindow(m_hWnd, SW_SHOWNA);
Update(hParentWnd);
// Replace the original WndProc of parent window to steal messages
m_OriParentProc = GetWindowLong(hParentWnd, GWL_WNDPROC);
#pragma warning(disable: 4311) // temporrarily disable the type_cast warning in Win32
SetWindowLong(hParentWnd, GWL_WNDPROC, (LONG)ParentProc);
#pragma warning(default: 4311)
LRESULT CALLBACK CShadowUI::ParentProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
_ASSERT(s_Shadowmap-&find(hwnd) != s_Shadowmap-&end()); // Shadow must have been attached
CShadowUI *pThis = (*s_Shadowmap)[hwnd];
switch(uMsg)
case WM_MOVE:
if(pThis-&m_Status & SS_VISABLE)
GetWindowRect(hwnd, &WndRect);
if (pThis-&m_bIsImageMode)
SetWindowPos(pThis-&m_hWnd, 0,
WndRect.left - pThis-&m_rcShadowCorner.left, WndRect.top - pThis-&m_rcShadowCorner.top,
0, 0, SWP_NOSIZE | SWP_NOACTIVATE);
SetWindowPos(pThis-&m_hWnd, 0,
WndRect.left + pThis-&m_nxOffset - pThis-&m_nSize, WndRect.top + pThis-&m_nyOffset - pThis-&m_nSize,
0, 0, SWP_NOSIZE | SWP_NOACTIVATE);
case WM_SIZE:
if(pThis-&m_Status & SS_ENABLED)
if(SIZE_MAXIMIZED == wParam || SIZE_MINIMIZED == wParam)
::ShowWindow(pThis-&m_hWnd, SW_HIDE);
pThis-&m_Status &= ~SS_VISABLE;
else if(pThis-&m_Status & SS_PARENTVISIBLE) // Parent maybe resized even if invisible
// Awful! It seems that if the window size was not decreased
// the window region would never be updated until WM_PAINT was sent.
// So do not Update() until next WM_PAINT is received in this case
if(LOWORD(lParam) & LOWORD(pThis-&m_WndSize) || HIWORD(lParam) & HIWORD(pThis-&m_WndSize))
pThis-&m_bUpdate =
pThis-&Update(hwnd);
if(!(pThis-&m_Status & SS_VISABLE))
::ShowWindow(pThis-&m_hWnd, SW_SHOWNA);
pThis-&m_Status |= SS_VISABLE;
pThis-&m_WndSize = lP
case WM_PAINT:
if(pThis-&m_bUpdate)
pThis-&Update(hwnd);
pThis-&m_bUpdate =
// In some cases of sizing, the up-right corner of the parent window region would not be properly updated
// Update() again when sizing is finished
case WM_EXITSIZEMOVE:
if(pThis-&m_Status & SS_VISABLE)
pThis-&Update(hwnd);
case WM_SHOWWINDOW:
if(pThis-&m_Status & SS_ENABLED)
if(!wParam) // the window is being hidden
::ShowWindow(pThis-&m_hWnd, SW_HIDE);
pThis-&m_Status &= ~(SS_VISABLE | SS_PARENTVISIBLE);
else if(!(pThis-&m_Status & SS_PARENTVISIBLE))
//pThis-&Update(hwnd);
pThis-&m_bUpdate =
::ShowWindow(pThis-&m_hWnd, SW_SHOWNA);
pThis-&m_Status |= SS_VISABLE | SS_PARENTVISIBLE;
case WM_DESTROY:
DestroyWindow(pThis-&m_hWnd); // Destroy the shadow
case WM_NCDESTROY:
s_Shadowmap-&erase(hwnd); // Remove this window and shadow from the map
#pragma warning(disable: 4312) // temporrarily disable the type_cast warning in Win32
// Call the default(original) window procedure for other messages or messages processed but not returned
return ((WNDPROC)pThis-&m_OriParentProc)(hwnd, uMsg, wParam, lParam);
#pragma warning(default: 4312)
void CShadowUI::Update(HWND hParent)
GetWindowRect(hParent, &WndRect);
int nShadWndW
int nShadWndH
if (m_bIsImageMode)
if(m_sShadowImage.IsEmpty())
nShadWndWid = WndRect.right - WndRect.left + m_rcShadowCorner.left + m_rcShadowCorner.
nShadWndHei = WndRect.bottom - WndRect.top + m_rcShadowCorner.top + m_rcShadowCorner.
nShadWndWid = WndRect.right - WndRect.left + m_nSize * 2;
nShadWndHei = WndRect.bottom - WndRect.top + m_nSize * 2;
// Create the alpha blending bitmap
BITMAPINFO
// bitmap header
ZeroMemory(&bmi, sizeof(BITMAPINFO));
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmi.bmiHeader.biWidth = nShadWndW
bmi.bmiHeader.biHeight = nShadWndH
bmi.bmiHeader.biPlanes = 1;
bmi.bmiHeader.biBitCount = 32;
// four 8-bit components
bmi.bmiHeader.biCompression = BI_RGB;
bmi.bmiHeader.biSizeImage = nShadWndWid * nShadWndHei * 4;
// pointer to DIB section
HBITMAP hbitmap = CreateDIBSection(NULL, &bmi, DIB_RGB_COLORS, (void **)&pvBits, NULL, 0);
HDC hMemDC = CreateCompatibleDC(NULL);
HBITMAP hOriBmp = (HBITMAP)SelectObject(hMemDC, hbitmap);
if (m_bIsImageMode)
RECT rcPaint = {0, 0, nShadWndWid, nShadWndHei};
const TImageInfo* data = m_pManager-&GetImageEx((LPCTSTR)m_sShadowImage, NULL, 0);
if( !data )
RECT rcBmpPart = {0};
rcBmpPart.right = data-&nX;
rcBmpPart.bottom = data-&nY;
CRenderEngine::DrawImage(hMemDC, data-&hBitmap, rcPaint, rcPaint, rcBmpPart, m_rcShadowCorner, data-&alphaChannel, 0xFF, true, false, false);
ZeroMemory(pvBits, bmi.bmiHeader.biSizeImage);
MakeShadow((UINT32 *)pvBits, hParent, &WndRect);
if (m_bIsImageMode)
ptDst.x = WndRect.left - m_rcShadowCorner.
ptDst.y = WndRect.top - m_rcShadowCorner.
ptDst.x = WndRect.left + m_nxOffset - m_nS
ptDst.y = WndRect.top + m_nyOffset - m_nS
POINT ptSrc = {0, 0};
SIZE WndSize = {nShadWndWid, nShadWndHei};
BLENDFUNCTION blendPixelFunction= { AC_SRC_OVER, 0, 255, AC_SRC_ALPHA };
MoveWindow(m_hWnd, ptDst.x, ptDst.y, nShadWndWid, nShadWndHei, FALSE);
BOOL bRet= ::UpdateLayeredWindow(m_hWnd, NULL, &ptDst, &WndSize, hMemDC,
&ptSrc, 0, &blendPixelFunction, ULW_ALPHA);
_ASSERT(bRet); // something was wrong....
// Delete used resources
SelectObject(hMemDC, hOriBmp);
DeleteObject(hbitmap);
DeleteDC(hMemDC);
void CShadowUI::MakeShadow(UINT32 *pShadBits, HWND hParent, RECT *rcParent)
// The shadow algorithm:
// Get the region of parent window,
// Apply morphologic erosion to shrink it into the size (ShadowWndSize - Sharpness)
// Apply modified (with blur effect) morphologic dilation to make the blurred border
// The algorithm is optimized by assuming parent window is just &one piece& and without &wholes& on it
// Get the region of parent window,
HRGN hParentRgn = CreateRectRgn(0, 0, 0, 0);
GetWindowRgn(hParent, hParentRgn);
// Determine the Start and end point of each horizontal scan line
SIZE szParent = {rcParent-&right - rcParent-&left, rcParent-&bottom - rcParent-&top};
SIZE szShadow = {szParent.cx + 2 * m_nSize, szParent.cy + 2 * m_nSize};
// Extra 2 lines (set to be empty) in ptAnchors are used in dilation
int nAnchors = max(szParent.cy, szShadow.cy); // # of anchor points pares
int (*ptAnchors)[2] = new int[nAnchors + 2][2];
int (*ptAnchorsOri)[2] = new int[szParent.cy][2]; // anchor points, will not modify during erosion
ptAnchors[0][0] = szParent.
ptAnchors[0][1] = 0;
ptAnchors[nAnchors + 1][0] = szParent.
ptAnchors[nAnchors + 1][1] = 0;
if(m_nSize & 0)
// Put the parent window anchors at the center
for(int i = 0; i & m_nS i++)
ptAnchors[i + 1][0] = szParent.
ptAnchors[i + 1][1] = 0;
ptAnchors[szShadow.cy - i][0] = szParent.
ptAnchors[szShadow.cy - i][1] = 0;
ptAnchors += m_nS
for(int i = 0; i & szParent. i++)
// find start point
for(j = 0; j & szParent. j++)
if(PtInRegion(hParentRgn, j, i))
ptAnchors[i + 1][0] = j + m_nS
ptAnchorsOri[i][0] =
if(j &= szParent.cx) // Start point not found
ptAnchors[i + 1][0] = szParent.
ptAnchorsOri[i][1] = 0;
ptAnchors[i + 1][0] = szParent.
ptAnchorsOri[i][1] = 0;
// find end point
for(j = szParent.cx - 1; j &= ptAnchors[i + 1][0]; j--)
if(PtInRegion(hParentRgn, j, i))
ptAnchors[i + 1][1] = j + 1 + m_nS
ptAnchorsOri[i][1] = j + 1;
if(m_nSize & 0)
ptAnchors -= m_nS // Restore pos of ptAnchors for erosion
int (*ptAnchorsTmp)[2] = new int[nAnchors + 2][2]; // Store the result of erosion
// First and last line should be empty
ptAnchorsTmp[0][0] = szParent.
ptAnchorsTmp[0][1] = 0;
ptAnchorsTmp[nAnchors + 1][0] = szParent.
ptAnchorsTmp[nAnchors + 1][1] = 0;
int nEroTimes = 0;
// morphologic erosion
for(int i = 0; i & m_nSharpness - m_nS i++)
nEroTimes++;
//ptAnchorsTmp[1][0] = szParent.
//ptAnchorsTmp[1][1] = 0;
//ptAnchorsTmp[szParent.cy + 1][0] = szParent.
//ptAnchorsTmp[szParent.cy + 1][1] = 0;
for(int j = 1; j & nAnchors + 1; j++)
ptAnchorsTmp[j][0] = max(ptAnchors[j - 1][0], max(ptAnchors[j][0], ptAnchors[j + 1][0])) + 1;
ptAnchorsTmp[j][1] = min(ptAnchors[j - 1][1], min(ptAnchors[j][1], ptAnchors[j + 1][1])) - 1;
// Exchange ptAnchors and ptAnchorsT
int (*ptAnchorsXange)[2] = ptAnchorsT
ptAnchorsTmp = ptA
ptAnchors = ptAnchorsX
// morphologic dilation
ptAnchors += (m_nSize & 0 ? -m_nSize : 0) + 1; // now coordinates in ptAnchors are same as in shadow window
// Generate the kernel
int nKernelSize = m_nSize & m_nSharpness ? m_nSize : m_nS
int nCenterSize = m_nSize & m_nSharpness ? (m_nSize - m_nSharpness) : 0;
UINT32 *pKernel = new UINT32[(2 * nKernelSize + 1) * (2 * nKernelSize + 1)];
UINT32 *pKernelIter = pK
for(int i = 0; i &= 2 * nKernelS i++)
for(int j = 0; j &= 2 * nKernelS j++)
double dLength = sqrt((i - nKernelSize) * (i - nKernelSize) + (j - nKernelSize) * (double)(j - nKernelSize));
if(dLength & nCenterSize)
*pKernelIter = m_nDarkness && 24 | PreMultiply(m_Color, m_nDarkness);
else if(dLength &= nKernelSize)
UINT32 nFactor = ((UINT32)((1 - (dLength - nCenterSize) / (m_nSharpness + 1)) * m_nDarkness));
*pKernelIter = nFactor && 24 | PreMultiply(m_Color, nFactor);
*pKernelIter = 0;
//TRACE(&%d &, *pKernelIter && 24);
pKernelIter ++;
//TRACE(&\n&);
// Generate blurred border
for(int i = nKernelS i & szShadow.cy - nKernelS i++)
if(ptAnchors[i][0] & ptAnchors[i][1])
// Start of line
for(j = ptAnchors[i][0];
j & min(max(ptAnchors[i - 1][0], ptAnchors[i + 1][0]) + 1, ptAnchors[i][1]);
for(int k = 0; k &= 2 * nKernelS k++)
UINT32 *pPixel = pShadBits +
(szShadow.cy - i - 1 + nKernelSize - k) * szShadow.cx + j - nKernelS
UINT32 *pKernelPixel = pKernel + k * (2 * nKernelSize + 1);
for(int l = 0; l &= 2 * nKernelS l++)
if(*pPixel & *pKernelPixel)
*pPixel = *pKernelP
pKernelPixel++;
} // for() start of line
// End of line
for(j = max(j, min(ptAnchors[i - 1][1], ptAnchors[i + 1][1]) - 1);
j & ptAnchors[i][1];
for(int k = 0; k &= 2 * nKernelS k++)
UINT32 *pPixel = pShadBits +
(szShadow.cy - i - 1 + nKernelSize - k) * szShadow.cx + j - nKernelS
UINT32 *pKernelPixel = pKernel + k * (2 * nKernelSize + 1);
for(int l = 0; l &= 2 * nKernelS l++)
if(*pPixel & *pKernelPixel)
*pPixel = *pKernelP
pKernelPixel++;
} // for() end of line
} // for() Generate blurred border
// Erase unwanted parts and complement missing
UINT32 clCenter = m_nDarkness && 24 | PreMultiply(m_Color, m_nDarkness);
for(int i = min(nKernelSize, max(m_nSize - m_nyOffset, 0));
i & max(szShadow.cy - nKernelSize, min(szParent.cy + m_nSize - m_nyOffset, szParent.cy + 2 * m_nSize));
UINT32 *pLine = pShadBits + (szShadow.cy - i - 1) * szShadow.
if(i - m_nSize + m_nyOffset & 0 || i - m_nSize + m_nyOffset &= szParent.cy) // Line is not covered by parent window
for(int j = ptAnchors[i][0]; j & ptAnchors[i][1]; j++)
*(pLine + j) = clC
for(int j = ptAnchors[i][0];
j & min(ptAnchorsOri[i - m_nSize + m_nyOffset][0] + m_nSize - m_nxOffset, ptAnchors[i][1]);
*(pLine + j) = clC
for(int j = max(ptAnchorsOri[i - m_nSize + m_nyOffset][0] + m_nSize - m_nxOffset, 0);
j & min(ptAnchorsOri[i - m_nSize + m_nyOffset][1] + m_nSize - m_nxOffset, szShadow.cx);
*(pLine + j) = 0;
for(int j = max(ptAnchorsOri[i - m_nSize + m_nyOffset][1] + m_nSize - m_nxOffset, ptAnchors[i][0]);
j & ptAnchors[i][1];
*(pLine + j) = clC
// Delete used resources
delete[] (ptAnchors - (m_nSize & 0 ? -m_nSize : 0) - 1);
delete[] ptAnchorsT
delete[] ptAnchorsO
delete[] pK
DeleteObject(hParentRgn);
void CShadowUI::ShowShadow(bool bShow)
m_bIsShowShadow = bS
bool CShadowUI::IsShowShadow() const
return m_bIsShowS
bool CShadowUI::SetSize(int NewSize)
if(NewSize & 20 || NewSize & -20)
m_nSize = (signed char)NewS
if(m_hWnd != NULL && (SS_VISABLE & m_Status))
Update(GetParent(m_hWnd));
bool CShadowUI::SetSharpness(unsigned int NewSharpness)
if(NewSharpness & 20)
m_nSharpness = (unsigned char)NewS
if(m_hWnd != NULL && (SS_VISABLE & m_Status))
Update(GetParent(m_hWnd));
bool CShadowUI::SetDarkness(unsigned int NewDarkness)
if(NewDarkness & 255)
m_nDarkness = (unsigned char)NewD
if(m_hWnd != NULL && (SS_VISABLE & m_Status))
Update(GetParent(m_hWnd));
bool CShadowUI::SetPosition(int NewXOffset, int NewYOffset)
if(NewXOffset & 20 || NewXOffset & -20 ||
NewYOffset & 20 || NewYOffset & -20)
m_nxOffset = (signed char)NewXO
m_nyOffset = (signed char)NewYO
if(m_hWnd != NULL && (SS_VISABLE & m_Status))
Update(GetParent(m_hWnd));
bool CShadowUI::SetColor(COLORREF NewColor)
m_Color = NewC
if(m_hWnd != NULL && (SS_VISABLE & m_Status))
Update(GetParent(m_hWnd));
bool CShadowUI::SetImage(LPCTSTR szImage)
if (szImage == NULL)
m_bIsImageMode =
m_sShadowImage = szI
if(m_hWnd != NULL && (SS_VISABLE & m_Status))
Update(GetParent(m_hWnd));
bool CShadowUI::SetShadowCorner(RECT rcCorner)
if (rcCorner.left & 0 || rcCorner.top & 0 || rcCorner.right & 0 || rcCorner.bottom & 0)
m_rcShadowCorner = rcC
if(m_hWnd != NULL && (SS_VISABLE & m_Status))
Update(GetParent(m_hWnd));
} //namespace DuiLib
& & & 这个阴影使用双层窗体实现的,可以避免duilib在半透明窗体上的不足。但也由于使用双层窗体,导致窗体大小快速改变时会看出阴影改变的延迟 ,能不能接受这个延迟就看个人了,所以如果是固定大小的窗体使用阴影效果最好。具体的效果可以看我的demo。
& & & 完整的修改版的库代码和阴影的demo,可以下载我的库:点击打开链接
相关搜索:
相关阅读:
相关频道:
&&&&&&&&&&&&&&&&
WEB编程教程最近更新}

我要回帖

更多关于 duilib异形窗口 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信