什么都不说,先上效果
图中是2个按钮,它们初始化时都是像右边那个样子,当鼠标多次滑过按钮后(hover变色),按钮就变成左边那个样子了。和明显锯齿很严重。我猜想是多次使用抗锯齿重绘,导致边缘那几个像素的alpha值越来越小,最后就看不见了。不知道我猜想是否正确,请问各位这种情况如何解决。代码(方便各位测试,贴了button的实现函数):
void CFlatButton::OnMouseMove(UINT nFlags, CPoint point)
{
//Active message WM_MOUSEHOVER and WM_MOUSELEAVE
if(!m_bMouseTracking)
{
TRACKMOUSEEVENT tme;
tme.cbSize = sizeof(TRACKMOUSEEVENT);
tme.dwFlags = TME_HOVER | TME_LEAVE;
tme.dwHoverTime = 10;
tme.hwndTrack = m_hWnd;
m_bMouseTracking = _TrackMouseEvent(&tme);
} CButton::OnMouseMove(nFlags, point);
}
void CFlatButton::OnMouseLeave()
{
m_bMouseTracking = false;
m_bMouseHover = false;
Invalidate(FALSE);
}
void CFlatButton::OnMouseHover(UINT nFlags, CPoint point)
{
m_bMouseHover = true;
Invalidate(FALSE);
}/*
cx,cy 圆角椭圆的包含矩形宽与高
*/
GraphicsPath* MakeRoundRectPath(GraphicsPath* path,
int x,int y,int width,int height,
int cx,int cy)
{
path->AddArc(x,y,cx,cy,180,90); //左上
path->AddArc(x+width-cx,y,cx,cy,270,90); //右上
path->AddArc(x+width-cx,y+height-cy,cx,cy,0,90); //右下
path->AddArc(x,y+height-cy,cx,cy,90,90); //左下 path->AddLine(x,y+cy/2,x,y+height-cy/2); return path;
}void CFlatButton::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
{
UINT state = lpDrawItemStruct->itemState;
CRect rt(lpDrawItemStruct->rcItem); Bitmap membmp(rt.Width(),rt.Height(),PixelFormat32bppARGB);
Graphics g(&membmp);
g.SetSmoothingMode(SmoothingModeAntiAlias);
Rect rc(rt.left,rt.top,rt.Width(),rt.Height()); GraphicsPath path;
MakeRoundRectPath(&path,rc.X,rc.Y,rc.Width-1,rc.Height-1,20,20); //Draw background
rc.Inflate(-GetSystemMetrics(SM_CXEDGE),-GetSystemMetrics(SM_CYEDGE));
if(m_bMouseHover)
g.FillPath(m_brushHoverBg,&path);
else
g.FillPath(m_brushBg,&path); //Draw caption
wchar_t title[128];
wmemset(title,0,128);
GetWindowTextW(m_hWnd,title,128); FontFamily fontFamily(L"微软雅黑");
Font font(&fontFamily, 15, FontStyleRegular, UnitPixel);
SolidBrush fontBrush(Color(255,255,0,0));
RectF boundRect;
g.MeasureString(title,wcslen(title),&font,PointF(0.0f,0.0f),&boundRect);
g.DrawString(title,-1,&font,PointF(REAL(rt.Width()-boundRect.Width)/2,REAL(rt.Height()-boundRect.Height)/2),&fontBrush); Graphics gdc(lpDrawItemStruct->hDC);
CachedBitmap cachedBmp(&membmp,&gdc);
gdc.DrawCachedBitmap(&cachedBmp,0,0);
}
void CFlatButton::PreSubclassWindow()
{
m_penBorder = new Gdiplus::Pen(Color(200,230,210),1.0f);
m_penHoverBorder = new Gdiplus::Pen(Color(0,255,0),1.0f);
m_brushBg = new Gdiplus::SolidBrush(Color(80,80,80));
m_brushHoverBg = new Gdiplus::SolidBrush(Color(20,20,20));
m_bMouseHover = m_bMouseTracking = FALSE; ModifyStyle(0,BS_OWNERDRAW);
CButton::PreSubclassWindow();
}
void CFlatButton::OnDestroy()
{
delete m_penBorder;
delete m_penHoverBorder;
delete m_brushBg;
delete m_brushHoverBg; CButton::OnDestroy();
}
图中是2个按钮,它们初始化时都是像右边那个样子,当鼠标多次滑过按钮后(hover变色),按钮就变成左边那个样子了。和明显锯齿很严重。我猜想是多次使用抗锯齿重绘,导致边缘那几个像素的alpha值越来越小,最后就看不见了。不知道我猜想是否正确,请问各位这种情况如何解决。代码(方便各位测试,贴了button的实现函数):
void CFlatButton::OnMouseMove(UINT nFlags, CPoint point)
{
//Active message WM_MOUSEHOVER and WM_MOUSELEAVE
if(!m_bMouseTracking)
{
TRACKMOUSEEVENT tme;
tme.cbSize = sizeof(TRACKMOUSEEVENT);
tme.dwFlags = TME_HOVER | TME_LEAVE;
tme.dwHoverTime = 10;
tme.hwndTrack = m_hWnd;
m_bMouseTracking = _TrackMouseEvent(&tme);
} CButton::OnMouseMove(nFlags, point);
}
void CFlatButton::OnMouseLeave()
{
m_bMouseTracking = false;
m_bMouseHover = false;
Invalidate(FALSE);
}
void CFlatButton::OnMouseHover(UINT nFlags, CPoint point)
{
m_bMouseHover = true;
Invalidate(FALSE);
}/*
cx,cy 圆角椭圆的包含矩形宽与高
*/
GraphicsPath* MakeRoundRectPath(GraphicsPath* path,
int x,int y,int width,int height,
int cx,int cy)
{
path->AddArc(x,y,cx,cy,180,90); //左上
path->AddArc(x+width-cx,y,cx,cy,270,90); //右上
path->AddArc(x+width-cx,y+height-cy,cx,cy,0,90); //右下
path->AddArc(x,y+height-cy,cx,cy,90,90); //左下 path->AddLine(x,y+cy/2,x,y+height-cy/2); return path;
}void CFlatButton::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
{
UINT state = lpDrawItemStruct->itemState;
CRect rt(lpDrawItemStruct->rcItem); Bitmap membmp(rt.Width(),rt.Height(),PixelFormat32bppARGB);
Graphics g(&membmp);
g.SetSmoothingMode(SmoothingModeAntiAlias);
Rect rc(rt.left,rt.top,rt.Width(),rt.Height()); GraphicsPath path;
MakeRoundRectPath(&path,rc.X,rc.Y,rc.Width-1,rc.Height-1,20,20); //Draw background
rc.Inflate(-GetSystemMetrics(SM_CXEDGE),-GetSystemMetrics(SM_CYEDGE));
if(m_bMouseHover)
g.FillPath(m_brushHoverBg,&path);
else
g.FillPath(m_brushBg,&path); //Draw caption
wchar_t title[128];
wmemset(title,0,128);
GetWindowTextW(m_hWnd,title,128); FontFamily fontFamily(L"微软雅黑");
Font font(&fontFamily, 15, FontStyleRegular, UnitPixel);
SolidBrush fontBrush(Color(255,255,0,0));
RectF boundRect;
g.MeasureString(title,wcslen(title),&font,PointF(0.0f,0.0f),&boundRect);
g.DrawString(title,-1,&font,PointF(REAL(rt.Width()-boundRect.Width)/2,REAL(rt.Height()-boundRect.Height)/2),&fontBrush); Graphics gdc(lpDrawItemStruct->hDC);
CachedBitmap cachedBmp(&membmp,&gdc);
gdc.DrawCachedBitmap(&cachedBmp,0,0);
}
void CFlatButton::PreSubclassWindow()
{
m_penBorder = new Gdiplus::Pen(Color(200,230,210),1.0f);
m_penHoverBorder = new Gdiplus::Pen(Color(0,255,0),1.0f);
m_brushBg = new Gdiplus::SolidBrush(Color(80,80,80));
m_brushHoverBg = new Gdiplus::SolidBrush(Color(20,20,20));
m_bMouseHover = m_bMouseTracking = FALSE; ModifyStyle(0,BS_OWNERDRAW);
CButton::PreSubclassWindow();
}
void CFlatButton::OnDestroy()
{
delete m_penBorder;
delete m_penHoverBorder;
delete m_brushBg;
delete m_brushHoverBg; CButton::OnDestroy();
}
各种状态改变时要确保实心背景被填充,在同一地方画多次透明色会导致颜色加深。
另外擦背景直接返回。
你的这种情况也许和我是一种原因造成的,但是我不希望在一个BUTTON控件自绘里还要处理父窗口的重画,而且在看了你的建议后我曾尝试过,似乎会导致一个循环调用发生。我原先的一种实验方式是在button得自绘函数里Invalidate(),这种方式可以消除边缘的锯齿,但是快速的滑进滑出,会看到按钮可能发生闪烁。to j8daxue:
你说的这种只是实现按钮全部区域绘制,如果后面的操作不能将边缘透明化,其实对我而言并无实际用处。之前我也使用了你的方式整体重画背景,但之后却想不出好办法是边缘透明(我要做的是一个圆角),如果你有办法处理这一后续情况,我很期望你的回复。to pshchao:
你说的这个似乎和leeihcy的说法截然相反,具体情况我也说不清,还没有使用图片绘制过按钮。
前面我说到,让按钮自己去重绘下它的背景,可以消除这种情况,但是我不明白,按钮时如何去重画它的背景的? 似乎它也没有填充任何的背景颜色。
你说的异性窗口应该是像那些游戏Loader类似的窗口吧。我刚刚试了下,代码如下(button所在对话框的OnInitdialog中): CFlatButton* btn = (CFlatButton*)GetDlgItem(IDC_BUTTON1);
CRect rt;
btn->GetClientRect(&rt); //MSDN所rgn左边是以窗口左上为原点的,我这里近似下
HRGN hRegin = CreateRoundRectRgn(rt.left,rt.top,rt.right/2,rt.bottom,20,20); //一半宽度
btn->SetWindowRgn(hRegin,TRUE);
这样子运行后,并没有对圆角处多次刷新出现毛边的问题有任何改善。不过却有个新问题(与此贴讨论无关),LPDRAWITEMSTRUCT结构中rcItem似乎并不知道该窗口已经被SetWindowRgn重新设置了显示区域了,依然是button的客户大小。MSDN里对SetWindowRng有如下描述: The system does not display any portion of a window that lies outside of the window region。按理不会重画到rgn外面的吧。不解。
怎么也不会去在自绘函数里调用Invalidate吧,这肯定会有问题的。我的刷新代码是这样写的://
// 刷新自己.如果需要透明效果的话,则需要刷新父窗口
//
void CMCButton2::Refresh()
{
if( m_bFlagIsBkTransparent )
{
this->InvalidateRect( NULL );
//UpdateWindow(); HWND hWndScreen = ::GetDesktopWindow();
HWND hWndParent = GetParent();
RECT rc;
this->GetWindowRect( &rc );
::MapWindowPoints( hWndScreen, hWndParent,(LPPOINT)&rc, 2 );
::InvalidateRect( hWndParent, &rc, TRUE );
::UpdateWindow(hWndParent);
}
else
{
this->InvalidateRect( NULL );
UpdateWindow();
}然后是在各种 鼠标事件、focus事件、enable事件中调用 Refresh();
在我的效果中并没有很多大的闪烁。虽然我也觉得这不是最好的解决办法,有更好的方法可以讨论。
现有几种方法,
父窗口背景是实心色
可以直接填充实心色。父窗口背景是图
a.对话框继承于一种带有获得图片路径的类。按钮绘制背景时获得这个图片所在区域作为底图。
b.CButtonST的做法,拷贝父窗口背景DC作为按钮背景。具体参考CButtonST的做法。里面SetBk很重要。
为了准确我专门做了下实验,使用spy++确认用鼠标对button做操作,不论选择,点击,悬停,还是窗口被其他window覆盖,都不会触发button的WM_ERASEBKGND消息。就是说全程都没有重画背景。这也解释了我之前将锯齿化的按钮拖出屏幕后,它们的圆角又恢复到原先的平滑状态了,因为在窗口脱出显示屏幕时会触发WM_ERASEBKGND消息,按钮背景得以重画。我看明白你的意思,关键在于你强制了背景的重画。前面的帖子我可能回复不当,说是直接使用Invalidate,其实我是发送WM_ERASEBKGND消息,跟你的代码大致意思一样的。//绘制时,强制绘制背景
SendMessage(WM_ERASEBKGND,WPARAM(lpDrawItemStruct->hDC),0);
button如果不是很大,闪动不是很明显,不过button大一些就很容易让人注意到了。
而且如果强调透明最好用directui的思路直接绘制
有句柄的东西比较难弄
刚把CButtonST 3.9 的代码下下来跑了一会,的确现在它好像已经解决那些问题了。(我是几年前测试的)CButtonST在父窗口的 WM_ERASEBKGND 消息中做了处理,在父窗口刚刚刷完背景后,就获取它的dc,保存这个时候的bitmap,用这个bitmap作为按钮的背景图来刷。我也试了试,在别的地方去获取父窗口的dc bitmap,这个bitmap中是会包含子控件信息的。
异型窗口我也是第一次用,很多地方还是不甚了解,directui没有接触过,只知道是直接绘制,不需要句柄。
异型窗口我还是见用于主窗口的比较多,背景色也是需要事先约定好。但圆角边缘还是没有抗锯齿处理,就像XP的标题栏一样,虽然大体上看是圆角的,其实锯齿还是严重,刚刚仔细看了下win7的窗口也是如此。========================================================================
leeihcy在#15中说到的情况,我另找了一个MFC程序看了看,确实发送了该消息。后来才发现,这是因为自绘样式的原因。我将目前使用的自绘按钮取消的后,它就能接收到WM_ERASEBKGND消息了,如果改回去,消息就不发送了,还是觉得应该是要自己去发送绘制背景消息的。========================================================================
经过几小时的测试,现在我明白j8daxue在#11楼说的2种方式了。
现在就3种(我意识到的)可能情况:
1、窗口背景是单色,也就是j8daxue提到的父窗口背景是实心色。
这种情形正是我这个问题中遇到的情形,窗口背景与按钮背景相同,其实按钮背景也是单色的,看起来像透明的一样,只不过是种错觉。所以正确的方式还是取得父窗口的背景颜色,然后在按钮的绘制过程中使用这种颜色来填充背景,问题得以解决。
2、背景是图片。这个还没做过,我也参考下CButtonST的好了。
3、背景动态变化的。
这个虽然有些苛刻,也很少见,但是讨论下还是无妨。WIN7下MFC对话框按钮在获得焦点时其颜色会在淡蓝色周围浮动变化,看起来很有动感,在SPY++下查看,原来是Timer与WM_PAINT的结合。它给出了一种可能的解决方法,就是添加timer和wm_paint,使得固定时间据去重新绘制下button,背景使用最新的绘制。其实主窗口如果实现背景动态变化,可能也需要一个timer,这2个timer也许可以合成一个共享使用。感谢各位的回复!
http://www.codeproject.com/KB/buttons/GdipButton.aspx这里面的透明方法是和CButtonST一样。所以我也决定修改我的代码,让闪烁见鬼去吧!