【问题标题】:Visual C++ How to redraw image in CView with CPaintDC, CDCVisual C++ 如何使用 CPaintDC、CDC 在 CView 中重绘图像
【发布时间】:2016-12-31 09:11:35
【问题描述】:

我使用 Visual Studio 2015,使用 MFC 多文档应用程序(功能区样式)。 我正在尝试将 png 图像添加到 CView 并使用 WM_TIMER 制作幻灯片。 首先,我制作了具有相同目的的基于对话框的应用程序,它运行良好。这些应用程序之间的区别在于,图像是在 PictureControl (CStatic) 对话框窗口中的第一个应用程序中绘制的,由工具箱添加。在第二个应用程序中,我试图以完全相同的方式将图像添加到 CView 中的 CStatic。但是使用 CView 它不能正确重绘。只有当我改变窗口大小(拉伸,最大化)时,png图像才会改变,但是当我停止调整窗口大小时,图像会再次冻结。

创建 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 被计时器正确调用。为什么只有在调整主窗口大小时才显示图像?

【问题讨论】:

  • 我建议您使用ON_WM_ERASEBKGND() 及其处理程序OnEraseBkgnd(CDC* pDC) 尝试一些事情。可能是返回与其默认父类实现相反的值。
  • 顺便说一句,为什么每次调整大小时都要销毁并重新创建m_ctrlImage?为什么不简单地调整它的大小 (CWnd::SetWindowPos)?

标签: c++ mfc png redraw cdc


【解决方案1】:
void CCardioAppView::OnPaint()
{
    CPaintDC view_dc(this); 
    ...
    CPaintDC dc(&m_ctrlImage); //<== wrong place
    ...
}

CPaintDCBeginPaint/EndPaint 的包装器,用于响应WM_PAINT 消息。它不能用于从另一个窗口获取设备上下文。

要在静态控件上绘图,您必须让所有者绘制控件。但这不是必需的。您可以只使用图片控件,只需调用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);
    }
}

第三个选项: 如果你想做自己的绘画,那么使用窗口自己的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();
}

【讨论】:

  • 生活和学习!万分感谢!适用于所有 3 种方法:使用 SetBitmap、CPaintDC dc(this) 和 CPictureHolder。几乎完美,除了一件事:眨眼。在所有 3 种方法中,图像变化通常伴随着闪烁。在基于对话框的情况下,没有任何图像闪烁。我在那里使用 CPaintDC dc(&m_ctrlImage);并且使用 CPaintDC dc(this) 它甚至无法在那里工作......
  • 调整大小时可能会闪烁。你是这个意思吗?或者删除dc.FillSolidRect(rect, RGB(255, 255, 255)),看看是不是这个原因。将Invalidate(TRUE) 更改为Invalidate(FALSE)
  • 我现在不使用 FillSolidRect,因为我选择了带有 CPictureHolder 的方法。无效(FALSE)工作。谢谢!
  • 顺便说一句,我不知道我为什么建议CPictureHolder。您可以只使用CImage 来绘制图像。你也可以使用CImage来加载png文件。
猜你喜欢
  • 2013-10-05
  • 1970-01-01
  • 1970-01-01
  • 2015-10-12
  • 1970-01-01
  • 2012-01-22
  • 1970-01-01
  • 1970-01-01
  • 2014-12-10
相关资源
最近更新 更多