Visual C++ 如何使用 CPaintDC、CDC 在 CView 中重绘图像
Visual C++ How to redraw image in CView with CPaintDC, CDC
我使用 Visual Studio 2015,使用 MFC 多文档应用程序(功能区样式)。
我正在尝试将 png 图像添加到 CView 并使用 WM_TIMER 制作幻灯片。
首先,我出于相同的目的制作了基于对话框的应用程序,它运行良好。这些应用程序之间的区别在于,图像是在 PictureControl (CStatic) 的对话框 window 中的第一个应用程序中绘制的,由工具箱添加。在第二个应用程序中,我试图以完全相同的方式将图像添加到 CView 中的 CStatic。但是对于 CView,它不能正确重绘。只有当我更改 window 大小时(拉伸、最大化它),png 图像才会改变,但是当我停止调整 window 大小时,图像会再次冻结。
正在创建 CStatic 控件。
void CCardioAppView::OnInitialUpdate()
{
CView::OnInitialUpdate();
CRect rect;
GetClientRect(rect);
BOOL b = m_ctrlImage.Create(_T(""), WS_CHILD | WS_VISIBLE, rect,this,2);
m_ctrlImage.ModifyStyle(0, SS_BITMAP, SWP_NOSIZE);
}
通过定时器和 OnSize() 重绘
void CCardioAppView::OnTimer(UINT_PTR nIDEvent)
{
if (ShowImageTimer == nIDEvent)
{
auto bmp_iter = theApp.FullBmpMap.begin();
int sz = theApp.FullBmpMap.size();
CRect ImageRect;
GetClientRect(&ImageRect);
if (m_iCurrentImage < sz)
{
m_iCurrentImage++;
InvalidateRect(ImageRect, false);
}
else
{
m_iCurrentImage = 1;
}
}
CView::OnTimer(nIDEvent);
}
void CCardioAppView::OnSize(UINT nType, int cx, int cy)
{
CView::OnSize(nType, cx, cy);
CRect rect;
if (m_ctrlImage.GetSafeHwnd())
{
GetClientRect(rect);
m_ctrlImage.DestroyWindow();
BOOL b = m_ctrlImage.Create(_T(""), WS_CHILD | WS_VISIBLE, rect, this, 2);
m_ctrlImage.ModifyStyle(0, SS_BITMAP);
}
}
重绘 OnPaint()
void CCardioAppView::OnPaint()
{
CPaintDC view_dc(this); // device context for painting
CBitmap bmp;
CRect rect, scaleRect;
BITMAP b;
auto bmp_iter = theApp.FullBmpMap.find(m_iCurrentImage);
GetClientRect(&rect);
if (bmp_iter == theApp.FullBmpMap.end()) return;
bmp.Attach((*bmp_iter).second);
bmp.GetObject(sizeof(BITMAP), &b);
CPaintDC dc(&m_ctrlImage);
CDC memdc;
memdc.CreateCompatibleDC(&dc);
memdc.SelectObject(&bmp);
if (rect.Height() <= b.bmHeight) //scaling image
{
scaleRect = rect;
scaleRect.right = rect.left + ((b.bmWidth*rect.Height())/ b.bmHeight);
}
dc.FillSolidRect(rect, RGB(255, 255, 255));
dc.StretchBlt(0, 0, scaleRect.Width(), scaleRect.Height(), &memdc,
0, 0, b.bmWidth, b.bmHeight, SRCCOPY);
//dc.MoveTo(0, 0);
(*bmp_iter).second.Detach();
(*bmp_iter).second.Attach(bmp);
bmp.Detach();
}
OnPaint 被计时器正确调用。为什么仅在主 window 调整大小时才显示图像?
void CCardioAppView::OnPaint()
{
CPaintDC view_dc(this);
...
CPaintDC dc(&m_ctrlImage); //<== wrong place
...
}
CPaintDC
是 BeginPaint/EndPaint
的包装器,用于响应 WM_PAINT
消息。它不能用于从另一个 window 获取设备上下文。
要绘制静态控件,您必须让所有者绘制控件。但这里没有必要。您可以只使用图片控件并简单地调用 CStatic::SetBitmap
void CCardioAppView::OnTimer(UINT_PTR nIDEvent)
{
CView::OnTimer(nIDEvent);
if (ShowImageTimer == nIDEvent)
{
m_iCurrentImage++;
if (m_iCurrentImage >= theApp.FullBmpMap.size())
m_iCurrentImage = 0;
//get HBITMAP: ???
auto bmp_iter = theApp.FullBmpMap.find(m_iCurrentImage);
HBITAMP hbitmap = bmp_iter->second; //???
m_ctrlImage.SetBitmap(hbitmap);
}
}
不要用此方法覆盖 OnPaint()
和 OnSize()
。
第二个选项:
CStatic
不会拉伸位图。您可以使用 CPictureHolder
来拉伸位图。使用下面的示例(不要创建任何 m_ctrlImage
/CStatic
控件)
void OnPaint()
{
CPaintDC dc(this);
auto bmp_iter = theApp.FullBmpMap.find(m_iCurrentImage);
HBITAMP hbitmap = bmp_iter->second; //???
CPictureHolder pic;
pic.CreateFromBitmap(hbitmap);
CRect rect;
GetClientRect(&rect);
pic.Render(&dc, rect, rect);
}
void CCardioAppView::OnTimer(UINT_PTR nIDEvent)
{
CView::OnTimer(nIDEvent);
if (nIDEvent == ShowImageTimer)
{
m_iCurrentImage++;
if (m_iCurrentImage >= theApp.FullBmpMap.size())
m_iCurrentImage = 0;
Invalidate(TRUE);
}
}
第三个选项:
如果你想自己画,那就用window自己的DC。示例:
void CCardioAppView::OnPaint()
{
CPaintDC dc(this);
CRect rect;
GetClientRect(&rect);
auto bmp_iter = theApp.FullBmpMap.find(m_iCurrentImage);
HBITAMP hbitmap = bmp_iter->second; //???
CBitmap bmp;
bmp.Attach(hbitmap);
CDC memdc;
memdc.CreateCompatibleDC(&dc);
memdc.SelectObject(&bmp);
BITMAP b;
bmp.GetObject(sizeof(BITMAP), &b);
CRect scaleRect = rect;
if (rect.Height() <= b.bmHeight)
{
scaleRect = rect;
scaleRect.right = rect.left + ((b.bmWidth*rect.Height()) / b.bmHeight);
}
dc.FillSolidRect(rect, RGB(255, 255, 255));
dc.StretchBlt(0, 0, scaleRect.Width(), scaleRect.Height(), &memdc,
0, 0, b.bmWidth, b.bmHeight, SRCCOPY);
bmp.Detach();
}
我使用 Visual Studio 2015,使用 MFC 多文档应用程序(功能区样式)。 我正在尝试将 png 图像添加到 CView 并使用 WM_TIMER 制作幻灯片。 首先,我出于相同的目的制作了基于对话框的应用程序,它运行良好。这些应用程序之间的区别在于,图像是在 PictureControl (CStatic) 的对话框 window 中的第一个应用程序中绘制的,由工具箱添加。在第二个应用程序中,我试图以完全相同的方式将图像添加到 CView 中的 CStatic。但是对于 CView,它不能正确重绘。只有当我更改 window 大小时(拉伸、最大化它),png 图像才会改变,但是当我停止调整 window 大小时,图像会再次冻结。
正在创建 CStatic 控件。
void CCardioAppView::OnInitialUpdate()
{
CView::OnInitialUpdate();
CRect rect;
GetClientRect(rect);
BOOL b = m_ctrlImage.Create(_T(""), WS_CHILD | WS_VISIBLE, rect,this,2);
m_ctrlImage.ModifyStyle(0, SS_BITMAP, SWP_NOSIZE);
}
通过定时器和 OnSize() 重绘
void CCardioAppView::OnTimer(UINT_PTR nIDEvent)
{
if (ShowImageTimer == nIDEvent)
{
auto bmp_iter = theApp.FullBmpMap.begin();
int sz = theApp.FullBmpMap.size();
CRect ImageRect;
GetClientRect(&ImageRect);
if (m_iCurrentImage < sz)
{
m_iCurrentImage++;
InvalidateRect(ImageRect, false);
}
else
{
m_iCurrentImage = 1;
}
}
CView::OnTimer(nIDEvent);
}
void CCardioAppView::OnSize(UINT nType, int cx, int cy)
{
CView::OnSize(nType, cx, cy);
CRect rect;
if (m_ctrlImage.GetSafeHwnd())
{
GetClientRect(rect);
m_ctrlImage.DestroyWindow();
BOOL b = m_ctrlImage.Create(_T(""), WS_CHILD | WS_VISIBLE, rect, this, 2);
m_ctrlImage.ModifyStyle(0, SS_BITMAP);
}
}
重绘 OnPaint()
void CCardioAppView::OnPaint()
{
CPaintDC view_dc(this); // device context for painting
CBitmap bmp;
CRect rect, scaleRect;
BITMAP b;
auto bmp_iter = theApp.FullBmpMap.find(m_iCurrentImage);
GetClientRect(&rect);
if (bmp_iter == theApp.FullBmpMap.end()) return;
bmp.Attach((*bmp_iter).second);
bmp.GetObject(sizeof(BITMAP), &b);
CPaintDC dc(&m_ctrlImage);
CDC memdc;
memdc.CreateCompatibleDC(&dc);
memdc.SelectObject(&bmp);
if (rect.Height() <= b.bmHeight) //scaling image
{
scaleRect = rect;
scaleRect.right = rect.left + ((b.bmWidth*rect.Height())/ b.bmHeight);
}
dc.FillSolidRect(rect, RGB(255, 255, 255));
dc.StretchBlt(0, 0, scaleRect.Width(), scaleRect.Height(), &memdc,
0, 0, b.bmWidth, b.bmHeight, SRCCOPY);
//dc.MoveTo(0, 0);
(*bmp_iter).second.Detach();
(*bmp_iter).second.Attach(bmp);
bmp.Detach();
}
OnPaint 被计时器正确调用。为什么仅在主 window 调整大小时才显示图像?
void CCardioAppView::OnPaint()
{
CPaintDC view_dc(this);
...
CPaintDC dc(&m_ctrlImage); //<== wrong place
...
}
CPaintDC
是 BeginPaint/EndPaint
的包装器,用于响应 WM_PAINT
消息。它不能用于从另一个 window 获取设备上下文。
要绘制静态控件,您必须让所有者绘制控件。但这里没有必要。您可以只使用图片控件并简单地调用 CStatic::SetBitmap
void CCardioAppView::OnTimer(UINT_PTR nIDEvent)
{
CView::OnTimer(nIDEvent);
if (ShowImageTimer == nIDEvent)
{
m_iCurrentImage++;
if (m_iCurrentImage >= theApp.FullBmpMap.size())
m_iCurrentImage = 0;
//get HBITMAP: ???
auto bmp_iter = theApp.FullBmpMap.find(m_iCurrentImage);
HBITAMP hbitmap = bmp_iter->second; //???
m_ctrlImage.SetBitmap(hbitmap);
}
}
不要用此方法覆盖 OnPaint()
和 OnSize()
。
第二个选项:
CStatic
不会拉伸位图。您可以使用 CPictureHolder
来拉伸位图。使用下面的示例(不要创建任何 m_ctrlImage
/CStatic
控件)
void OnPaint()
{
CPaintDC dc(this);
auto bmp_iter = theApp.FullBmpMap.find(m_iCurrentImage);
HBITAMP hbitmap = bmp_iter->second; //???
CPictureHolder pic;
pic.CreateFromBitmap(hbitmap);
CRect rect;
GetClientRect(&rect);
pic.Render(&dc, rect, rect);
}
void CCardioAppView::OnTimer(UINT_PTR nIDEvent)
{
CView::OnTimer(nIDEvent);
if (nIDEvent == ShowImageTimer)
{
m_iCurrentImage++;
if (m_iCurrentImage >= theApp.FullBmpMap.size())
m_iCurrentImage = 0;
Invalidate(TRUE);
}
}
第三个选项: 如果你想自己画,那就用window自己的DC。示例:
void CCardioAppView::OnPaint()
{
CPaintDC dc(this);
CRect rect;
GetClientRect(&rect);
auto bmp_iter = theApp.FullBmpMap.find(m_iCurrentImage);
HBITAMP hbitmap = bmp_iter->second; //???
CBitmap bmp;
bmp.Attach(hbitmap);
CDC memdc;
memdc.CreateCompatibleDC(&dc);
memdc.SelectObject(&bmp);
BITMAP b;
bmp.GetObject(sizeof(BITMAP), &b);
CRect scaleRect = rect;
if (rect.Height() <= b.bmHeight)
{
scaleRect = rect;
scaleRect.right = rect.left + ((b.bmWidth*rect.Height()) / b.bmHeight);
}
dc.FillSolidRect(rect, RGB(255, 255, 255));
dc.StretchBlt(0, 0, scaleRect.Width(), scaleRect.Height(), &memdc,
0, 0, b.bmWidth, b.bmHeight, SRCCOPY);
bmp.Detach();
}