2

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?

cbuchart
  • 10,847
  • 9
  • 53
  • 93
  • have you tried invalidating **window area containing slider** with `InvalidateRect`? or try invalidating whole client area of window to see if it solves problem. It is a long time since I have written MFC code, but I have a feeling that you need to invalidate window, not control, in your case. – Afshin Jul 07 '21 at 07:35
  • @Afshin yes, I've tried to invalidate it using `slider.GetWindowRect()` and then in the main application window calling `InvalidateRect()`, both with screen and client coordinates, and nothing. – cbuchart Jul 07 '21 at 08:02
  • Do you do this in a CView derived class? The best is to destroy the view (without destroying the attached document, and recreate the view. – Tom Tom Jul 07 '21 at 13:22
  • Not exactly, I forgot to mention I'm wrapping the slider into a `CMFCToolBarButton` in order to display it in the toolbar. I've updated the question to reflect this. – cbuchart Jul 07 '21 at 13:44
  • Not sure, but can you try whether adding the `RDW_FRAME` flag to the `RedrawWindow` call changes anything? If that doesn't work, how about calling [`SetWindowPos`](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setwindowpos) with the `RDW_FRAMECHANGED` flag? I seem to recall that either one applies some sort of fixup required when changing themes, though I could be misremembering. – IInspectable Jul 07 '21 at 13:58
  • @IInspectable thanks, I have tried both suggestions in all three controls (slider, wrapper, toolbar) and nothing. Actually, when tried the `SetWindowsPos` without `SWP_NOMOVE` and `SWP_NOSIZE` then slider actually moved, but it didn't redraw correctly. Maybe there is some drawing buffer cached that's not being marked as dirty by any of the operations I've tried so far? – cbuchart Jul 08 '21 at 07:06

1 Answers1

1

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.

cbuchart
  • 10,847
  • 9
  • 53
  • 93