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
    ...
}

CPaintDCBeginPaint/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();
}