2

I use Visual Studio 2015, working with MFC Multiple document Application (Ribbon Style). I'm trying to add a png images to CView and make a slideshow using WM_TIMER. First I made dialog based application with the same purpose, it works perfectly. The difference between those applications is that images are drawn in first app in dialog window in PictureControl (CStatic), adding by toolbox. And in the second app I'm trying to add image to CStatic in CView exactly at the same way. But with CView it doesn't redraw correctly. Only when I change window size (stretch, maximize it) the png image changes, but when I stop resizing window, an image freezes again.

Creating CStatic control.

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);
}

Redrawing by timer and 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);
    }
}

Redrawing 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 is called by timer correctly. Why images are displayed only when the main window was resized?

Nika_Rika
  • 613
  • 2
  • 6
  • 29
  • I suggest you to try some things with `ON_WM_ERASEBKGND()` and its handler `OnEraseBkgnd(CDC* pDC)`. May be to return the opposite value than its default parent class implementation. – sergiol Aug 24 '16 at 13:41
  • Incidentally, why do you destroy and re-create `m_ctrlImage` on every resize? Why not simply resize it as well ([CWnd::SetWindowPos](https://msdn.microsoft.com/en-us/library/a1yzfz6d.aspx))? – IInspectable Aug 24 '16 at 15:09

1 Answers1

3
void CCardioAppView::OnPaint()
{
    CPaintDC view_dc(this); 
    ...
    CPaintDC dc(&m_ctrlImage); //<== wrong place
    ...
}

CPaintDC is a wrapper for BeginPaint/EndPaint in response to WM_PAINT message. It can't be used to get device context from another window.

To draw on the static control you have to make owner draw control. But that's not necessary here. You can just use the picture control and simply call 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);
    }
}

Don't override OnPaint() and OnSize() with this method.


Second option: CStatic will not stretch the bitmap. You can use CPictureHolder to stretch the bitmap. Use the example below (don't create any m_ctrlImage/CStatic control)

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);
    }
}

Third option: If you want to do your own painting, then use the window's own DC. Example:

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();
}
Barmak Shemirani
  • 30,904
  • 6
  • 40
  • 77
  • Live and learn! Thank you a lot! Works with all 3 metods: with SetBitmap, CPaintDC dc(this) and CPictureHolder. Almost perfectly, exept one thing: blinking. Often image change is accompanied with blinking in all 3 methods. In dialog based there is no any image blinking. There I use CPaintDC dc(&m_ctrlImage); and with CPaintDC dc(this) it doesn't even work there... – Nika_Rika Aug 25 '16 at 07:39
  • 1
    It could flicker during resize. Is that what you mean? Or remove `dc.FillSolidRect(rect, RGB(255, 255, 255))`, see if that's the cause. Change `Invalidate(TRUE)` to `Invalidate(FALSE)` – Barmak Shemirani Aug 25 '16 at 08:18
  • I don't use FillSolidRect now as I choose the method with CPictureHolder. Invalidate(FALSE) worked. Thanks! – Nika_Rika Aug 25 '16 at 08:36
  • 1
    By the way, I don't know why I suggested `CPictureHolder`. You can just use `CImage` to draw the image instead. You can also use `CImage` to load png files. – Barmak Shemirani Aug 26 '16 at 23:33