0

I am learning WIN32 API in C/C++ and want to know how to create custom slider/trackbar control with custom shaped thumb that is a child of a dialog. An example would be of benefit on how to do it as there is very low information about WIN32 programming on the internet, especially for customizing your own child controls. Please do not post MFC examples.

Here is the code example I am trying to complete:

#pragma comment(linker,"\"/manifestdependency:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")

#include <string>
#include <Windows.h>
#include <CommCtrl.h>

using namespace std;

HWND window = nullptr;
HWND trackBar = nullptr;
HWND progressBar = nullptr;
HWND staticText = nullptr;
WNDPROC defWndProc = nullptr;

static HBITMAP hBitmapThumb, hBitmapBar;
static BITMAP bm;

LRESULT OnWindowClose(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
    PostQuitMessage(0);
    return CallWindowProc(defWndProc, hwnd, message, wParam, lParam);
}

LRESULT OnTrackBarChanged(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
    LRESULT value = SendMessage(trackBar, TBM_GETPOS, 0, 0);
    SendMessage(progressBar, PBM_SETPOS, value, 0);
    SendMessage(staticText, WM_SETTEXT, 0, reinterpret_cast<LPARAM>(to_wstring(value).c_str()));
    return CallWindowProc(defWndProc, hwnd, message, wParam, lParam);
}

void DrawBitmapTransparent(HDC hDCDest, int nXDest, int nYDest, int nBitmapWidth, int nBitmapHeight, HBITMAP hBitmap, int nXSrc, int nYSrc, int nTransparentColor)
{
    HDC hDCSrc;
    HBITMAP hBitmapOld;
    HDC hDCMask;
    HBITMAP hBitmapMask;
    HBITMAP hBitmapMaskOld;
    HDC hDCMem;
    HBITMAP hBitmapMem;
    HBITMAP hBitmapMemOld;
    int nBkColorOld;
    int nTextColorOld;
    BITMAP bm;

    GetObject( hBitmap, sizeof( BITMAP ), &bm );

    if (!nBitmapWidth) {
        nBitmapWidth = bm.bmWidth;
    }

    if (!nBitmapHeight) {
        nBitmapHeight = bm.bmHeight;
    }

    hDCSrc = CreateCompatibleDC( hDCDest );
    hBitmapOld = reinterpret_cast<HBITMAP>(SelectObject( hDCSrc, hBitmap ));
    hDCMask = CreateCompatibleDC( hDCDest );
    hBitmapMask = CreateBitmap( nBitmapWidth, nBitmapHeight, 1, 1, 0 );
    hBitmapMaskOld = reinterpret_cast<HBITMAP>(SelectObject( hDCMask, hBitmapMask ));
    hDCMem = CreateCompatibleDC( hDCDest );
    hBitmapMem = CreateCompatibleBitmap( hDCDest, nBitmapWidth, nBitmapHeight );
    hBitmapMemOld = reinterpret_cast<HBITMAP>(SelectObject( hDCMem, hBitmapMem ));
    nBkColorOld = SetBkColor( hDCSrc, nTransparentColor );
    BitBlt( hDCMask, 0, 0, nBitmapWidth, nBitmapHeight, hDCSrc, nXSrc, nYSrc, SRCCOPY );
    SetBkColor( hDCSrc, nBkColorOld );
    nBkColorOld = SetBkColor( hDCDest, RGB(255,255,255) );
    nTextColorOld = SetTextColor( hDCDest, RGB(0,0,0) );
    BitBlt( hDCMem, 0, 0, nBitmapWidth, nBitmapHeight, hDCDest, nXDest, nYDest, SRCCOPY );
    BitBlt( hDCMem, 0, 0, nBitmapWidth, nBitmapHeight, hDCSrc, nXSrc, nYSrc, SRCINVERT );
    BitBlt( hDCMem, 0, 0, nBitmapWidth, nBitmapHeight, hDCMask, 0, 0, SRCAND );
    BitBlt( hDCMem, 0, 0, nBitmapWidth, nBitmapHeight, hDCSrc, nXSrc, nYSrc, SRCINVERT );
    BitBlt( hDCDest, nXDest, nYDest, nBitmapWidth, nBitmapHeight, hDCMem, 0, 0, SRCCOPY );
    SetBkColor( hDCDest, nBkColorOld );
    SetTextColor( hDCDest, nTextColorOld );
    SelectObject( hDCMem, hBitmapMemOld );
    DeleteDC( hDCMem );
    DeleteObject( hBitmapMem );
    SelectObject( hDCMask, hBitmapMaskOld );
    DeleteDC( hDCMask );
    DeleteObject( hBitmapMask );
    SelectObject( hDCSrc, hBitmapOld );
    DeleteDC( hDCSrc );
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
switch(message) {
    case WM_NOTIFY :
    {
        LPNMHDR lpNmhdr = reinterpret_cast<LPNMHDR>(lParam);
        if (lpNmhdr->code == NM_CUSTOMDRAW)
        {
            LPNMCUSTOMDRAW lpNMCustomDraw = reinterpret_cast<LPNMCUSTOMDRAW>(lParam);

            if (lpNMCustomDraw->dwDrawStage == CDDS_PREPAINT) {
                return CDRF_NOTIFYITEMDRAW;
            }

            else if (lpNMCustomDraw->dwDrawStage == CDDS_ITEMPREPAINT)
            {
                long nLeft = lpNMCustomDraw->rc.left;
                long nTop = lpNMCustomDraw->rc.top;
                long nRight = lpNMCustomDraw->rc.right;
                long nBottom = lpNMCustomDraw->rc.bottom;

                if (lpNMCustomDraw->dwItemSpec == TBCD_THUMB && hBitmapThumb)
                {
                    long nWidth = nRight - nLeft;
                    long nHeight = nBottom - nTop;

                    if (nWidth - bm.bmWidth > 0)
                    {
                        nLeft += (nWidth - bm.bmWidth)/2;
                        nWidth = bm.bmWidth;
                    }

                    if (nHeight - bm.bmHeight > 0)
                    {
                        nTop += (nHeight - bm.bmHeight) / 2;
                        nHeight = bm.bmHeight;
                    }

                    DrawBitmapTransparent(lpNMCustomDraw->hdc , nLeft, nTop, nWidth, nHeight, hBitmapThumb, 0, 0, RGB( 255, 0, 255 ));

                    return CDRF_SKIPDEFAULT ;
                }
            }
        }
    }
    break;
}
    if (message == WM_CLOSE && hwnd == window) return OnWindowClose(hwnd, message, wParam, lParam);
    if (message == WM_HSCROLL && hwnd == window && reinterpret_cast<HWND>(lParam) == trackBar) return OnTrackBarChanged(hwnd, message, wParam, lParam);
    return CallWindowProc(defWndProc, hwnd, message, wParam, lParam);
}

int main() {
    window = CreateWindowEx(0, WC_DIALOG, L"TrackBar example", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 300, 300, nullptr, nullptr, nullptr, nullptr);
    trackBar = CreateWindowEx(0, TRACKBAR_CLASS, nullptr, WS_CHILD | TBS_HORZ | TBS_BOTTOM | WS_VISIBLE | TBS_FIXEDLENGTH, 150, 10, 250, 70, window, nullptr, nullptr, nullptr);
    progressBar = CreateWindowEx(0, PROGRESS_CLASS, nullptr, WS_CHILD | PBS_SMOOTH | WS_VISIBLE, 20, 100, 200, 23, window, nullptr, nullptr, nullptr);
    staticText = CreateWindowEx(0, WC_STATIC, L"100", WS_CHILD | WS_VISIBLE, 20, 150, 100, 23, window, nullptr, nullptr, nullptr);

    defWndProc = reinterpret_cast<WNDPROC>(SetWindowLongPtr(window, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(WndProc)));

    hBitmapThumb = reinterpret_cast<HBITMAP>(LoadImage(NULL, reinterpret_cast<LPCWSTR>("pink.bmp"), IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE));
    GetObject( hBitmapThumb, sizeof( BITMAP ), &bm );

    SendMessage(trackBar, TBM_SETRANGEMIN, 1, 0);
    SendMessage(trackBar, TBM_SETRANGEMAX, 1, 200);
    SendMessage(trackBar, TBM_SETTHUMBLENGTH, bm.bmWidth*2, 0);
    SendMessage(progressBar, PBM_SETRANGE32, 0, 200);
    SendMessage(progressBar, PBM_SETPOS, 100, 0);

    ShowWindow(window, SW_SHOW);

    MSG message = { 0 };
    while (GetMessage(&message, nullptr, 0, 0))
    DispatchMessage(&message);
}

In the example I am trying to replace the trackbar thumb with a bitmap image but obviously nothing is happening, it compiles but the trackbar is very small and not replaced by the image. Please I need advice on how to solve this problem.

Sormik
  • 3
  • 3
  • 1
    A side note: using C++ for GUI is usually **not** the best way to go. Consider to use C#, and if needed C++/CLI for connecting to C++ number crunching code. – wohlstad Sep 04 '22 at 11:16
  • @wohlstad I already know that, but the question clearly states C++. Thanks anyway. – Sormik Sep 04 '22 at 11:34
  • I saw the C++ tag etc., which is why I prefixed my comment with _"A side note"_... BTW - is there a special reason you want to implement the GUI in C++ ? – wohlstad Sep 04 '22 at 11:51
  • @wohlstad The topic is a bit more nuanced than a blunt *"Use C# for GUI development"* can capture. There's some truth to it, specifically when it comes to productivity. But there are other goals or requirements to consider. Now personally, I like my GUIs snappy, and .NET's mixture of AoT and JIT compilation is noticeable, even for somewhat trivial GUIs. Startup times often don't matter (like they did on Windows 10 Mobile, for example), but when you look at Visual Studio, you probably wouldn't call its launch times desirable. – IInspectable Sep 04 '22 at 13:24
  • @IInspectable I used MFC many years ago for Windows GUI, but in the last years I always find it more convenient to use C# with WinForms for the GUI (mainly for dev tools etc.), and C++ for the number crunching etc. I was genuinely curious about the advantages of Windows C++ GUI these days, and you centainly listed some. Thanks! – wohlstad Sep 04 '22 at 14:03
  • @IInspectable You can't just take a random application and say "well, it was written in and it's slow/fast/snappy/sluggish so therefore is (not) suited". VS launch time has very little to nothing to do with it being written in language X but because of the sheer amount of work it has to do on startup. – RobIII Sep 05 '22 at 07:52
  • @RobIII I was making a statement about *platforms*, not programming languages. .NET specifically has overhead, be it JIT compilation, or the ubiquitous managed/unmanaged transitions. WinForms, wrapping the native windowing system, inevitably does more work than accessing the native windowing system directly. There's undoubtedly overhead involved. Though snappiness isn't just about speed, but also consistency. Unmanaged code tends to have more predictable performance characteristics. As for VS: It's a window, with a menu and some toolbars. No reason to *not* start in less than a second's time. – IInspectable Sep 05 '22 at 09:05
  • @IInspectable I was talking about performance too; I was explaining that being developed in doesn't -automatically- mean that performance is bad; for your specific VS example it's that it has to do A LOT of stuff on startup. A lot of managed applications can be, and are, 'snappy' - JIT and other overhead and all. As to VS being "just a window" - I disagree. VS **Code** has a much smaller footprint (and, because of it, loads much quicker in general) which kind of proves my point. VS just does a lot more on startup - just use Procmon / Regmon to get a glimpse of it. – RobIII Sep 05 '22 at 09:26
  • @RobIII I'm not denying that VS *does* a **lot** at startup, I'm just spelling out [the obvious](https://youtu.be/GC-0tCy4P1U): It **shouldn't**. It's almost like it's reliving its entire history on launch, starting from the major performance regression between VC++6 and VS.NET 2002 (which coincides with the switch from native to managed). Looking at VS Code, that's another poster child example of a platform setting a hard limit on the performance. Sure, it's not a simple as saying "platform X is *inherently* slower than Y", but when you look at examples, a pretty solid trend manifests. – IInspectable Sep 05 '22 at 10:48
  • As for the specific question here: Every cast is a lie. Some of which are inconsequential, others are fatal. Examples of the (potentially) fatal ones: `reinterpret_cast(WndProc)`, or `reinterpret_cast("pink.bmp")`. Anyway, this is not how you would want to [subclass controls](https://stackoverflow.com/q/73586945/1889329). – IInspectable Sep 05 '22 at 10:53
  • That's an entirely different discussion - whether VS does too much on startup - and has nothing to do with it being written in or it being JIT'ted or (un)managed or not. Again, all goes back to [this comment of yours](https://stackoverflow.com/questions/73598868/how-to-create-custom-slider-control-in-win32-in-c?noredirect=1#comment129969206_73598868). You can argue about VS being quick to startup or not and the cause etc. all day long, it's just not relevant to either the original question or the discussion in the comments. – RobIII Sep 05 '22 at 10:53

1 Answers1

0

I copy/pasted your code and ran it and saw the missing thumb too. Started tracing it and found that hbitmapThumb was null.... of course, because I didn't have pink.bmp! I changed that to load it from an absolute path... and noticed you missed the L on the thing:

    hBitmapThumb = reinterpret_cast<HBITMAP>(LoadImage(NULL, reinterpret_cast<LPCWSTR>(L"c:\\users\\me\\Documents\\pink.bmp"), IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE));

You might not need the absolute path, depends on where you have the files on your computer.

But you absolutely do need that L before the quote - otherwise you are reinterpret casting a narrow string as a wide string, which will be an invalid filename!

So yeah, I think you did the harder part of getting the custom draw etc right, but just made a char vs w_char mistake there. Let me know if fixing that L fixes the issue for you too.

Adam D. Ruppe
  • 25,382
  • 4
  • 41
  • 60
  • @Adam_D._Ruppe This actually solved the problem. I am just curious is there any way to optimize the code for minimizing the flickering issue, especially when the slider is moved right, it somehow halves the image, at least in my case. Thank you for the solution. I would give you +1 but due to low rep I am unable to do it. – Sormik Sep 06 '22 at 08:59
  • I don't know about that, it looks like you are already doing a double buffer so that's good. Maybe the skipping the erase background step of custom draw too would help. But I just don't really know. – Adam D. Ruppe Sep 06 '22 at 15:24