PNG button (按钮) files with transparency, for Visual C++ 6.0 and VS2005

PNG button (按钮) files with transparency, for Visual C++ 6.0 and VS2005
2008年11月06日 下午 02:47

要用到PNG格式的按钮,起初使用CxImage,发现有些地方实现起来很不方便。最终找到了他。

希望对大家有用。

Introduction

There are several owner draw buttons on this site, but I couldn’t find one that easily supports PNG files with transparency, so I created this class. Since this class uses GDI+, it actually supports many image formats, but the better quality buttons available are now PNG instead of ICO, so that is the one highlighted here.

Update: I have an extended version of this class in my Style Toolkit. If all you want is to use an image on a button, this class is probably simpler to use.

Background

GDI+ is part of the Microsoft Windows SDK, and it needs to be initialized when the application starts up. If you haven’t used GDI+ before, look at the source code of the demo project and read this article:

Features

  • Grayscale Image
    • The class will automatically create a grayscale image from the loaded resource. The grayscale image will be displayed when the button is set to the disabled state.
  • Highlight Image
    • The class will automatically create a highlighted image from the loaded resource. The highlighted image is displayed when the mouse hovers over the button boundaries.
  • Alternate Image
    • You can optionally add an alternate image. The alternate image will be displayed when set by a function call, or when the button is clicked and toggle mode is enabled.
  • Toggle Mode
    • When enabled, the button switches between the standard image and the alternate image each time it is pressed.
  • Pressed State
    • When the button is pressed, the image moves down and to the right 1 pixel.
  • Tool Tips
    • Tool tips can be optionally added.

The image at the top of the page shows the play button in three different states. From left to right, they are: normal, highlighted, and disabled. The other two buttons are just more examples that look cool.

It may not be too obvious from the picture that the second button is in the highlighted state. This is by design, I don’t want to significantly change the image. The highlight state just increases the brightness and the contrast a bit. It is obvious enough when you move the mouse over it. This class prioritizes image quality, so it will never stretch or shrink the image which usually degrades the quality, it will just paint the part that fits. If you need to resize your image, use an image editor like Photoshop.

The picture below shows the play button in the toggled state, and it should be obvious why you would want such a feature. The exit button is in the highlighted state, and the tool tip is shown in this image.

GdipButton2.PNG

There is no performance penalty for using GDI+ (assuming such a penalty exists) since it is only used during initialization. On creation, the images are converted to bitmaps, and the bitmaps are used when the control needs to be repainted.

Using the code

Step 1 – Add these files to your project

Step 2 – Add resources, member variables, and images

Add a button to your dialog box with the resource editor, set the resource ID, and erase the text in the Caption box. You could set the style to Owner Draw, but you don’t need to because the code will automatically set this style.

Using the Class Wizard, add a variable to the ID you just created. In this example, I set the ID to IDC_PLAY, and the variable name to m_cPlay. Edit the dialog’s .h file, and change the control from CButton to CGdiButton. Don’t forget to include the file “GdipButton.h”.

In the resource editor, import the .png file from the res folder and set the resource type to PNG. Using PNG is just convention, it can be anything as long as the code matches what you name it. Right click on IDR_PNG1, select Properties, and rename it to something useful, IDR_PLAY in this example.

Step 3 – Add the LoadStdImage() function

Now, we just need to load the image to the button on initialization. In the OnInitDialg() function, add the following code near the bottom:

BOOL CTestGdipButtonDlg::OnInitDialog()
{
    CDialog::OnInitDialog();

    /// a bunch of stuff here

    m_cPlay.LoadStdImage(IDR_PLAY, _T("PNG"));

    return TRUE; 
}

Step 4 – Build and Run

You should be able to run it now and see your new PNG button! If it crashes here, it is probably because you didn’t initialize GDI+. Review the Background section of this article.

The demo project

This is all the code that is needed to create the buttons in the test program:

// load the standard image and alternate image
m_cPlay.LoadStdImage(IDR_PLAY, _T("PNG"));
m_cPlay.LoadAltImage(IDR_PAUSE, _T("PNG"));
m_cPlay.EnableToggle(TRUE);

// just to show highlight state for article
m_cPlayHi.LoadStdImage(IDR_PLAY, _T("PNG"));

// set as disabled
m_cPlayDis.LoadStdImage(IDR_PLAY, _T("PNG"));
m_cPlayDis.EnableButton(FALSE);

// show a larger button type
m_cGear.LoadStdImage(IDR_GEAR, _T("PNG"));

// replace the OK button with something
m_cShutDn.LoadStdImage(IDR_EXIT, _T("PNG"));
m_cShutDn.SetToolTipText(_T("Close Program"));

Both the VC6 and VS2005 versions are included in the demo project.

Issues with transparent images

The button control has no idea what the background beneath it should be; it gets this information from the bitmap associated with the current DC. In most cases, this works fine, the background is what is on the screen just before the control is painted. However, the application may not be the top most when it is launched. An always on top application like Task Manager may be in the way, so when it gets the background image, it is the wrong data. This is overcome by calling the SetBkGnd() function by the code that actually creates the background.

Set all the button backgrounds in the parent’s OnEraseBkgnd() function. The demo program does this with the following code:

BOOL CTestGdipButtonDlg::OnEraseBkgnd(CDC* pDC)
{
    CDialog::OnEraseBkgnd(pDC);
    CRect rect;
    GetClientRect(rect);

    CMemDC pDevC(pDC, rect);

    // fill in the back ground with something

    SetButtonBackGrounds(pDevC);

    return TRUE;
}

void CTestGdipButtonDlg::SetButtonBackGrounds(CDC *pDC)
{
    m_cPlay.SetBkGnd(pDC);
    m_cPlayHi.SetBkGnd(pDC);
    m_cPlayDis.SetBkGnd(pDC);
    m_cShutDn.SetBkGnd(pDC);
}

Since the DC that is passed is a memory DC, it doesn’t matter what other applications might be doing. The code above assumes you want to use something other than the crummy default background; otherwise, you would probably just use the default crummy button.

VC6 Build issues

VC6 needs a few extra things to compile correctly, add the following to your stdafx.h file. Also add the SDK include and lib paths to your environment.

// VC6
#if defined(_MSC_VER) && _MSC_VER == 1200

#ifndef ULONG_PTR
#define ULONG_PTR unsigned long*
#endif
#include <Specstrings.h>
#include <gdiplus.h>

#pragma comment(lib, "gdiplus.lib")
using namespace Gdiplus;

// VS2005
#else 

#include <gdiplus.h>
#pragma comment(lib, "gdiplus.lib")
using namespace Gdiplus;

#endif
发现问题:
拿到该程序后,发现原始代码为了显示效果流畅,所需图片已经全部加载到了资源当中。而我需要的是从本地进行读取并显示。在CGdipButton类中怎加了如下函数:
//==============================================================================
BOOL CGdipButton::SetImage(LPCTSTR pFilePath)
//==============================================================================
{
m_pStdImage = new CGdiPlusBitmapResource;
bool bIsSuccess = m_pStdImage->LoadFromFile(pFilePath);
CRect rect;
//GetClientRect(rect);
GetWindowRect(rect);
float width = (float)m_pStdImage->m_pBitmap->GetWidth();
float height = (float)m_pStdImage->m_pBitmap->GetHeight();
MoveWindow(rect.left, rect.top, width, height);//
return bIsSuccess;
}
对CGdiPlusBitmap也进行了修改:
class CGdiPlusBitmap
{
public:
Gdiplus::Bitmap* m_pBitmap;
public:
CGdiPlusBitmap()        { m_pBitmap = NULL; }
CGdiPlusBitmap(LPCWSTR pFile)     { m_pBitmap = NULL; LoadFromFile(pFile); }
virtual ~CGdiPlusBitmap()      { Empty(); }
 void Empty()          { delete m_pBitmap; m_pBitmap = NULL; }
 operator Gdiplus::Bitmap*() const     { return m_pBitmap; }
bool LoadFromFile(LPCWSTR pFile)
{
   Empty();
   m_pBitmap = Gdiplus::Bitmap::FromFile(pFile);
   return m_pBitmap->GetLastStatus() == Gdiplus::Ok;;
}
};
对于从文件中读取的图像格式,在显示上有问题。需要对CGdipButton::CtlColor(CDC* pScreenDC, UINT nCtlColor)函数部分位置进行修正。
//=============================================================================
//
// The framework calls this member function when a child control is about to
// be drawn. All the bitmaps are created here on the first call. Every thing
// is done with a memory DC except the background, which get's it's information
// from the parent. The background is needed for transparent portions of PNG
// images. An always on top app (such as Task Manager) that is in the way can
// cause it to get an incorrect background. To avoid this, the parent should
// call the SetBkGnd function with a memory DC when it creates the background.
//    
//=============================================================================
HBRUSH CGdipButton::CtlColor(CDC* pScreenDC, UINT nCtlColor)
{
if(!m_bHaveBitmaps)
{
   if(!m_pStdImage)
   {
    return NULL; // Load the standard image with LoadStdImage()
   }
   CBitmap bmp, *pOldBitmap;
   CRect rect;
   GetClientRect(rect);
   // do everything with mem dc
   CMemDC pDC(pScreenDC, rect);
   Gdiplus::Graphics graphics(pDC->m_hDC);
   // background
   if (m_dcBk.m_hDC == NULL)
   {
    CRect rect1;
    CClientDC clDC(GetParent());
    GetWindowRect(rect1);
    GetParent()->ScreenToClient(rect1);
    m_dcBk.CreateCompatibleDC(&clDC);
    bmp.CreateCompatibleBitmap(&clDC, rect.Width(), rect.Height());
    pOldBitmap = m_dcBk.SelectObject(&bmp);
    m_dcBk.BitBlt(0, 0, rect.Width(), rect.Height(), &clDC, rect1.left, rect1.top, SRCCOPY);
    bmp.DeleteObject();
   }
   // standard image
   if (m_dcStd.m_hDC == NULL)
   {
    PaintBk(pDC);
    graphics.DrawImage(*m_pStdImage, 0, 0);
  
    m_dcStd.CreateCompatibleDC(pDC);
    bmp.CreateCompatibleBitmap(pDC, rect.Width(), rect.Height());
    pOldBitmap = m_dcStd.SelectObject(&bmp);
    m_dcStd.BitBlt(0, 0, rect.Width(), rect.Height(), pDC, 0, 0, SRCCOPY);
    bmp.DeleteObject();
    // standard image pressed
    if (m_dcStdP.m_hDC == NULL)
    {
     PaintBk(pDC);
    
      ColorMatrix GrayMat = { 0.30f, 0.30f, 0.30f, 0.00f, 0.00f,
           0.59f, 0.59f, 0.59f, 0.00f, 0.00f,
           0.11f, 0.11f, 0.11f, 0.00f, 0.00f,
           0.00f, 0.00f, 0.00f, 1.00f, 0.00f,
           0.00f, 0.00f, 0.00f, 0.00f, 1.00f };
     
     /*
     ColorMatrix GrayMat = { 0.30f, 0.30f, 0.30f, 0.00f, 0.00f,
          0.30f, 0.30f, 0.30f, 0.00f, 0.00f,
          0.11f, 0.11f, 0.11f, 0.00f, 0.00f,
          0.00f, 0.00f, 0.00f, 1.00f, 0.00f,
          0.00f, 0.00f, 0.00f, 0.00f, 1.00f };*/
    
     ImageAttributes ia;
     ia.SetColorMatrix(&GrayMat);
     float width = (float)m_pStdImage->m_pBitmap->GetWidth();
     float height = (float)m_pStdImage->m_pBitmap->GetHeight();
     RectF grect; grect.X=0, grect.Y=0; grect.Width = width; grect.Height = height;
     graphics.DrawImage(*m_pStdImage, grect, 0, 0, width, height, UnitPixel, &ia);
     m_dcStdP.CreateCompatibleDC(pDC);
     bmp.CreateCompatibleBitmap(pDC, rect.Width(), rect.Height());
     pOldBitmap = m_dcStdP.SelectObject(&bmp);
     m_dcStdP.BitBlt(0, 0, rect.Width(), rect.Height(), pDC, 0, 0, SRCCOPY);
     bmp.DeleteObject();
    }
    // standard image hot
    if(m_dcStdH.m_hDC == NULL)
    {
     PaintBk(pDC);
     ColorMatrix HotMat = { 1.05f, 0.00f, 0.00f, 0.00f, 0.00f,
           0.00f, 1.05f, 0.00f, 0.00f, 0.00f,
           0.00f, 0.00f, 1.05f, 0.00f, 0.00f,
           0.00f, 0.00f, 0.00f, 1.00f, 0.00f,
           0.05f, 0.05f, 0.05f, 0.00f, 1.00f };
     ImageAttributes ia;
     ia.SetColorMatrix(&HotMat);
     float width = (float)m_pStdImage->m_pBitmap->GetWidth();
     float height = (float)m_pStdImage->m_pBitmap->GetHeight();
     RectF grect; grect.X=0, grect.Y=0; grect.Width = width; grect.Height = height;
     graphics.DrawImage(*m_pStdImage, grect, 0, 0, width, height, UnitPixel, &ia);
     m_dcStdH.CreateCompatibleDC(pDC);
     bmp.CreateCompatibleBitmap(pDC, rect.Width(), rect.Height());
     pOldBitmap = m_dcStdH.SelectObject(&bmp);
     m_dcStdH.BitBlt(0, 0, rect.Width(), rect.Height(), pDC, 0, 0, SRCCOPY);
     bmp.DeleteObject();
    }
    // grayscale image
    if(m_dcGS.m_hDC == NULL)
    {
     PaintBk(pDC);
     ColorMatrix GrayMat = { 0.30f, 0.30f, 0.30f, 0.00f, 0.00f,
           0.59f, 0.59f, 0.59f, 0.00f, 0.00f,
           0.11f, 0.11f, 0.11f, 0.00f, 0.00f,
           0.00f, 0.00f, 0.00f, 1.00f, 0.00f,
           0.00f, 0.00f, 0.00f, 0.00f, 1.00f };
     ImageAttributes ia;
     ia.SetColorMatrix(&GrayMat);
     float width = (float)m_pStdImage->m_pBitmap->GetWidth();
     float height = (float)m_pStdImage->m_pBitmap->GetHeight();
     RectF grect; grect.X=0, grect.Y=0; grect.Width = width; grect.Height = height;
     graphics.DrawImage(*m_pStdImage, grect, 0, 0, width, height, UnitPixel, &ia);
     m_dcGS.CreateCompatibleDC(pDC);
     bmp.CreateCompatibleBitmap(pDC, rect.Width(), rect.Height());
     pOldBitmap = m_dcGS.SelectObject(&bmp);
     m_dcGS.BitBlt(0, 0, rect.Width(), rect.Height(), pDC, 0, 0, SRCCOPY);
     bmp.DeleteObject();
    }
   }
   // alternate image
   if( (m_dcAlt.m_hDC == NULL) && m_bHaveAltImage )
   {
    PaintBk(pDC);
    graphics.DrawImage(*m_pAltImage, 0, 0);
  
    m_dcAlt.CreateCompatibleDC(pDC);
    bmp.CreateCompatibleBitmap(pDC, rect.Width(), rect.Height());
    pOldBitmap = m_dcAlt.SelectObject(&bmp);
    m_dcAlt.BitBlt(0, 0, rect.Width(), rect.Height(), pDC, 0, 0, SRCCOPY);
    bmp.DeleteObject();
    // alternate image pressed
    if( (m_dcAltP.m_hDC == NULL) && m_bHaveAltImage )
    {
     PaintBk(pDC);
     graphics.DrawImage(*m_pAltImage, 1, 1);
   
     m_dcAltP.CreateCompatibleDC(pDC);
     bmp.CreateCompatibleBitmap(pDC, rect.Width(), rect.Height());
     pOldBitmap = m_dcAltP.SelectObject(&bmp);
     m_dcAltP.BitBlt(0, 0, rect.Width(), rect.Height(), pDC, 0, 0, SRCCOPY);
     bmp.DeleteObject();
    }
    // alternate image hot
    if(m_dcAltH.m_hDC == NULL)
    {
     PaintBk(pDC);
     ColorMatrix HotMat = { 1.05f, 0.00f, 0.00f, 0.00f, 0.00f,
           0.00f, 1.05f, 0.00f, 0.00f, 0.00f,
           0.00f, 0.00f, 1.05f, 0.00f, 0.00f,
           0.00f, 0.00f, 0.00f, 1.00f, 0.00f,
           0.05f, 0.05f, 0.05f, 0.00f, 1.00f };
     ImageAttributes ia;
     ia.SetColorMatrix(&HotMat);
     float width = (float)m_pStdImage->m_pBitmap->GetWidth();
     float height = (float)m_pStdImage->m_pBitmap->GetHeight();
     RectF grect; grect.X=0, grect.Y=0; grect.Width = width; grect.Height = height;
     graphics.DrawImage(*m_pAltImage, grect, 0, 0, width, height, UnitPixel, &ia);
     m_dcAltH.CreateCompatibleDC(pDC);
     bmp.CreateCompatibleBitmap(pDC, rect.Width(), rect.Height());
     pOldBitmap = m_dcAltH.SelectObject(&bmp);
     m_dcAltH.BitBlt(0, 0, rect.Width(), rect.Height(), pDC, 0, 0, SRCCOPY);
     bmp.DeleteObject();
    }
   }
   if(m_pCurBtn == NULL)
   {
    m_pCurBtn = &m_dcStd;
   }
   m_bHaveBitmaps = TRUE;
}
 return NULL;
}
原文地址:https://www.cnblogs.com/kevinzhwl/p/3878948.html