1. 程式人生 > >重溫WIN32 API ------ 視窗上繪製點陣圖檔案

重溫WIN32 API ------ 視窗上繪製點陣圖檔案

1 基本思路

做介面模擬時,經常需要在視窗上貼圖,隨著圖片數量的增多,通過資原始檔中新增點陣圖的方式會變得越來越不可控。所以本著“資源與程式分離“的原則,還是使用外部點陣圖檔案更加清晰明瞭。

那麼如何管理點陣圖的貼圖位置呢?如果寫死到程式程式碼中,則又會導致耦合行過高的問題。容易想到解決方法有兩個,一是使用一個單獨的xml檔案來記錄圖片檔名稱和貼圖位置的關係,二是直接把貼圖位置資訊包含進點陣圖檔案的檔名中。本文采用更加簡單的後者,點陣圖檔名格式規範為:description_xxx-yyy.bmp ,其中xxx為貼圖時相對於所在視窗的x座標,yyy為貼圖時相對於所在視窗的y座標,例如:發動機按鈕_100-200.bmp,表示貼圖時,目的座標點為(100,200)。

至於貼圖的實現,考慮到只需要支援bmp一種格式即可,所以採用GDI庫完成。考慮到一個位圖檔案可能會被貼圖多次(例如重新整理的時候),所以實現時沒有直接SetDIBitsToDevice(),而是首先把DIB通過CreateDIBitmap()轉化為DDB,然後儲存這個DDB,這樣以後每次貼圖時,只需要BitBlt()這個DDB就可以了,提高了效率。

2 程式碼實現

BitmapHelper.h

#pragma once
/********************************************************************************
                            BitmapHelper 貼圖助手
功能描述:
	根據點陣圖檔名,把點陣圖檔案讀入並貼到指定視窗,為提高效率物件內部一直
	儲存讀入記憶體的BITMAP,所以只在第一次貼圖時需要從外部檔案讀取。

使用說明:
	每一個位圖檔案對應一個BitmapHelper類物件。使用樣例:

	BitmapHelper *pBmp = new BitmapHelper(L"D:\\風景_100-300.bmp");
	pBmp->ShowOnWindow(this->m_hWnd);

	delete pBmp;  // 程式結束或不再需要這個點陣圖時,刪除
********************************************************************************/
class BitmapHelper
{
public:
	BitmapHelper(TCHAR* file);
	~BitmapHelper();

protected:
	TCHAR fileName[256] ; // 點陣圖檔名
	HBITMAP hBitmap;      // 點陣圖控制代碼
	int desX;             // 目的x座標
	int desY;             // 目的y座標
protected:
	void ShowOnWindow(HWND hwnd, int x, int y);   // 在指定視窗上顯示
public:
	HBITMAP CreateBitmapObjectFromDibFile(HDC hdc); //從檔案中獲取DDB
	void ShowOnWindow(HWND hwnd);
	void ShowOnDevice(HDC dc, int x, int y);

};

BitmapHelper.cpp

#include "stdafx.h"
#include "BitmapHelper.h"
#include "string.h"

BitmapHelper::BitmapHelper(TCHAR* file)
{
	this->hBitmap = NULL;
	this->desX = -9999;
	this->desY = -9999;
	::StrCpyNW(this->fileName, file, 256);
	this->fileName[255] = TEXT('\0');
}

/*
  功能:從點陣圖檔案建立DDB
  引數: hdc 裝置DC
  返回值: DDB控制代碼,錯誤返回NULL
*/
HBITMAP BitmapHelper::CreateBitmapObjectFromDibFile(HDC hdc)
{
	BITMAPFILEHEADER* pbmfh = NULL;
	HANDLE hFile = NULL;
	DWORD dwFileSize = 0;
	
	hFile = CreateFile(fileName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL);
	if (hFile == INVALID_HANDLE_VALUE)
	{
		return NULL;
	}
	dwFileSize = GetFileSize(hFile, NULL);
	pbmfh = (BITMAPFILEHEADER*)malloc(dwFileSize);
	if (pbmfh == NULL)
	{
		CloseHandle(hFile);
		return NULL;
	}
	DWORD dwBytesRead = 0;
	BOOL bSuccess = ::ReadFile(hFile, pbmfh, dwFileSize, &dwBytesRead, NULL);
	::CloseHandle(hFile);
	// 驗證檔案確實是點陣圖檔案
	if (!bSuccess || dwBytesRead != dwFileSize || pbmfh->bfType != *(WORD*) "BM" || pbmfh->bfSize != dwFileSize)
	{
		free(pbmfh);
		return NULL;
	}

	HBITMAP hBitmap = ::CreateDIBitmap(hdc,
		(BITMAPINFOHEADER*)(pbmfh + 1),
		CBM_INIT,
		(BYTE*)pbmfh + pbmfh->bfOffBits,
		(BITMAPINFO*)(pbmfh + 1),
		DIB_RGB_COLORS);

	free(pbmfh);

	return hBitmap;
}
/*
功能:在指定的裝置上顯示圖片
引數:hdc 裝置DC, (x,y)為目的左上角座標
*/
void BitmapHelper::ShowOnDevice(HDC hdc, int x, int y)
{
	if (this->hBitmap == NULL)
	{
		this->hBitmap = this->CreateBitmapObjectFromDibFile(hdc);
	}
	if (this->hBitmap != NULL)
	{
		BITMAP bitmap;
		::GetObject(this->hBitmap, sizeof(BITMAP), &bitmap);

		HDC hdcMem = ::CreateCompatibleDC(hdc);
		::SelectObject(hdcMem, this->hBitmap);
		::BitBlt(hdc, x, y, bitmap.bmWidth, bitmap.bmHeight, hdcMem, 0, 0, SRCCOPY);
		::DeleteDC(hdcMem);
	}
}
/*
功能:在指定視窗上顯示點陣圖
引數:hwnd 視窗控制代碼, (x,y) 目的左上角座標
*/
void BitmapHelper::ShowOnWindow(HWND hwnd, int x, int y)
{
	if (this->hBitmap == NULL)
	{
		HDC hdc = ::GetDC(hwnd);
		this->hBitmap = this->CreateBitmapObjectFromDibFile(hdc);
		::ReleaseDC(hwnd, hdc);
	}
	if (this->hBitmap != NULL)
	{
		BITMAP bitmap;
		::GetObject(this->hBitmap, sizeof(BITMAP), &bitmap);

		HDC hdc = ::GetDC(hwnd);

		HDC hdcMem = ::CreateCompatibleDC(hdc);
		::SelectObject(hdcMem, this->hBitmap);
		::BitBlt(hdc, x, y, bitmap.bmWidth, bitmap.bmHeight, hdcMem, 0, 0, SRCCOPY);
		::DeleteDC(hdcMem);

		::ReleaseDC(hwnd, hdc);
	}
}
/*
功能:根據檔名解析位置顯示點陣圖檔案到指定視窗
引數: hwnd 目的視窗
說明: 點陣圖檔案命名規範 name_xxx-yyy.bmp
*/
void BitmapHelper::ShowOnWindow(HWND hwnd)
{
	// 解析檔名
	if (this->desX == -9999)
	{
		int i = 0;
		int indexLast_ = 0; //最後一個_,表示座標的開始
		int indexLastDot = 0;   //最後一個.,表示副檔名的開始
		int indexSep = 0;       // 座標分割標誌-
		int n = wcslen(this->fileName);
		for (i = n-1; i >=0; i--)
		{
			if (this->fileName[i] == L'_')
			{
				break;
			}
		}
		indexLast_ = (i == 0 ? -1 : i); // -1 表示沒有目錄部分
		for (i = n - 1; i >= 0; i--)
		{
			if (this->fileName[i] == L'.')
			{
				break;
			}
		}
		indexLastDot = (i == 0 ? n : i); // n表示沒有副檔名部分

		int xyStart = indexLast_ + 1; // 座標起始位置
		int xyEnd = indexLastDot - 1;     // 座標結束位置

		for (i = xyStart; i <= xyEnd; i++)
		{
			if (this->fileName[i] == L'-')
			{
				break;
			}
		}
		indexSep = i == xyEnd ? -1 : i; // -1 表示沒有找到-
		
		if (n==0 || indexSep==-1) {
			this->desX = 0;
			this->desY = 0;
		}
		else
		{
			TCHAR s_x[10];
			int count = indexSep - xyStart;
			wcsncpy_s(s_x, 10, this->fileName+xyStart, count);
			s_x[count] = L'\0';
			this->desX = _wtoi(s_x);

			WCHAR s_y[10];
			count = xyEnd - indexSep;
			wcsncpy_s(s_y, 10, this->fileName+indexSep+1, count);
			s_y[count] = L'\0';
			this->desY = _wtoi(s_y);
		}
	}
	this->ShowOnWindow(hwnd, this->desX, this->desY);
}
BitmapHelper::~BitmapHelper()
{
	if (this->hBitmap != NULL)         // 清理點陣圖資源
	{
		::DeleteObject(this->hBitmap);
	}
}