承接上篇「VC++ 使用 IPicture 顯示本地圖片」,本文擴展至**從 HTTP URL 下載並直接在視窗/控件繪製圖片**。核心流程:CInternetSession::OpenURL 取得 CHttpFile → 讀取位元組到 HGLOBAL → CreateStreamOnHGlobal 建立 IStream → OleLoadPicture 載入 IPicture → Render 繪製至 DC。完整封裝為 Utils::ShowPic(LPCSTR url, HWND hWnd) 可直接復用。
#include <afxinet.h>
#include <olectl.h> // IPicture, OleLoadPicture
#include <atlbase.h> // CComPtr (建議使用智能指標)
#pragma comment(lib, "wininet.lib")
#pragma comment(lib, "oleaut32.lib")
#pragma comment(lib, "olepro32.lib")
class Utils
{
public:
// 顯示網路圖片於指定視窗
// lpstrImgUrl: 圖片 HTTP/HTTPS URL
// hWnd: 目標視窗控制代碼 (可為 Picture Control 的 m_hWnd)
static HRESULT ShowPic(LPCSTR lpstrImgUrl, HWND hWnd)
{
if (!lpstrImgUrl || !hWnd) return E_INVALIDARG;
HDC hDC = GetDC(hWnd);
if (!hDC) return E_FAIL;
CComPtr spPicture;
CComPtr spStream;
HRESULT hr = E_FAIL;
// 1. 下載圖片資料
CInternetSession session(_T("ImgDownloader/1.0"));
CFile* pFile = NULL;
DWORD dwFileSize = 0;
try {
pFile = session.OpenURL(lpstrImgUrl, 1, NULL, NULL); // 1 = TRANSFER_ASCII (二進位亦可)
if (!pFile) return E_FAIL;
dwFileSize = pFile->GetLength();
if (dwFileSize == 0 || dwFileSize == 0xFFFFFFFF) {
pFile->Close(); delete pFile; ReleaseDC(hWnd, hDC); return E_FAIL;
}
// 2. 配置全域記憶體並讀入
HGLOBAL hGlobal = GlobalAlloc(GMEM_MOVEABLE, dwFileSize);
if (!hGlobal) { pFile->Close(); delete pFile; ReleaseDC(hWnd, hDC); return E_OUTOFMEMORY; }
LPVOID pvData = GlobalLock(hGlobal);
if (!pvData) { GlobalFree(hGlobal); pFile->Close(); delete pFile; ReleaseDC(hWnd, hDC); return E_FAIL; }
DWORD dwRead = pFile->Read(pvData, dwFileSize);
GlobalUnlock(hGlobal);
pFile->Close(); delete pFile; pFile = NULL;
if (dwRead != dwFileSize) { GlobalFree(hGlobal); ReleaseDC(hWnd, hDC); return E_FAIL; }
// 3. 建立 IStream
hr = CreateStreamOnHGlobal(hGlobal, TRUE, &spStream);
if (FAILED(hr)) { ReleaseDC(hWnd, hDC); return hr; }
// 4. 載入 IPicture
hr = OleLoadPicture(spStream, dwFileSize, TRUE, IID_IPicture, (LPVOID*)&spPicture);
if (FAILED(hr) || !spPicture) { ReleaseDC(hWnd, hDC); return hr; }
// 5. 取得圖片尺寸 (HIMETRIC: 0.01 毫米)
OLE_XSIZE_HIMETRIC hmWidth; OLE_YSIZE_HIMETRIC hmHeight;
spPicture->get_Width(&hmWidth);
spPicture->get_Height(&hmHeight);
// 6. 轉換為裝置像素
int nWidth = MulDiv(hmWidth, GetDeviceCaps(hDC, LOGPIXELSX), 2540);
int nHeight = MulDiv(hmHeight, GetDeviceCaps(hDC, LOGPIXELSY), 2540);
// 7. 繪製 (左上角 0,0)
hr = spPicture->Render(hDC, 0, 0, nWidth, nHeight,
0, hmHeight, hmWidth, -hmHeight, NULL);
ReleaseDC(hWnd, hDC);
return SUCCEEDED(hr) ? S_OK : E_FAIL;
}
catch (CInternetException* e) {
TCHAR szErr[256]; e->GetErrorMessage(szErr, 255);
TRACE(_T("下載失敗: %s (0x%08X)\n"), szErr, e->m_dwError);
e->Delete();
if (pFile) { pFile->Close(); delete pFile; }
ReleaseDC(hWnd, hDC);
return E_FAIL;
}
catch (CMemoryException*) {
if (pFile) { pFile->Close(); delete pFile; }
ReleaseDC(hWnd, hDC);
return E_OUTOFMEMORY;
}
}
};
// 在對話框按鈕事件中
void CMyDlg::OnBtnLoadImage()
{
CString url = _T("https://example.com/image.jpg");
HRESULT hr = Utils::ShowPic(url, GetDlgItem(IDC_STATIC_PIC)->m_hWnd);
if (FAILED(hr)) AfxMessageBox(_T("圖片載入失敗"));
}
| 項目 | 本地檔案版 | 網路 URL 版 |
|---|---|---|
| 資料來源 | CreateFile + ReadFile | CInternetSession::OpenURL → CHttpFile::Read |
| 檔案大小 | GetFileSize | CFile::GetLength |
| 記憶體讀取 | ReadFile | CHttpFile::Read |
| 後續流程 | 同樣:HGLOBAL → IStream → OleLoadPicture → Render | 同上 |
OleLoadPicture 僅渲染第一幀。需動畫請用 GDI+ Image + ImageAnimator 或 Direct2D。PostMessage 通知 UI 繪製。INTERNET_FLAG_IGNORE_CERT_* 旗標。CComPtr 自動釋放 COM 介面;HGLOBAL 由 CreateStreamOnHGlobal(..., TRUE) 交由 Stream 釋放。wininet.lib、oleaut32.lib、olepro32.lib;包含 afxinet.h、olectl.h。Bitmap::FromStream 支援更多格式 (PNG 透明、JPG、GIF 動畫)。SHCreateStreamOnFileEx 或 WinHttp 非同步下載。BitmapImage 的 UriSource 綁定 URL 即可。