什么都不说,先上效果
图中是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();
}

解决方案 »

  1.   

    我用PNG做按钮的时候,鼠标多经过几次,旁边的阴影也会加重,原因我也不知道不过如果试试将  Invalidate(FALSE); 改成刷新父窗口的该按钮处位置,情况也许会好些吧因为这个按钮是不规则的
      

  2.   

    Bitmap membmp(rt.Width(),rt.Height(),PixelFormat32bppARGB);这个缓冲图背景要填充实心,不能有透明色。
    各种状态改变时要确保实心背景被填充,在同一地方画多次透明色会导致颜色加深。
    另外擦背景直接返回。
      

  3.   

    用png做按钮,按钮产生事件就要就要刷新按钮,就不会有阴影加重了
      

  4.   

    to leeihcy:
     你的这种情况也许和我是一种原因造成的,但是我不希望在一个BUTTON控件自绘里还要处理父窗口的重画,而且在看了你的建议后我曾尝试过,似乎会导致一个循环调用发生。我原先的一种实验方式是在button得自绘函数里Invalidate(),这种方式可以消除边缘的锯齿,但是快速的滑进滑出,会看到按钮可能发生闪烁。to j8daxue:
    你说的这种只是实现按钮全部区域绘制,如果后面的操作不能将边缘透明化,其实对我而言并无实际用处。之前我也使用了你的方式整体重画背景,但之后却想不出好办法是边缘透明(我要做的是一个圆角),如果你有办法处理这一后续情况,我很期望你的回复。to pshchao:
    你说的这个似乎和leeihcy的说法截然相反,具体情况我也说不清,还没有使用图片绘制过按钮。
    前面我说到,让按钮自己去重绘下它的背景,可以消除这种情况,但是我不明白,按钮时如何去重画它的背景的? 似乎它也没有填充任何的背景颜色。
      

  5.   

    to xianglitian:
    你说的异性窗口应该是像那些游戏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外面的吧。不解。
      

  6.   


    怎么也不会去在自绘函数里调用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();
    在我的效果中并没有很多大的闪烁。虽然我也觉得这不是最好的解决办法,有更好的方法可以讨论。
      

  7.   

    所谓透明只是让按钮圆角区域不覆盖父窗口背景而已。
    现有几种方法,
    父窗口背景是实心色
    可以直接填充实心色。父窗口背景是图
    a.对话框继承于一种带有获得图片路径的类。按钮绘制背景时获得这个图片所在区域作为底图。
    b.CButtonST的做法,拷贝父窗口背景DC作为按钮背景。具体参考CButtonST的做法。里面SetBk很重要。
      

  8.   

    CButtonST的做法让我一直觉得不爽,它的这种方法有缺陷,也有问题。1. 如果父窗口的背景是变化的,那么按钮上的背景不会随着刷新2. 拷贝父窗口的背景时机问题,我就在CButtonST上测试出它存在这个问题。在窗口刚创建的时候,不停的去用atl+tab切换窗口,有几率你拷贝到的父窗口背景完全不正确,很可能就是屏幕上的背景。
      

  9.   

    to leeihcy(#10):
    为了准确我专门做了下实验,使用spy++确认用鼠标对button做操作,不论选择,点击,悬停,还是窗口被其他window覆盖,都不会触发button的WM_ERASEBKGND消息。就是说全程都没有重画背景。这也解释了我之前将锯齿化的按钮拖出屏幕后,它们的圆角又恢复到原先的平滑状态了,因为在窗口脱出显示屏幕时会触发WM_ERASEBKGND消息,按钮背景得以重画。我看明白你的意思,关键在于你强制了背景的重画。前面的帖子我可能回复不当,说是直接使用Invalidate,其实我是发送WM_ERASEBKGND消息,跟你的代码大致意思一样的。//绘制时,强制绘制背景
    SendMessage(WM_ERASEBKGND,WPARAM(lpDrawItemStruct->hDC),0);
    button如果不是很大,闪动不是很明显,不过button大一些就很容易让人注意到了。
      

  10.   

    不知道你仔细读过CButtonST没?里面有个SetBk函数
      

  11.   

    都不会触发button的WM_ERASEBKGND消息.....这里就不知道你哪弄错了吧,我的代码里WM_ERASEBKGND可是会响应的!
      

  12.   

    用异型窗口的目的是可以加背景色啊
    而且如果强调透明最好用directui的思路直接绘制
    有句柄的东西比较难弄
      

  13.   


    刚把CButtonST 3.9 的代码下下来跑了一会,的确现在它好像已经解决那些问题了。(我是几年前测试的)CButtonST在父窗口的 WM_ERASEBKGND 消息中做了处理,在父窗口刚刚刷完背景后,就获取它的dc,保存这个时候的bitmap,用这个bitmap作为按钮的背景图来刷。我也试了试,在别的地方去获取父窗口的dc bitmap,这个bitmap中是会包含子控件信息的。
      

  14.   


    异型窗口我也是第一次用,很多地方还是不甚了解,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也许可以合成一个共享使用。感谢各位的回复!
      

  15.   

    要抗锯齿的最好的还是用png图片吧...而且自带透明、阴影效果。代码画圆角很难看在CODEPROJECT上转了转,找到另外一篇,
    http://www.codeproject.com/KB/buttons/GdipButton.aspx这里面的透明方法是和CButtonST一样。所以我也决定修改我的代码,让闪烁见鬼去吧!