1. Any variables local to the CALLBACK must be STATIC if you want them to retain a value for more than one message.

2. To generate a WM_PAINT after changing your memDC use;
InvalidateRect() //generate paint msg
UpdateWindow() // send paint directly to app callback bypassing OS msg queue

3. Only redraw the minimum area by using the RECT in the PAINTSTRUCT.
Do NOT always redraw the entire client area.
Not all paint msgs are generated inside your app (ie moving a msgbox over a corner of your app will redraw the entire screen).

4. Catch the default GDI objects when using SelectObject(), as you have done with the pen, but not with the bitmap. Select these default GDI objects back into the DC before deleting the GDIs (ie return the created MemDC back to the default state)


Generally complex TABs are done by creating an individual dialog for each TAB and positioned over the TAB client area.
Otherwise an array of DCs is used, or just a single DC that is redrawn based on the TAB selected.
In this case it is hard to determine the 'correct' approach (as I lack the details of the other TAbs).

As a pattern for single DCs;

WM_CREATE / WM_INITDIALOG
Create MemDC
Create the MemDC bitmap to the max size (ie screen res or app client area if app not resizeable).
This negates the need to resize the MemDC if the app changes size.

WM_SIZE
Redraw MemDC to new size / scale (StretchBit() is the easiest but will not work well for line drawings)

WM_PAINT
BitBlt() from MemDC to PAINTSTRUCT DC using PAINTSTRUCT rect

WM_CLOSE / DESTROY
Clean up GDI (put default GDI objects back into the MemDC, then delete the created bitmap, DC, etc).

TCN_SELCHANGE
Redraw MemDC based on TAB selected
Generate a paint msg

If you want to do mouse drawing I am sure I have posted code previously (in the last 10 years...)