Force call to OnCustomDraw with CDDS_ITEMPREPAINT

Viewed 126

I have a custom slider in a toolbar. In order to show the slider in the toolbar it is wrapped in a CMFCToolBarButton derived class.

The slider uses OnCustomDraw to perform its rendering:

BEGIN_MESSAGE_MAP(CCustomSlider, CSliderCtrl)
  ON_NOTIFY_REFLECT(NM_CUSTOMDRAW, OnCustomDraw)
END_MESSAGE_MAP()

void CCustomSlider::OnCustomDraw(NMHDR* pNMHDR, LRESULT* pResult)
{
  const auto lpcd = (LPNMCUSTOMDRAW)pNMHDR;
  std::cout << "Draw stage: " << lpcd->dwDrawStage << '\n'; // for debugging purposes

  switch (lpcd->dwDrawStage) {
  case CDDS_PREPAINT:
    *pResult = CDRF_NOTIFYITEMDRAW;
    break;
  case CDDS_ITEMPREPAINT: // custom drawing done here
    switch (lpcd->dwItemSpec) {
      // ...
    }
    break;
  }
}

(For those interested in customizing a slider, I followed this tutorial).

At some point of my application I have to force redrawing the slider because the user changes its visual style.

I've tried the following (including combinations) without succeed:

  • slider.Invalidate();
  • slider.GetOwner()->SendMessage(WM_COMMAND, m_nID); (m_nID is the command ID the slider sends)
  • slider.SendNotifyMessageA(NULL, NULL, RDW_ALLCHILDREN | RDW_INVALIDATE | RDW_UPDATENOW | RDW_ERASE);

The same with the wrapper. I've also tried forcing the toolbar to be redrawn:

toolbar.AdjustLayout();
toolbar.Invalidate();
toolbar.RedrawWindow(NULL, NULL, RDW_ALLCHILDREN | RDW_INVALIDATE | RDW_UPDATENOW | RDW_ERASE);

The rest of the application is refreshing correctly when the visual style changes (including the toolbar), but not the slider.

The slider updates correctly if I resize the window.

As far as I've been able to find, it seems that I'm not sending the correct message to the OnCustomDraw. When redrawn by myself it shows the draw stage is CDDS_PREPAINT, while on the resize it's called several times, some of them including the CDDS_ITEMPREPAINT, so it seems I'm only calling for a general update without indicating the specific items, but I don't know how to do it.

In overall question is then: How can I force a redraw of this control?

1 Answers

So far the only way I've found to force a redrawing is to use the CSliderCtrl::SetRangeMin or CSliderCtrl::SetRangeMax methods, that receive an optional parameter BOOL bRedraw:

slider.SetRangeMin(slider.GetRangeMin(), TRUE);

This hack is good enough for solving the problem I have, but looks like a bit dirty and indirect.

Taken from MFC's source code:

_AFXCMN_INLINE void CSliderCtrl::SetRangeMin(_In_ int nMin, _In_ BOOL bRedraw)
{ ASSERT(::IsWindow(m_hWnd)); ::SendMessage(m_hWnd, TBM_SETRANGEMIN, bRedraw, nMin); }

What I haven't been able to find is what it is called under-the-hood when bRedraw = TRUE.

Related