本文以一個基於對話框的時鐘程式為例,示範在 VC6.0 環境下如何使用 GDI+ 載入帶透明通道的 PNG 圖片,實現半透明漸變視窗效果。程式支援指針式與數字式雙模式顯示,並具備視窗拖動無矩形框、隱藏工作列按鈕等特性。

圖一:程式執行效果與 Windows XP 桌面融合
使用 Photoshop 製作時鐘背景、時針、分針、秒針及數字顯示所需圖片,儲存為帶 Alpha 通道的 PNG 格式,確保 GDI+ 繪製時背景透明。
C:\GDI+\Includes
C:\GDI+\Lib
C:\GDI+\gdiplus.dll
#define UNICODE
#ifndef ULONG_PTR
#define ULONG_PTR unsigned long*
#endif
#include "c:\gdi+\includes\gdiplus.h"
using namespace Gdiplus;
#pragma comment(lib, "c:\\gdi+\\lib\\gdiplus.lib")
GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
// ... 程式結束前
GdiplusShutdown(gdiplusToken);
Image *m_pImageClock; // 背景圖
Image *m_pImageClock1; // 替換背景圖(閃爍用)
Image *m_pImageHHour; // 時針
Image *m_pImageHMinu; // 分針
Image *m_pImageHSec; // 秒針
Image *m_pImageNum; // 數字圖片
int m_BakWidth, m_BakHeight;
int m_HourWidth, m_HourHeight;
int m_MinuWidth, m_MinuHeight;
int m_SecWidth, m_SecHeight;
HINSTANCE hFuncInst;
typedef BOOL (WINAPI* MYFUNC)(HWND, HDC, POINT*, SIZE*, HDC, POINT*, COLORREF, BLENDFUNCTION*, DWORD);
MYFUNC UpdateLayeredWindow;
關鍵說明:VC6.0 SDK 缺少 UpdateLayeredWindow 宣告,需從 User32.dll 動態載入,後三行即為此用途。
int CGDIPClockDlg::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CDialog::OnCreate(lpCreateStruct) == -1) return -1;
// 載入 User32.dll 並取得 UpdateLayeredWindow 函數指標
hFuncInst = LoadLibrary(_T("User32.DLL"));
if (hFuncInst)
UpdateLayeredWindow = (MYFUNC)GetProcAddress(hFuncInst, "UpdateLayeredWindow");
else { AfxMessageBox(_T("User32.dll ERROR!")); exit(0); }
// 混合函數設定
m_Blend.BlendOp = AC_SRC_OVER;
m_Blend.BlendFlags = 0;
m_Blend.AlphaFormat = AC_SRC_ALPHA;
m_Blend.SourceConstantAlpha = 255;
// 從資源載入 PNG 圖片
ImageFromIDResource(IDR_PNGBAK1, _T("PNG"), m_pImageClock1);
ImageFromIDResource(IDR_PNGNUM, _T("PNG"), m_pImageNum);
ImageFromIDResource(IDR_PNGBAK, _T("PNG"), m_pImageClock);
ImageFromIDResource(IDR_PNGHOUR, _T("PNG"), m_pImageHHour);
ImageFromIDResource(IDR_PNGMIN, _T("PNG"), m_pImageHMinu);
ImageFromIDResource(IDR_PNGSEC, _T("PNG"), m_pImageHSec);
// 取得圖片尺寸
m_BakWidth = m_pImageClock->GetWidth();
m_BakHeight = m_pImageClock->GetHeight();
m_HourWidth = m_pImageHHour->GetWidth();
m_HourHeight = m_pImageHHour->GetHeight();
m_MinuWidth = m_pImageHMinu->GetWidth();
m_MinuHeight = m_pImageHMinu->GetHeight();
m_SecWidth = m_pImageHSec->GetWidth();
m_SecHeight = m_pImageHSec->GetHeight();
// 設定視窗大小並置頂
::SetWindowPos(m_hWnd, HWND_TOPMOST, 0, 0, m_BakWidth, m_BakHeight, SWP_NOSIZE | SWP_NOMOVE);
return 0;
}
BOOL CGDIPClockDlg::OnInitDialog()
{
CDialog::OnInitDialog();
UpdateClockDisplay(); // 立即更新顯示
SetTimer(1, 500, NULL); // 500ms 定時器
ModifyStyleEx(WS_EX_APPWINDOW, WS_EX_TOOLWINDOW); // 隱藏工作列按鈕
return TRUE;
}
void CGDIPClockDlg::OnTimer(UINT nIDEvent)
{
UpdateClockDisplay();
CDialog::OnTimer(nIDEvent);
}
BOOL CGDIPClockDlg::UpdateClockDisplay(int Transparent)
{
HDC hdcTemp = GetDC()->m_hDC;
m_hdcMemory = CreateCompatibleDC(hdcTemp);
HBITMAP hBitMap = CreateCompatibleBitmap(hdcTemp, m_BakWidth, m_BakHeight);
SelectObject(m_hdcMemory, hBitMap);
if (Transparent < 0 || Transparent > 100) Transparent = 100;
m_Blend.SourceConstantAlpha = int(Transparent * 2.55);
HDC hdcScreen = ::GetDC(m_hWnd);
RECT rct; GetWindowRect(&rct);
POINT ptWinPos = { rct.left, rct.top };
Graphics graph(m_hdcMemory);
Point points[] = { Point(0, 0), Point(m_BakWidth, 0), Point(0, m_BakHeight) };
static bool bFly = false;
bFly ? graph.DrawImage(m_pImageClock, points, 3) : graph.DrawImage(m_pImageClock1, points, 3);
bFly = !bFly; // 背景閃爍效果
int OxyX = 140, OxyY = 90; // 表盤中心座標
SYSTEMTIME st; GetLocalTime(&st);
// 時針旋轉矩陣
Matrix matrixH(1, 0, 0, 1, OxyX, OxyY);
matrixH.Rotate(st.wHour * 30 + st.wMinute / 2.0 - 180);
Point pointsH[] = { Point(0, 0), Point(m_HourWidth, 0), Point(0, m_HourHeight) };
matrixH.Translate(-m_HourWidth/2, -m_HourHeight/6);
matrixH.TransformPoints(pointsH, 3);
graph.DrawImage(m_pImageHHour, pointsH, 3);
// 分針旋轉矩陣
Matrix matrixM(1, 0, 0, 1, OxyX, OxyY);
matrixM.Rotate(st.wMinute * 6 - 180);
Point pointsM[] = { Point(0, 0), Point(m_MinuWidth, 0), Point(0, m_MinuHeight) };
matrixM.Translate(-m_MinuWidth/2, -m_MinuHeight/6);
matrixM.TransformPoints(pointsM, 3);
graph.DrawImage(m_pImageHMinu, pointsM, 3);
// 秒針旋轉矩陣
Matrix matrixS(1, 0, 0, 1, OxyX, OxyY);
matrixS.Rotate(st.wSecond * 6 - 180);
Point pointsS[] = { Point(0, 0), Point(m_SecWidth, 0), Point(0, m_SecHeight) };
matrixS.Translate(-m_SecWidth/2, -m_SecHeight/7);
matrixS.TransformPoints(pointsS, 3);
graph.DrawImage(m_pImageHSec, pointsS, 3);
// 數字時間繪製 (從數字圖片裁切對應數字)
graph.DrawImage(m_pImageNum, 0, 0, 14*(st.wHour/10), 0, 14, 23, UnitPixel);
graph.DrawImage(m_pImageNum, 20, 0, 14*(st.wHour%10), 0, 14, 23, UnitPixel);
graph.DrawImage(m_pImageNum, 40, 0, 14*10, 0, 14, 23, UnitPixel); // 冒號
graph.DrawImage(m_pImageNum, 60, 0, 14*(st.wMinute/10), 0, 14, 23, UnitPixel);
graph.DrawImage(m_pImageNum, 80, 0, 14*(st.wMinute%10), 0, 14, 23, UnitPixel);
graph.DrawImage(m_pImageNum, 100, 0, 14*10, 0, 14, 23, UnitPixel); // 冒號
graph.DrawImage(m_pImageNum, 120, 0, 14*(st.wSecond/10), 0, 14, 23, UnitPixel);
graph.DrawImage(m_pImageNum, 140, 0, 14*(st.wSecond%10), 0, 14, 23, UnitPixel);
// 更新分層視窗
SIZE sizeWindow = { m_BakWidth, m_BakHeight };
POINT ptSrc = { 0, 0 };
DWORD dwExStyle = GetWindowLong(m_hWnd, GWL_EXSTYLE);
if ((dwExStyle & WS_EX_LAYERED) != WS_EX_LAYERED)
SetWindowLong(m_hWnd, GWL_EXSTYLE, dwExStyle | WS_EX_LAYERED);
BOOL bRet = UpdateLayeredWindow(m_hWnd, hdcScreen, &ptWinPos, &sizeWindow,
m_hdcMemory, &ptSrc, 0, &m_Blend, ULW_ALPHA);
// 資源釋放
graph.ReleaseHDC(m_hdcMemory);
::ReleaseDC(m_hWnd, hdcScreen);
::ReleaseDC(m_hWnd, hdcTemp);
DeleteObject(hBitMap);
DeleteDC(m_hdcMemory);
m_hdcMemory = NULL;
return bRet;
}
BOOL CGDIPClockDlg::ImageFromIDResource(UINT nID, LPCTSTR sTR, Image*& pImg)
{
HINSTANCE hInst = AfxGetResourceHandle();
HRSRC hRsrc = ::FindResource(hInst, MAKEINTRESOURCE(nID), sTR);
if (!hRsrc) return FALSE;
DWORD len = SizeofResource(hInst, hRsrc);
BYTE* lpRsrc = (BYTE*)LoadResource(hInst, hRsrc);
if (!lpRsrc) return FALSE;
HGLOBAL m_hMem = GlobalAlloc(GMEM_FIXED, len);
BYTE* pmem = (BYTE*)GlobalLock(m_hMem);
memcpy(pmem, lpRsrc, len);
IStream* pstm;
CreateStreamOnHGlobal(m_hMem, FALSE, &pstm);
pImg = Gdiplus::Image::FromStream(pstm);
GlobalUnlock(m_hMem);
pstm->Release();
FreeResource(lpRsrc);
return TRUE;
}
void CGDIPClockDlg::OnLButtonDown(UINT nFlags, CPoint point)
{
::SystemParametersInfo(SPI_SETDRAGFULLWINDOWS, TRUE, NULL, 0);
SendMessage(WM_SYSCOMMAND, SC_MOVE | HTCAPTION, 0);
CDialog::OnLButtonDown(nFlags, point);
}
本文完整展示了在 VC6.0 中整合 GDI+、動態載入 User32.dll API、使用矩陣變換繪製旋轉指針、分層視窗實現半透明效果等關鍵技術。完整原始碼可供下載參考。