高级图像控件






4.94/5 (37投票s)
用于查看大多数常见图像格式的高级图像控件,包含导入图像、预览、调整大小、定位、平移、缩放、导出图像、提取资源图标等高级功能。
目录
1. 引言
2. CImageCtrl 类
3. 平移和缩放
4. GDI+
5. GDI:内存设备上下文
6. 资源
6.1 仅资源 DLL
6.2 ResourceList 模块
6.3 Icon 模块
6.4 从资源 DLL 或 EXE 加载图像
7. 可调整大小的对话框
8. 背景
9. 参考
10. 使用代码
11. 历史记录
引言
本文描述了图像控件(派生自 CStatic),主要用于导入和查看以下图像格式:BMP、DIB、JPG、JPEG、JPE、JFIF、GIF、TIF、TIFF、PNG、ICO、WMF、EMF。此外,还集成了一些其他方便的功能,如调整大小、定位、平移、缩放、导出图像和提取资源图标。
我借鉴了Tobias Eiseler 的解决方案1 的基础,并进一步改进,以支持从 dll 或 exe 资源导入所有提到的图像类型,并提供额外的调整大小和定位(在客户区内)选项。
目标是创建一个高级图像控件,图像可以从以下四种来源之一获得:
1) 图像文件路径,
2) 图像 IStream 接口,
3) 图像 BYTE(unsigned char)
数组,
4) 图像 DLL 或 EXE 资源。
CImageCtrl 类
现在,快速看一下封装此行为的 CImageCtrl
类。
class CImageCtrl : public CStatic { public: CImageCtrl(); ~CImageCtrl(); enum sizeType { SIZE_ORIGINAL, SIZE_SCALETOFIT, SIZE_CUSTOM }; enum allignmentType { ALLIGN_TOPLEFT, ALLIGN_TOPCENTER, ALLIGN_TOPRIGHT, ALLIGN_MIDDLELEFT, ALLIGN_MIDDLECENTER, ALLIGN_MIDDLERIGHT, ALLIGN_BOTTOMLEFT, ALLIGN_BOTTOMCENTER, ALLIGN_BOTTOMRIGHT }; void setSizeType(int sizeType) { m_sizeType = sizeType; } // Size type: SIZE_ORIGINAL,... double getLeft() { return m_left; } // x-coordinate of the image top-left point. double getTop() { return m_top; } // y-coordinate of the image top-left point. void setWidth(double width) { m_width = width; } // Set image width (pixels). double getWidth() { return m_width; } // Image width (pixels). double getWidthOriginal() { return m_widthOriginal; } // Image original width (pixels). void setHeight(double height) { m_height = height; } // Set image height (pixels). double getHeight() { return m_height; } // Image height (pixels). double getHeightOriginal() { return m_heightOriginal; } // Image original height (pixels). void setMaintainAspectRatio(bool maintainAspectRatio) { m_maintainAspectRatio = maintainAspectRatio; } double setAspectRatio(double aspectRatio) { return m_aspectRatio = aspectRatio; } double getAspectRatio() { return m_aspectRatio; } // Case "image": m_height / m_width. void setAllignmentType(int allignmentType) { m_allignmentType = allignmentType; } void setPanMode(bool isPanMode) { m_isPanMode = isPanMode; } // Enable/disable PAN mode. void setZoomMode(bool isZoomMode) { m_isZoomMode = isZoomMode; } // Enable/disable ZOOM mode. bool isImageShown() { return m_isImageShown > 0; } // Is image shown in the control? void update(); // Update image in the control. void erase(); // Erase image from the control. BOOL load(CString szFilePath); // Loads an image from file. BOOL load(IStream* piStream); // Loads an image from IStream interface. BOOL load(BYTE* pData, size_t nSize); // Loads an image from BYTE array. // Loads image from resource. BOOL load(HMODULE hModule, LPCTSTR lpName, LPCTSTR lpType, WORD wlan); BOOL convert(CString pathName, CString imageType); // Converts image to specified image type. // Loads icon from resource EXE or DLL and saves it to *.ico file. BOOL iconResourceToFile(CString resPathName, LPCTSTR lpName, WORD wlan, CString icoPathName); private: void release(bool calcFrameAspectRatio = true); // Release allocated memory and initialize. int m_sizeType; // Size type: SIZE_ORIGINAL, SIZE_SCALETOFILL, SIZE_CUSTOM. double m_left; // x-coordinate of the image top-left point. double m_top; // y-coordinate of the image top-left point. double m_width; // Image width (pixels). double m_height; // Image height (pixels). double m_widthOriginal; // Image original width (pixels). double m_heightOriginal; // Image original height (pixels). bool m_maintainAspectRatio; // Maintain aspect ratio or not. double m_aspectRatio; // Aspect ratio factor: Case "image": m_height / m_width. int m_allignmentType; // Image alignment type inside control: ALLIGN_TOPLEFT,... bool m_isPanMode; // Enabled/disabled PAN mode. bool m_isZoomMode; // Enabled/disabled ZOOM mode. double m_zoomMin; // Minimal rectangle side value (in pixels) on ZOOM action. double m_zoomMax; // Maximal rectangle side value (in pixels) on ZOOM action. BOOL m_isImageShown; // Is image shown in the control? BOOL m_isInitialShow; // Initial image show? True, if not derived from PAN/ZOOM mode action. CPoint m_panAtPt; // Origin point of PAN action. CPoint m_panOffset; // Offset distances at PAN action. CPoint m_zoomAtPt; // Point at zoom event, triggered by mouse wheel scrolling ON image. double m_zoomFactor; // Zoom factor: Case > 1: zoom in, Case < 1: zoom out. + Bitmap* m_pBmp; // Pointer to GDI+ bitmap. + ULONG_PTR m_gdiplusToken; // GDI+ Token. protected: virtual void PreSubclassWindow(); virtual void DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct); afx_msg void OnLButtonDown(UINT nFlags, CPoint point); afx_msg void OnLButtonUp(UINT nFlags, CPoint point); afx_msg void OnMouseMove(UINT nFlags, CPoint point); afx_msg BOOL OnMouseWheel(UINT nFlags, short zDelta, CPoint pt); DECLARE_MESSAGE_MAP() };
平移和缩放
首先,您需要通过选择对话框窗口上的相应复选框来启用平移或缩放模式。
什么是平移及其工作原理?
在图像客户区上方单击鼠标左键,然后移动鼠标,最后释放鼠标左键。结果是将图像按照“鼠标移动”向量进行位移。
什么是缩放及其工作原理?
将鼠标光标置于图像上(或至少在图像客户区内)。使用鼠标中键(滚轮)进行放大或缩小。结果是缩放后的图像,以鼠标位置点为固定点。
GDI+
用于查看图像的技术是 GDI+,它首次出现在 Windows XP 和 Windows Server 2003 中(GDI+ 功能用代码行开头的加号表示)。
+
我在 CimageCtrl
构造函数中这样初始化它:
CImageCtrl::CImageCtrl(void) : CStatic(), m_pBmp(NULL), m_gdiplusToken(0), m_sizeType(sizeType::SIZE_SCALETOFIT), m_maintainAspectRatio(true), m_aspectRatio(1), m_allignmentType(allignmentType::ALLIGN_MIDDLECENTER), m_isPanMode(FALSE), m_isZoomMode(FALSE) { + GdiplusStartupInput startupInput; GdiplusStartup(&m_gdiplusToken, &startupInput, NULL); m_isImageShown = FALSE; m_panAtPt.SetPoint(-1, -1); m_panOffset.SetPoint(0, 0); m_zoomAtPt.SetPoint(-1, -1); m_zoomFactor = 1.0; m_zoomMin = 1; m_zoomMax = 99999; }
并在析构函数中进行清理:
CImageCtrl::~CImageCtrl(void) { + release(false); GdiplusShutdown(m_gdiplusToken); }
变量 CImageCtrl::m_pBmp
是指向 Bitmap 的指针,该指针之前已通过四个 CImageCtrl::load()
方法之一获取,而 CImageCtrl::DrawItem()
过程执行 OnPaint()
“工作”,该工作由 CImageCtrl::Invalidate()
和 CImageCtrl::UpdateWindow()
方法触发。
void CImageCtrl::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct) { double w0, h0, sx, sy, s, dx, dy; CRect rect; CDC *pDC = NULL, dcMem; CBitmap bmpMem, *oldBmp = NULL; if (pDC = GetDC()) { + if (m_pBmp) { // Create an in-memory device context, compatible with the painting device context. dcMem.CreateCompatibleDC(pDC); // Create bitmap compatible with the painting device context. GetClientRect(rect); bmpMem.CreateCompatibleBitmap(pDC, rect.Width(), rect.Height()); // Select the bitmap into this in-memory device context. oldBmp = dcMem.SelectObject(&bmpMem); + Graphics graphics(dcMem.m_hDC); // Paint with dialog-background color. dcMem.FillSolidRect(rect, GetSysColor(COLOR_3DFACE)); if (m_isInitialShow) { m_widthOriginal = m_pBmp->GetWidth(); m_heightOriginal = m_pBmp->GetHeight(); m_aspectRatio = m_heightOriginal / m_widthOriginal; if (!m_maintainAspectRatio) { if (m_sizeType == sizeType::SIZE_SCALETOFIT) { m_width = rect.Width(); m_height = rect.Height(); } else if (m_sizeType == sizeType::SIZE_ORIGINAL) { m_width = m_widthOriginal; m_height = m_heightOriginal; } m_left = m_top = 0; } else { if (m_sizeType == sizeType::SIZE_SCALETOFIT) { sx = rect.Width() / m_widthOriginal; sy = rect.Height() / m_heightOriginal; s = (sx > sy) ? sy : sx; m_height = m_aspectRatio * (m_width = s * m_widthOriginal); } else if (m_sizeType == sizeType::SIZE_CUSTOM) { sx = m_width / m_widthOriginal; sy = m_height / m_heightOriginal; s = (sx > sy) ? sy : sx; m_height = m_aspectRatio * (m_width = s * m_widthOriginal); } else if (m_sizeType == sizeType::SIZE_ORIGINAL) { m_width = m_widthOriginal; m_height = m_heightOriginal; } } if (m_allignmentType == allignmentType::ALLIGN_TOPLEFT) m_left = m_top = 0; else if (m_allignmentType == allignmentType::ALLIGN_TOPCENTER) { m_left = (rect.Width() - m_width) / 2; m_top = 0; } else if (m_allignmentType == allignmentType::ALLIGN_TOPRIGHT) { m_left = rect.Width() - m_width; m_top = 0; } else if (m_allignmentType == allignmentType::ALLIGN_MIDDLELEFT) { m_left = 0; m_top = (rect.Height() - m_height) / 2; } else if (m_allignmentType == allignmentType::ALLIGN_MIDDLECENTER) { m_left = (rect.Width() - m_width) / 2; m_top = (rect.Height() - m_height) / 2; } else if (m_allignmentType == allignmentType::ALLIGN_MIDDLERIGHT) { m_left = rect.Width() - m_width; m_top = (rect.Height() - m_height) / 2; } else if (m_allignmentType == allignmentType::ALLIGN_BOTTOMLEFT) { m_left = 0; m_top = rect.Height() - m_height; } else if (m_allignmentType == allignmentType::ALLIGN_BOTTOMCENTER) { m_left = (rect.Width() - m_width) / 2; m_top = rect.Height() - m_height; } else if (m_allignmentType == allignmentType::ALLIGN_BOTTOMRIGHT) { m_left = rect.Width() - m_width; m_top = rect.Height() - m_height; } } else if (m_zoomAtPt.x < 0) { m_left += m_panOffset.x; m_top += m_panOffset.y; }// PAN else if (m_zoomFactor > 1e-6) // ZOOM { ScreenToClient(&m_zoomAtPt); if ((dx = (m_zoomAtPt.x - m_left)) < 1e-6) { m_zoomAtPt.x = (LONG)m_left;dx = 0; } else if (m_zoomAtPt.x > m_left + m_width - 1e-6) { m_zoomAtPt.x = (LONG)(m_left + (dx = m_width)); } if ((dy = (m_zoomAtPt.y - m_top)) < 1e-6) { m_zoomAtPt.y = (LONG)m_top; dy = 0; } else if (m_zoomAtPt.y > m_top + m_height - 1e-6) { m_zoomAtPt.y = (LONG)(m_top + (dy = m_height)); } w0 = m_width * m_zoomFactor; h0 = m_height * m_zoomFactor; if (w0 >= m_zoomMin && w0 <= m_zoomMax && h0 >= m_zoomMin && h0 <= m_zoomMax) { dx *= (w0 / m_width); dy *= (h0 / m_height); m_left = m_zoomAtPt.x - dx; m_top = m_zoomAtPt.y - dy; m_height = m_aspectRatio * (m_width = w0); GetParent()->SendMessage(WM_APP + 1, 12345, 0); } } + m_isImageShown = (graphics.DrawImage(m_pBmp, (int)(m_left + 1e-6), (int)(m_top + 1e-6), (int)(m_width + 1e-6), (int)(m_height + 1e-6)) == Status::Ok); pDC->BitBlt(0, 0, rect.Width(), rect.Height(), &dcMem, 0, 0, SRCCOPY); dcMem.SelectObject(oldBmp); bmpMem.DeleteObject(); dcMem.DeleteDC(); } else { GetClientRect(rect); pDC->FillSolidRect(rect, GetSysColor(COLOR_3DFACE)); m_isImageShown = FALSE; } ReleaseDC(pDC); m_zoomAtPt.SetPoint(-1, -1); } }
GDI:内存设备上下文
此技术用于消除平移或缩放操作期间的“闪烁”效果。技巧是创建一个内存设备上下文(与绘图设备上下文兼容),所有绘图都在其中完成。最后,只需将内存设备上下文的一部分复制到绘图设备上下文。这通过以下语句实现:
CDC *pDC = NULL, dcMem; CBitmap bmpMem, *oldBmp = NULL; pDC = GetDC(); // Create an in-memory device context, compatible with the painting device context. dcMem.CreateCompatibleDC(pDC); // Create bitmap compatible with the painting device context. GetClientRect(rect); bmpMem.CreateCompatibleBitmap(pDC, rect.Width(), rect.Height()); // Select the bitmap into this in-memory device context. oldBmp = dcMem.SelectObject(&bmpMem); // Do all the paintings with dcMem in-memory device context here. // Copy a portion of the in-memory device context to the painting device context. pDC->BitBlt(0, 0, rect.Width(), rect.Height(), &dcMem, 0, 0, SRCCOPY); dcMem.SelectObject(oldBmp); bmpMem.DeleteObject(); dcMem.DeleteDC(); ReleaseDC(pDC);
如果您想了解更多关于设备上下文的信息,这里 2 是 Marius Bancila 的一篇优秀文章。
仅资源 DLL
我决定创建一个仅资源 DLL3(包含所有图像),以测试“从资源 DLL 或 EXE 加载图像”选项。
仅资源 DLL 是一个只包含资源的 DLL,如图像、字符串、对话框等。
如何在Microsoft Visual Studio中创建它?
1) 创建“新建项目->WIn32 项目->DLL”应用程序类型。
2) 选择“项目->属性->配置属性->链接器->高级->无入口点”并将选项设置为
“是 (/NOENTRY)”。
3) 添加资源。
我添加了 BMP、DIB、JPG、JPEG、JPE、JFIF、GIF、TIF、TIFF、PNG、ICO、WMF、EMF 类型的图像。
*.ico 图像在“图标”资源类型下;
*.bmp 图像在“位图”和“RCDATA”资源类型下;
*.jpg 图像在“JPG”自定义资源类型下;
*.png 图像在“PNG”自定义资源类型下,依此类推。
ResourceList 模块
ResourceList 模块包含“ResourceList.h
”和“ResourceList.cpp
”(项目 ImageControl
),用于列出所有资源,由资源语言、资源类型和资源名称定义。我想强调的是,资源类型和名称可以是字符串或整数字符串(例如 MAKEINTRESOURCE(
153)
,RT_BITMAP
)。
如何获取可能是图像类型的资源?
#include "ResourceList.h" CResourceList rl; HMODULE hModule; CArray<tResourceLCID*> *pRes; // Array of resource "Locale Culture Identifier"-s. if(hModule = LoadLibrary(_T("..."))) { rl.setResourceType(CResourceList::resourceType::RESOURCE_IMAGE); pRes = rl.getResourceList(hModule); // Get the resource list. Do something with it. FreeLibrary(hModule); }
Icon 模块
Icon 模块包含“Icon.h
”和“Icon.cpp
”(项目 ImageControl
),用于提供 importIcon()
(从资源模块或 *.ico 文件)和 exportIcon()
(导出到 *.ico 文件)方法。
如何加载资源图标并将其导出为 *.ico 文件?
#include "Icon.h" CIconExtractor ie; HMODULE hModule; LPCTSTR lpName = ...; // Icon resource name. WORD wlan = ...; // Icon resource language. CString icoPathName = _T("..."); // Output icon path. // You can get resource list as mentioned in previous section, and filter only // RT_GROUP_ICON resources by calling setResourceType(RT_GROUP_ICON) preliminary. // Thus you will get all resource icons listed. if(hModule = LoadLibrary(_T("..."))) { if(ie.importIcon(hModule, lpName, wlan)) ie.exportIcon(icoPathName); FreeLibrary(hModule); }
从资源 DLL 或 EXE 加载图像
使用 CImageCtrl
类,您可以分 3 步完成此操作。
1) 调用函数 hModule = LoadLibrary()
加载相应的资源 dll 或 exe。
2) 调用 m_image.load(hModule, MAKEINTRESOURCE(.), ., .)
。
3) 调用 FreeLibrary(hModule)
卸载资源。
这在 CImageControlDlg::OnCbnSelchangeComboResname()
事件中实现。
void CImageControlDlg::OnCbnSelchangeComboResname()
{
int i, j, k; bool isTypeInt, isNameInt; CString type, name, buff; HMODULE hModule;
if ((i = ((CComboBox *)GetDlgItem(IDC_COMBO_RESLOCALE))->GetCurSel()) != CB_ERR
&& (j = ((CComboBox *)GetDlgItem(IDC_COMBO_RESTYPE))->GetCurSel()) != CB_ERR
&& (k = ((CComboBox *)GetDlgItem(IDC_COMBO_RESNAME))->GetCurSel()) != CB_ERR)
if (hModule = LoadLibrary(m_resFilePath))
{
isTypeInt = m_pRes->GetAt(i)->resTypes.GetAt(j)->isInteger;
type = m_pRes->GetAt(i)->resTypes.GetAt(j)->type;
isNameInt = m_pRes->GetAt(i)->resTypes.GetAt(j)->resNames.GetAt(k)->isInteger;
name = m_pRes->GetAt(i)->resTypes.GetAt(j)->resNames.GetAt(k)->name;
// if(m_image.load(m_hModule, MAKEINTRESOURCE(164), RT_GROUP_ICON, m_pRes->GetAt(i)->lcid))
// if(m_image.load(m_hModule, MAKEINTRESOURCE(153), _T("GIF"), m_pRes->GetAt(i)->lcid))
if(m_image.load(hModule, isNameInt ? MAKEINTRESOURCE(_wtoi(name)) : name,
isTypeInt ? MAKEINTRESOURCE(_wtoi(type)) : type, m_pRes->GetAt(i)->lcid))
{
m_isOnEnChangeEditWidthHeight = false;
buff.Format(_T("%d"), m_image.getWidth());
GetDlgItem(IDC_EDIT_WIDTH)->SetWindowText(buff);
buff.Format(_T("%d"), m_image.getHeight());
GetDlgItem(IDC_EDIT_HEIGHT)->SetWindowText(buff);
m_isOnEnChangeEditWidthHeight = true;
((CComboBox*)GetDlgItem(IDC_COMBO_RESTYPE))->GetLBText(j, buff);
}
FreeLibrary(hModule);
}
GetDlgItem(IDC_BUTTON_SAVEICONAS)->EnableWindow(buff == _T("Icon"));
GetDlgItem(IDC_BUTTON_SAVEAS)->EnableWindow(m_image.isImageShown());
}
备注
注意加载图标。资源类型为 RT_GROUP_ICON。图标资源可以包含多个图像,每个图像的类型为 RT_ICON。
现在,让我们更精确地看一下 CImageCtrl::load(hModule, MAKEINTRESOURCE(.), ., .)
方法。基本上,代码根据资源类型(非图标和图标资源)分为两个分支。
1) “非图标资源”通过此代码片段。
BOOL CImageCtrl::load(HMODULE hModule, LPCTSTR lpName, LPCTSTR lpType, WORD wlan) { HRSRC hRes; DWORD resSize; HGLOBAL hGlobal, hGlobal2, hGlobal3; HICON hIcon; void *pRes = NULL, *pRes2 = NULL; IStream *pStream = NULL; GRPICONDIR *pIconDir = NULL; release(); m_isImageFromResource = true; if ((hRes = FindResourceEx(hModule, lpType, lpName, wlan)) && (resSize = SizeofResource(hModule, hRes)) && (hGlobal = LoadResource(hModule, hRes))) { if (lpType != RT_GROUP_ICON) { if (lpType == RT_BITMAP) { m_pBmp = new Bitmap((HBITMAP)LoadImage(hModule, lpName, IMAGE_BITMAP, 0, 0, 0), 0); m_isInitialShow = TRUE; Invalidate(); UpdateWindow(); m_isInitialShow = FALSE; } else if (pRes = LockResource(hGlobal)) { if (hGlobal2 = GlobalAlloc(GMEM_MOVEABLE, resSize)) { if (pRes2 = GlobalLock(hGlobal2)) { CopyMemory(pRes2, pRes, resSize); if (CreateStreamOnHGlobal(hGlobal2, FALSE, &pStream) == S_OK) { if (pBmp = Bitmap::FromStream(pStream)) { Graphics g(m_pBmp = new Bitmap(pBmp->GetWidth(), pBmp->GetHeight())); g.DrawImage(pBmp, 0, 0, pBmp->GetWidth(), pBmp->GetHeight()); delete pBmp; } pStream->Release(); } UnlockResource(hGlobal2); } GlobalFree(hGlobal2); } UnlockResource(hGlobal); } } FreeResource(hGlobal); } m_isImageFromResource = true; return m_isImageShown; }
typedef struct { WORD idReserved; // Reserved (must be 0). WORD idType; // Resource type (1 for icons). WORD idCount; // Number of icon images. GRPICONDIRENTRY idEntries[1]; // An entry for each icon image. } GRPICONDIR, *LPGRPICONDIR;
同时,所有类型为 RT_ICON 的图标图像都位于 GRPICONDIRENTRY 结构数组中。
typedef struct { BYTE bWidth; // Width (in pixels) of the image. BYTE bHeight; // Height (in pixels )of the image. BYTE bColorCount; // Number of colors in image (0 if >= 8bpp). BYTE bReserved; // Reserved (must be 0). WORD wPlanes; // Color Planes. WORD wBitCount; // Bits per pixel. DWORD dwBytesInRes; // How many bytes in this resource? WORD nID; // The ID. } GRPICONDIRENTRY, *LPGRPICONDIRENTRY;
GRPICONDIR *pIconDir = NULL; if (pIconDir = (GRPICONDIR*)LockResource(hGlobal)) { if (pIconDir->idCount && (hRes = FindResource(hModule, MAKEINTRESOURCE(pIconDir->idEntries[0].nID), RT_ICON)) && (resSize = SizeofResource(hModule, hRes)) && (hGlobal3 = LoadResource(hModule, hRes))) { // // Same as in non-icon resource images. // FreeResource(hGlobal3); } UnlockResource(hGlobal); }
可调整大小的对话框
我们希望在调整对话框大小时执行适当的操作,例如重新定位和/或调整对话框控件的大小。为此,开发了“StandardLibrary”(静态库项目),其中包含“ResizableDlg”模块。
一种方法是相对于对话框大小的变化,从锚点描述这些操作。如果我们只想执行重新定位,那么一个锚点就足以描述所有可能的重新定位(让它成为对话框的左上角点)。调整大小的操作更棘手。如果我们观察 x 方向的调整大小,可以固定左边缘并移动右边缘,反之亦然。y 方向的调整大小也有类似的情况。但是,以左上角为锚点,x 方向的相应调整大小是移动右边缘,y 方向是移动底部边缘。类似地,我们可以为其余三个锚点定义 x 和 y 方向的调整大小操作:左下角、右上角、右下角。
相对于对话框大小变化有四种基本操作:
- x 方向的重新定位(参数
dxMoveRelative
), - y 方向的重新定位(参数
dyMoveRelative
), - x 方向的调整大小(参数
dxResizeRelative
), - y 方向的调整大小(参数
dyResizeRelative
)。
所有这四种操作都可以用区间 [0,1] 中的参数来描述。0 表示无操作,1 表示 x 或 y 方向的操作与对话框的 x 或 y 方向变化量相同,0.3 表示 x 或 y 方向的操作为变化量的 30%……
最好是
dxMoveRelative + dxResizeRelative <= 1
且 dyMoveRelative + dyResizeRelative <= 1
,
否则控件可能会离开对话框区域,这是不允许的。
如果我们选择所有四个参数为零,则控件将固定在距离锚点的 x 和 y 距离处。
最后,“ResizableDlg”模块中有一个方法 CResizableDlg::moveResizeControl(...)
,其参数为:
int nID
(控件 ID),
int anchor
(锚点:TOP_LEFT
、BOTTOM_LEFT
、TOP_RIGHT
、BOTTOM_RIGHT
),
double dxMoveRelative
(x 方向的重新定位),
double dyMoveRelative
(y 方向的重新定位),
double dxResizeRelative
(x 方向的调整大小),
double dyResizeRelative
(y 方向的调整大小),
bool isTransparent
(组合框为 true
),
该方法定义了在对话框调整大小时对控件执行的操作。
如果您想使用 **ResizableDlg 模块**,请点击这里。
背景
- GDI+(图形、位图、图元文件)
- GDI(内存设备上下文、客户设备上下文)
- 仅资源 DLL
- 可调整大小的对话框
参考
1. Picture Control - Tobias Eiseler - CodeProject
2. Working with Device Contexts - Marius Bancila - CodeGuru
3. Creating Resource-Only DLL - Microsoft - MSDN
4. Icons - John Hornick - MSDN
使用代码
文件“Source.zip”包含“ImageControl”静态库项目。
您可以在您的项目中这样使用此库:
1) 在“项目->属性->配置属性->C/C++->常规->附加包含目录”下
添加适当的相对路径“..\ImageControl\inc”,以定位“ImageCtrl.h”、“ResourceList.h”和“Icon.h”文件。
2) 在“项目->属性->配置属性->链接器->常规->附加库目录”下
添加适当的相对路径“..\ImageControl\x64\Release”,以定位“ImageControl.lib”文件。
3) 在“项目->属性->配置属性->链接器->输入->附加依赖项”下
添加“ImageControl.lib”文件。
4)
4.1) 如果您想使用 **ImageCtrl** 模块……
4.1.1) 包含头文件“ImageCtrl.h”并在您的对话框类中声明一个 CImageCtrl
类型的变量。
#include "ImageCtrl.h"
class CImageControlDlg : public CDialogEx
{
public:
CImageControlDlg(CWnd* pParent = NULL); // standard constructor
enum { IDD = IDD_IMAGECONTROL_DIALOG };
private:
CImageCtrl m_image;
HICON m_hIcon;
protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support
virtual BOOL OnInitDialog();
afx_msg void OnCbnSelchangeComboLoadOption();
afx_msg void OnCbnSelchangeComboResize();
afx_msg void OnBnClickedButtonLoadimage();
afx_msg void OnBnClickedButtonAbout();
DECLARE_MESSAGE_MAP()
};
4.1.2) 子类化您的 CStatic 控件,它将用于显示图像。
void CImageControlDlg::DoDataExchange(CDataExchange* pDX)
{
CDialogEx::DoDataExchange(pDX);
DDX_Control(pDX, IDC_STATIC, m_image);
}
4.1.3) 调用四个重载方法之一 m_image.load()
将图像加载到控件中。
4.2) 如果您只想使用 **ResourceList 模块**,请点击这里。
4.3) 如果您只想使用 **Icon 模块**,请点击这里。
文件“Source.zip”包含“StandardLibrary”静态库项目。
您可以在您的项目中这样使用此库:
1) 在“项目->属性->配置属性->C/C++->常规->附加包含目录”下
添加适当的相对路径“..\StandardLibrary\inc”,以定位“ResizableDlg.h”。
2) 在“项目->属性->配置属性->链接器->常规->附加库目录”下
添加适当的相对路径“..\StandardLibrary\x64\Release”,以定位 “StandardLibrary.lib”文件。
3) 在“项目->属性->配置属性->链接器->输入->附加依赖项”下
添加“StandardLibrary.lib”文件。
4) 包含头文件“ResizableDlg.h”并将您的对话框类派生自 CResizableDlg
。
#include "ImageCtrl.h"
#include "ResizableDlg.h"
class CImageControlDlg : public CResizableDlg
{
public:
CImageControlDlg(CWnd* pParent = NULL); // standard constructor
enum { IDD = IDD_IMAGECONTROL_DIALOG };
private:
CImageCtrl m_image;
HICON m_hIcon;
protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support
virtual BOOL OnInitDialog();
afx_msg void OnCbnSelchangeComboLoadOption();
afx_msg void OnCbnSelchangeComboResize();
afx_msg void OnBnClickedButtonLoadimage();
afx_msg void OnBnClickedButtonAbout();
DECLARE_MESSAGE_MAP()
};
5) 像这样定义构造函数:
CImageControlDlg::CImageControlDlg(CWnd* pParent /*=NULL*/) : CResizableDlg(CImageControlDlg::IDD, 400, 490, pParent), m_pRes(NULL), m_isOnEnChangeEditWidthHeight(true) { m_hIcon = AfxGetApp()->LoadIcon(IDI_ICON_OCEANWAVE); m_rl.setResourceType(CResourceList::resourceType::RESOURCE_IMAGE); // m_rl.setResourceType((UINT)RT_GROUP_ICON); }
其中 400 表示最小对话框宽度,490 表示最小对话框高度。如果这些参数中的任何一个为 0,则最小对话框宽度/高度将设置为初始对话框宽度/高度值。
6) 在 OnInitDialog()
开始时,为每个需要重新定位和/或调整大小的对话框控件添加方法 CResizableDlg::moveResizeControl(...)
,然后在之后调用 CResizableDlg::OnInitDialog()
。
BOOL CImageControlDlg::OnInitDialog() { moveResizeControl(IDC_STATIC_IMAGEFRAME, tControl::anchor::TOP_LEFT, 0, 0, 1, 1); moveResizeControl(IDC_STATIC_LOADOPTION, tControl::anchor::TOP_LEFT, 1, 0, 0, 0); moveResizeControl(IDC_COMBO_LOADOPTION, tControl::anchor::TOP_LEFT, 1, 0, 0, 0); moveResizeControl(IDC_GROUP_RESOURCE, tControl::anchor::TOP_LEFT, 1, 0, 0, 0, true); moveResizeControl(IDC_STATIC_RESFILE, tControl::anchor::TOP_LEFT, 1, 0, 0, 0); moveResizeControl(IDC_EDIT_RESFILE, tControl::anchor::TOP_LEFT, 1, 0, 0, 0); moveResizeControl(IDC_BUTTON_RESFILE, tControl::anchor::TOP_LEFT, 1, 0, 0, 0); moveResizeControl(IDC_STATIC_RESLOCALE, tControl::anchor::TOP_LEFT, 1, 0, 0, 0); moveResizeControl(IDC_COMBO_RESLOCALE, tControl::anchor::TOP_LEFT, 1, 0, 0, 0); moveResizeControl(IDC_STATIC_RESTYPE, tControl::anchor::TOP_LEFT, 1, 0, 0, 0); moveResizeControl(IDC_COMBO_RESTYPE, tControl::anchor::TOP_LEFT, 1, 0, 0, 0); moveResizeControl(IDC_STATIC_RESNAME, tControl::anchor::TOP_LEFT, 1, 0, 0, 0); moveResizeControl(IDC_COMBO_RESNAME, tControl::anchor::TOP_LEFT, 1, 0, 0, 0); moveResizeControl(IDC_BUTTON_LOADIMAGE, tControl::anchor::TOP_LEFT, 1, 0, 0, 0); moveResizeControl(IDC_STATIC_GDIPLUS, tControl::anchor::TOP_LEFT, 1, 0, 0, 0); moveResizeControl(IDC_GROUP_SIZEPOSITION, tControl::anchor::TOP_LEFT, 1, 0, 0, 0, true); moveResizeControl(IDC_STATIC_SIZETYPE, tControl::anchor::TOP_LEFT, 1, 0, 0, 0); moveResizeControl(IDC_COMBO_SIZETYPE, tControl::anchor::TOP_LEFT, 1, 0, 0, 0); moveResizeControl(IDC_STATIC_WIDTH, tControl::anchor::TOP_LEFT, 1, 0, 0, 0); moveResizeControl(IDC_EDIT_WIDTH, tControl::anchor::TOP_LEFT, 1, 0, 0, 0); moveResizeControl(IDC_STATIC_WIDTHUNITS, tControl::anchor::TOP_LEFT, 1, 0, 0, 0); moveResizeControl(IDC_STATIC_HEIGHT, tControl::anchor::TOP_LEFT, 1, 0, 0, 0); moveResizeControl(IDC_EDIT_HEIGHT, tControl::anchor::TOP_LEFT, 1, 0, 0, 0); moveResizeControl(IDC_STATIC_HEIGHTUNITS, tControl::anchor::TOP_LEFT, 1, 0, 0, 0); moveResizeControl(IDC_CHECK_MAR, tControl::anchor::TOP_LEFT, 1, 0, 0, 0); moveResizeControl(IDC_STATIC_ALIGNMENT, tControl::anchor::TOP_LEFT, 1, 0, 0, 0); moveResizeControl(IDC_COMBO_ALIGNMENT, tControl::anchor::TOP_LEFT, 1, 0, 0, 0); moveResizeControl(IDC_CHECK_PAN, tControl::anchor::TOP_LEFT, 1, 0, 0, 0); moveResizeControl(IDC_CHECK_ZOOM, tControl::anchor::TOP_LEFT, 1, 0, 0, 0); moveResizeControl(IDC_BUTTON_SAVEICONAS, tControl::anchor::TOP_LEFT, 1, 0, 0, 0); moveResizeControl(IDC_BUTTON_SAVEAS, tControl::anchor::TOP_LEFT, 1, 0, 0, 0); moveResizeControl(IDOK, tControl::anchor::TOP_LEFT, 1, 1, 0, 0); moveResizeControl(IDC_BUTTON_ABOUT, tControl::anchor::TOP_LEFT, 1, 1, 0, 0); CResizableDlg::OnInitDialog(); return TRUE; }
文件“Demo.zip”包含 4 个项目:
- [LIB]/ImageControl(静态库项目),
- [LIB]/StandardLibrary(静态库项目),
- [Resource]/ImageControlRes(仅资源 DLL,包含所有图像),
- ImageControl Demo(演示项目)。
如何使用它?
1) 解压“Demo.zip”文件。
2) 生成所有四个项目。
3) 运行应用程序“ImageControl Demo.exe”。请记住,所有图像文件都位于“[Resource]/ImageControlRes/res”文件夹下,并且仅资源文件“ImageControlRes.dll”位于“[Resource]/ImageControlRes/x64/Release”下。
历史记录
版本 1.5:发布于 2016-06-18
新增附加功能
- 支持可调整大小的对话框,定义了最小对话框窗口尺寸并记住最后一个窗口位置。
添加了“StandardLibrary”(静态库项目),支持在对话框大小调整时重新定位和/或调整对话框控件的大小。
CImageControlDlg
现在派生自 CResizableDlg (
,以CDialogEx)
支持这些可调整大小的对话框功能。
版本 1.4:发布于 2015-11-12
CImageCtrl
类中的图像对象m_pImage
被发现是多余的,因此被移除,同时 Bitmap 对象m_pBmp
取代了它的位置。
新增附加功能
- 从任何图像创建自定义大小的 32 位图标,
- 导出资源图像,
- 平移和缩放资源图像。
版本 1.3:发布于 2015-10-28
- 缩放操作已重新定义,精度更高。现在,即使鼠标位于图像矩形之外(但在图像客户区内)也可以执行。
版本 1.2:发布于 2015-10-24
新增附加功能
- 导入“位图”资源,
- 从任意多语言(资源)dll 或 exe 导入图像,
- 自定义调整大小,
- 平移,
- 缩放,
- 导出图像,
- 提取资源图标。
版本 1.1 发布于 2015-10-08
CImageCtrl::DrawItem()
中的图像对象image
已被替换为指向 Image 的m_pImage
指针,并移至CImageCtrl
类。现在它仅在每个图像周期中初始化一次 - 无论调用CImageCtrl::DrawItem()
多少次!- 通过移除
Read()/Write()
调用优化了函数load(CString szFilePath)
。 - 通过移除 IStream 副本优化了函数
load(IStream* piStream)
。
版本 1.0:发布于 2015-10-06