Docking a child window to the parent window - c++

I need my 6 controls (child windows of the main window) to get larger when the main window gets resized by the user (dragging the corners). I thought I could accomplish this by using the MoveWindow function to change the proportions of each child in the main window's WM_SIZE or WM_SIZING function. Doing this made the Debug build go strange (multiple windows, image of the window sticking after exiting, etc). The Release build ran fine but the child windows didn't change when I resized the main window.
I found http://msdn.microsoft.com/en-us/library/ms632598%28v=VS.85%29.aspx#creating_enumerating_etc used a different method of doing this: by enumerating all the child windows, and the enum callback function handling the window resizing through a unique ID assigned to every child. Upon trying this myself, it made no difference to the controls when the main window got resized.
Why isn't this working?
In the main windows switch statement:
case WM_SIZING:
GetClientRect(hwnd, &hwndRect);
EnumChildWindows(hwnd, EnumChildProc, (LPARAM)&hwndRect);
break;
The child enumerator callback function:
BOOL CALLBACK EnumChildProc(HWND hwndChild, LPARAM lParam)
{
LPRECT hwndRect = (LPRECT)lParam;
switch(GetWindowLong(hwndChild, GWL_ID))
{
case ID_CHILD_LLABEL:
MoveWindow(hwndChild, 0, 0, (hwndRect->right - hwndRect->left) - 30, 20, false);
break;
case ID_CHILD_LDIR:
MoveWindow(hwndChild, 12, 20, (hwndRect->right - hwndRect->left) - 40, 20, false);
break;
case ID_CHILD_LLIST:
MoveWindow(hwndChild, 12, 40, (hwndRect->right - hwndRect->left) - 40, (hwndRect->bottom - hwndRect->top) - 238, false);
break;
}
}

From MSDN's article on WM_SIZE: "If the SetScrollPos or MoveWindow function is called for a child window as a result of the WM_SIZE message, the bRedraw or bRepaint parameter should be nonzero to cause the window to be repainted."
I suspect the child controls are moving, they simply aren't being repainted.
It might also be worth verifying that your switch cases are actually being hit.
Edit:
I missed the obvious. You are responding to WM_SIZING, which indicates that the size of the window is about to (but has not yet) changed. WM_SIZE indicates that the size has changed. If you want to use WM_SIZING, you need to use the rect carried in the lParam, not the results of GetClientRect. Unfortunately the WM_SIZING rect is the rect of the window, not the client area, and is in screen coordinates. Unless you really need to display the resized controls while the user is still performing the resize, it would be much easier to just handle the WM_SIZE message.

Related

Properly handling owner drawn Win32 button hovering

I want to add multiple color themes to my Win32 application, this means that I have to manually handle all the control drawing manually by using the BS_OWNERDRAW style flag. I then handle all the drawing in the WM_DRAWITEM message through the LPDRAWITEMSTRUCT structure stored in the lParam. Here's the problem though, by introducing owner drawing I also have to handle click and hover events, which in and of itself isn't a problem but it becomes one because it turns out that the LPDRAWITEMSTRUCT has no message being sent to indicate whether or not a control is being hovered. The itemState member do have messages like ODS_SELECTED, ODS_FOCUS and so on, but no flag for hovering only.
That being said, there does exist a flag named ODS_HOTLIGHT which according to MSDN should do the job, the problem however is that this flag never occurs for buttons, only menu items. I've tried using the SetWindowSubclass function by giving each control it's separate WindowProc callback where you can track the mouse leaving and entering the control. This works, but that also means that I need to transition all drawing commands over to the subclass procedure, which seems rather stupid to me, since the WM_DRAWITEM is intended for custom drawing in the first place.
You could always send WM_DRAWITEM manually via SendMessage but that also means that I need to provide an LPDRAWITEMSTRUCT manually which hasn't worked out that great. In essence I do not know what the best approach is to react to these hover events and draw them accordingly, the LPDRAWITEMSTRUCT does not provide such a flag and thus I have no idea what other approach to use. How should I tackle this?
The way I'm currently handling the WM_DRAWITEM message (buttons only):
case WM_DRAWITEM: {
LPDRAWITEMSTRUCT pDIS = (LPDRAWITEMSTRUCT)lParam;
HPEN borderPen = CreatePen(PS_INSIDEFRAME, 0, RGB(0, 0, 0));
HGDIOBJ oldPen = SelectObject(pDIS->hDC, borderPen);
HGDIOBJ oldBrush;
if (pDIS->itemState & ODS_SELECTED) {
oldBrush = SelectObject(pDIS->hDC, buttonHoverBrush);
}
else {
oldBrush = SelectObject(pDIS->hDC, buttonDefaultBrush);
}
// Rounded button
RoundRect(pDIS->hDC, pDIS->rcItem.left, pDIS->rcItem.top, pDIS->rcItem.right, pDIS->rcItem.bottom, 5, 5);
//Clean up
SelectObject(pDIS->hDC, oldPen);
SelectObject(pDIS->hDC, oldBrush);
DeleteObject(borderPen);
// Calculate button dimensions
int buttonWidth = pDIS->rcItem.right - pDIS->rcItem.left;
int buttonHeight = pDIS->rcItem.bottom - pDIS->rcItem.top;
WCHAR staticText[128];
int len = SendMessage(pDIS->hwndItem, WM_GETTEXT, ARRAYSIZE(staticText), (LPARAM)staticText);
HFONT buttonFont = (HFONT)SendMessage(pDIS->hwndItem, WM_GETFONT, 0, 0);
SIZE buttonDim;
HFONT oldFont = (HFONT)SelectObject(pDIS->hDC, buttonFont);
GetTextExtentPoint32(pDIS->hDC, staticText, len, &buttonDim);
SetTextColor(pDIS->hDC, RGB(255, 255, 255));
SetBkMode(pDIS->hDC, TRANSPARENT);
TextOut(pDIS->hDC, buttonWidth / 2 - buttonDim.cx / 2, buttonHeight / 2 - buttonDim.cy / 2, staticText, len);
wasHandled = TRUE;
result = TRUE;
break;
}
The code listed above creates a button with a given background color and centers text within it. It also changes color to the hover rush when clicked. What I want to happen is that the button changes its color immediately upon hover and not upon click. Thank you in advance!
I've tried using the SetWindowSubclass function by giving each control it's separate WindowProc callback where you can track the mouse leaving and entering the control. This works, but that also means that I need to transition all drawing commands over to the subclass procedure, which seems rather stupid to me, since the WM_DRAWITEM is intended for custom drawing in the first place.
Unfortunately, that is precisely what you will likely need to do.
For instance, you can have the button's subclass procedure handle the WM_MOUSEMOVE message to detect when the mouse is over the button, and handle the WM_MOUSELEAVE message to detect when the mouse moves out of the button. The message handler can use WM_MOUSEMOVE to call TrackMouseEvent() to trigger WM_MOUSELEAVE and WM_MOUSEHOVER messages. It can then use WM_MOUSEHOVER to set a flag and invalidate the button to trigger a repaint, and it can use WM_MOUSELEAVE to clear the flag and invalidate the button to trigger a repaint.
Inside the button's normal draw handler (which DOES NOT need to be moved to the subclass procedure. BTW), if the flag is set then draw the button as hovered, otherwise draw it as non-hovered.
The only gotcha with this approach is if you have multiple buttons then you will need multiple flags, one per button. But you can use (Get|Set)WindowLongPtr(GWL_USERDATA) or (Get|Set)Prop() to store/retrieve each button's flag inside/from the button's HWND directly Or, you can just maintain your own std::map (or other lookup table) of HWND-to-flag associations.
Another option would be to have the subclass procedure use the WM_MOUSE(MOVE|HOVER|LEAVE) messages to send simulated WM_DRAWITEM messages to the button, specifying/omitting the ODS_HOTLIGHT style as needed so the drawing code can look for that style. You might need the subclass procedure to intercept WM_PAINT messages to make that work, though. Not sure about that.
Otherwise, you could simply have your WM_DRAWITEM handler grab the mouse's current coordinates and see if they fall within the button's client area, and then draw accordingly.

Setcapture causing PtInRect to malfunction?

For some reason, using the SetCapture function, causes the PtInRect() function to not work!
Here's some code:
POINT curmouse;
RECT testrect = {0, 0, 200, 200};
WM_LBUTTONDOWN:
if (MK_LBUTTONDOWN == true)
SetCapture(hWnd);
break;
WM_MOUSEMOVE:
curmouse.x = LOWORD(lParam);
curmouse.y = HIWORD(lParam);
WM_LBUTTONUP:
if (PtInRect(&testrect , curmouse))
//Draw Image
Please Note: I did not make a call to release capture on WM_LBUTTONUP. In fact, releasecapture is never called!
If I do not call setcapture, then ptinrect works as intended on mouse release.
Can someone please tell me how to get ptinrect to work with setcapture() being called?
Most importantly, there are two seperate windows in this program. The first window is named bg_window, the second, other_window.
You're only initialising curmouse on WM_MOUSEMOVE, not WM_LBUTTONUP - so the values in there when you actually get a button up will be random.
SetCapture() restricts the mouse messages to the window of which you passed the handle. Unless you call a ReleaseCapture() the mouse messages on the other window cannot be interpreted.Using SetCapture() and never calling ReleaseCapture() is not a good coding practice.
If your rect is in the other window the PtInrect is not going to work fine.
Also If you want to know whether the user clicked in the rect region you can use the mouse point you got during the WM_LBUTTONDOWN message.

WIN32: How Do I Tell an Owner Drawn Static Control to Refresh Itself?

I have a WIN32 owner-drawn static control that draws a progress bar using two source images (filled and unfilled). Works great on the initial draw:
case WM_DRAWITEM:
{
DRAWITEMSTRUCT* draw = (DRAWITEMSTRUCT*)lparam;
// Manually draw the progress bar.
if( draw->hwndItem == hwndProgress )
{
// Progress bar is 526 pixels wide.
int left = progressPercent * 526 / 100;
// Paint sections of window with filled and unfilled bitmaps
// based on progress bar position.
HDC hdcMem = ::CreateCompatibleDC(draw->hDC);
::SelectObject(hdcMem, hBmpProgressFull);
::BitBlt(draw->hDC, 0, 0, left, 36, hdcMem, 0, 0, SRCCOPY);
::DeleteDC(hdcMem);
HDC hdcMem2 = ::CreateCompatibleDC(draw->hDC);
::SelectObject(hdcMem2, hBmpProgressEmpty);
::BitBlt(draw->hDC, left, 0, 526-left, 36, hdcMem2, left, 0, SRCCOPY);
::DeleteDC(hdcMem2);
return TRUE;
}
}
return 0;
However, I can’t seem to get the thing to erase and repaint properly. I’ve tried SendMessage with WM_PAINT and RedrawWindow and neither one has worked quite right:
bool SetLoginProgressBar(float value)
{
if( hwndProgress != NULL )
{
progressPercent = (int)(value * 100.0);
//::RedrawWindow(hwndProgress, NULL, NULL, RDW_INVALIDATE|RDW_INTERNALPAINT);
::SendMessage(hwndProgress, WM_PAINT, NULL, NULL);
}
return true;
}
Instead of redrawing the window with the new values, it just sits there with the initially drawn image and ignores further drawing commands. It draws the progress correctly for the initial value, whether it's 0%, 50%, etc, and I can verify that my WM_DRAWITEM message handler code is being called.
So, what is the correct way to tell this control to erase and redraw in WIN32?
Is is possible that I need to do something like BeginPaint/EndPaint, or delete the hDC in the DRAWITEMSTRUCT that I've been passed?
You aren't deselecting the bitmaps from the memory DC before you destroy the DC. Perhaps the bitmaps are in a state where Windows won't allow you to select them again, so the BitBlts are failing.
P.S. RedrawWindow is what I use in this situation. InvalidateRect works too, but only if your message loop is running. Which leads to another observation: if you're in the middle of a long running operation, you may not get back to the message loop and your app will appear to be hung, including updates to the progress window.
InvalidateRect() is the function you need to call.
You never send or post WM_PAINT messages—the Window manager does that for you when they are needed (e.g. windows dragged over your window). If the repaint is due to changes that the Window manager does not know about then you force a repaint cycle by calling InvalidateRect(). Pass NULL for lpRect and the entire client area will be repainted. Pass TRUE for bErase to force the background to be erased when the repaint cycle begins.
When you call InvalidateRect() what happens is that a WM_PAINT message is placed in your message queue and the InvalidateRect() function call returns. When you next clear your message queue you the process the WM_PAINT message.
I suggest that you get hold of a copy of Petzold's Programming Windows book and read all about it.

Static Control Background Color with C++

I am creating a basic GUI with the Windows API and I have run into an issue. It starts with a main window that opens with a custom background color I set (RGB(230,230,230)). It then displays text in the upper left corner with the static control.
settingstext = CreateWindow("STATIC",
"SETTINGS",
SS_LEFT | WS_CHILD,
12,
20,
100,
20,
hwnd,
NULL,
proginstance,
NULL);
ShowWindow(settingstext, 1);
This works, but when the text is displayed I need a way to change the background of it to match the main window or else it just looks like it doesn't blend in.
My question is, how do I do this? I currently use the method below and it works, but I wanted to know, is there a way to permanently set the background color somehow, right after the CreateWindow function for the static control without changing system colors, and just have it apply to that one control and not anything that sends the WM_CTLCOLORSTATIC message. I have experimented around with using the GetDC function and SetBkColor function outside of the message loop but nothing works.
case WM_CTLCOLORSTATIC:
{
HDC hdcStatic = (HDC) wParam;
SetTextColor(hdcStatic, RGB(0,0,0));
SetBkColor(hdcStatic, RGB(230,230,230));
return (INT_PTR)CreateSolidBrush(RGB(230,230,230));
}
I want to do this because...
I don't want to fill up my message loop with functions that need to be called every time the window repaints.
Have the changes apply to only this static control.
I would be very thankful for any help that could be provided, at least pointing me in the right direction, thanks.
For static text controls there's no permanent way to set the text color or their background. Even if you want to apply the changes to a single static control; you would still have to handle WM_CTLCOLORSTATIC notification message in parent dlgproc just when the control is about to be drawn.
This is due to the DefWindowProc overwriting your changes to the device context each time it handles WM_CTLCOLORSTATIC as stated in the MSDN:
By default, the DefWindowProc function selects the default system colors for the static control.
static HBRUSH hBrush = CreateSolidBrush(RGB(230,230,230));
case WM_CTLCOLORSTATIC:
{
if (settingstext == (HWND)lParam)
//OR if the handle is unavailable to you, get ctrl ID
DWORD CtrlID = GetDlgCtrlID((HWND)lParam); //Window Control ID
if (CtrlID == IDC_STATIC1) //If desired control
{
HDC hdcStatic = (HDC) wParam;
SetTextColor(hdcStatic, RGB(0,0,0));
SetBkColor(hdcStatic, RGB(230,230,230));
return (INT_PTR)hBrush;
}
}
If you're looking to make the control's background transparent over a parent dialog you could use SetBkMode(hdcStatic, TRANSPARENT).
I think there is a permanent way to do it.
Just after you create the label,use GetDC() function to get the Device Context.
Then use:
SetTextColor(hdcStatic, RGB(0,0,0));
SetBkColor(hdcStatic, RGB(230,230,230)); // Code Copied from the above answer by cpx.
And it should do .
Have you considered subclassing the static window and doing owner draw?

GDI+ not clearing my window on repaint for vista

on WM_PAINT i do the following:
//RectF mNameRect;
//WCHAR* mName;
//HWND mWin; // this is the window handle
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(mWin, &ps);
Graphics g(hdc);
g.Clear(Color::White);
StringFormat stringForm;
stringForm.SetLineAlignment(StringAlignmentCenter);
stringForm.SetAlignment(StringAlignmentCenter);
// set the rectangle to the size of the whole window
mNameRect.Width = static_cast<float>(size.cx);
mNameRect.Height = static_cast<float>(size.cy);
g.DrawString(mName, -1, &mNameFont, mNameRect, &stringForm, &mNameBrush);
EndPaint(mWin, &ps);
}
In XP this works fine, the mName is displayed in the middle of the window. However on Vista the text doesn't move, it stays in its location no matter how I resize the window. the g.Clear(Color::White) doesn't seem to do any difference. The text doesn't even change position when the window is hidden behind another window and on focus needs to be repainted again.
How do I make mName change position in Vista?
Edit:
The paint code gets called via WM_PAINT and via WM_SIZE in the following manner:
// WndProc function
switch (msg){
case WM_SIZE:
// intentionally call paint when WM_SIZE is triggered
case WM_PAINT:
paint();
break;
You are explicitly calling your paint() function when the window is resized. However, your window is not invalidated, so it could be that the system is restricting your painting efforts to the region marked "dirty".
Instead of calling paint() directly, it is better to use InvalidateRgn to trigger a repaint. This will cause a WM_PAINT to be sent which will be handled by your application in the normal way. As a bonus, you can also tell InvalidateRgn to erase the background for you
InvalidateRgn(hWnd, NULL, TRUE);