[C++] Give window adjustable border on one side? (And WS_BORDER makes window shorter?)

The title has the first question rather plain: How can I give a window an adjustable border, but only on the left side? I'm looking for something like in WMP where you can adjust the playlist/Now Playing panel width.

The second is just something odd I came across, I have this code:

1
2
3
GetClientRect(hwnd, &rect);
dlg=CreateWindowExA(0, "listbox", 0, WS_CHILD|LBS_NOTIFY|WS_VSCROLL, 
  rect.right-200, 0, 200, rect.bottom, hwnd, 0, GetModuleHandle(0), 0);


The odd part is that if I add WS_BORDER, I loose 16px (Or so the number is for me) but when it's removed, the listbox goes all the way to the bottom of the parent window, as it should. Does anybody know what that happens?

Thanks!
have a look at the WM_SIZING message: http://msdn.microsoft.com/en-us/library/ms632647%28VS.85%29.aspx (for your first question)

and listboxes size themselves so they can fit their rows in equally. You can set the LBS_NOINTEGRALHEIGHT style to stop it from doing that
Last edited on
How do I get the border that when hovered gives the dual sided arrow? But only on one side?

Ahh, okay, that makes sense.
on the side of your main window or the listbox?
The listbox. Something similiar to what it has in WMP. (Windows Media Player)
the only way i can think is to subclass the listbox window like this;

1
2
3
4
5
(global) WNDPROC g_wpPrev;

// after listbox has been created
g_wpPrev = (WNDPROC)SetWindowLongPtr(hListbox, GWLP_WNDPROC, (LONG)wpNewProc); // Set new window proc


then in your new window proc,

1: Handle WM_NCCALCSIZE to create extra space for a right border
2: Draw the border in WM_NCPAINT
3: Handle WM_NCHITTEST to check if the mouse is in the new right border, and return HTRIGHT to trick it into sizing.

I know it seems a lot but i have done this in one of my old projects and it works fine, theres a tutorial which might lead you in a better direction:

http://www.catch22.net/tuts/editbutton



Last edited on
this is from my old project, its pretty cheap but it works

1
2
3
4
5
6
7

g_hListbox = CreateWindowEx(WS_EX_CLIENTEDGE, WC_LISTBOX, _T(""), WS_CHILD | WS_VISIBLE | LBS_NOINTEGRALHEIGHT, 
				0, 0, 150, wHeight, hWnd, NULL, g_hInst, NULL);

			// subclass listbox
			g_wpPrev = (WNDPROC)SetWindowLongPtr(g_hListbox, GWLP_WNDPROC, (LONG)wpListboxProc);


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
LRESULT CALLBACK wpListboxProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
	switch (msg)
	{
	case WM_NCHITTEST:
		{
			LRESULT lrDef = CallWindowProc(g_wpPrev, hWnd, msg, wParam, lParam);

			if (lrDef == HTBORDER)
			{
				// Mouse in border
				RECT rect;
				GetWindowRect(hWnd, &rect);

				if (LOWORD(lParam) > (rect.right - 5))
					return HTRIGHT;
			}

			return lrDef;
		}
	}
	return CallWindowProc(g_wpPrev, hWnd, msg, wParam, lParam);
}
Many thanks. Looks like I'll be doing a bit of research today to learn how this all works. (Thankfully I've already learned subclassing.)
Okay, I am having a hard time finding anything on WM_NCCALCSIZE that makes sense. The code in the link you gave me I have no clue how it works. Do you know of any tutorials on that?

Also I've decided not to add the resizing, due to the reajusting when it's moved. Nonetheless, I still need the border, so I came up with this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
case WM_PAINT: {
  HBITMAP brush;
  HBRUSH newBrush;
  HGDIOBJ oldBrush;
  HDC hdc;
  PAINTSTRUCT ps;
  RECT rect;

  //BeginPaint(hwnd, &ps);
  hdc=GetDC(hwnd);
  GetClientRect(hwnd, &rect);
  rect.right=5;

  brush=LoadBitmap(GetModuleHandle(0), MAKEINTRESOURCE(IDB_BORDER));
  newBrush=CreatePatternBrush(brush);
  oldBrush=SelectObject(hdc, newBrush);
  SelectObject(hdc, GetStockObject(NULL_PEN));

  Rectangle(hdc, 0,0,rect.right,rect.bottom);
			
  SelectObject(hdc, oldBrush);
  DeleteObject(newBrush);

  UpdateWindow(hwnd);
  ReleaseDC(hwnd, hdc);
  //EndPaint(hwnd, &ps);
  CallWindowProc(listboxProc, hwnd, msg, wParam, lParam);
}


(If there's a better way to do that, let me know please.)

The odd part about that is that if I uncomment begin/endPaint, then the list items don't appear till they are clicked on. Do you know why that happens?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
LRESULT CALLBACK listboxProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
#define RIGHT_BORDER_WIDTH 10

	switch (msg)
	{
	case WM_NCCALCSIZE:
		{
			CallWindowProc(g_wpPrev, hwnd, msg, wParam, lParam);

			RECT *pRect = (RECT*)lParam;

			pRect->right -= RIGHT_BORDER_WIDTH; // Space for a 10px border on the right side.
			return 0;
		}
	case WM_NCPAINT:
		{
			HDC hDC = GetWindowDC(hwnd);

			RECT rect;
			GetWindowRect(hwnd, &rect);

			rect.left = (rect.right - rect.left) - RIGHT_BORDER_WIDTH;
			rect.bottom -= rect.top;
			rect.top = 0;
			rect.right = rect.left + RIGHT_BORDER_WIDTH;

			// Fill extra space with black brush
			FillRect(hDC, &rect, (HBRUSH)GetStockObject(BLACK_BRUSH));

			ReleaseDC(hwnd, hDC);
			break;
		}
	case WM_NCHITTEST:
		{
			LRESULT lrDef = CallWindowProc(g_wpPrev, hwnd, msg, wParam, lParam);

			if (lrDef != HTCLIENT)
			{
				// if not clicked in client area
				return HTRIGHT;
			}

			return lrDef;
		}
	}
	return CallWindowProc(g_wpPrev, hwnd, msg, wParam, lParam);
}


better to leave the client area painting alone and draw in the none client area, so that it doesn't mess with the default drawing, anyway this seems to work, don't create tyour listbox with boarders otherwise might have to edit this abit.

edit:

forgot to add that you have to call this after doin SetWindowLong to force to do WM_NCCALCSIZE:

1
2
3
4
5
6
7
8
g_hListbox = CreateWindowEx(0, WC_LISTBOX, _T(""), WS_CHILD | WS_VISIBLE | LBS_NOINTEGRALHEIGHT, 
				0, 0, 150, wHeight, hWnd, NULL, g_hInst, NULL);

			// subclass listbox
			g_wpPrev = (WNDPROC)SetWindowLongPtr(g_hListbox, GWLP_WNDPROC, (LONG)listboxProc);

			// force to call WM_NCCALCSIZE
			SetWindowPos(g_hListbox, NULL,0,0,0,0,SWP_NOMOVE|SWP_NOSIZE|SWP_NOZORDER| SWP_FRAMECHANGED);
Last edited on
Ahh, perfect!

Final code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
long fnListSubClass(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam){
  switch(msg){
  case WM_NCCALCSIZE: {
	
    CallWindowProc(listboxProc, hwnd, msg, wParam, lParam);
		
    RECT* pRect = (RECT*)lParam;
    pRect->left+=4;
			
    return 0;
  }
  case WM_NCPAINT: {

    HBITMAP brush;
    HBRUSH newBrush;
    HGDIOBJ oldBrush;
    HDC hdc;
    RECT rect;
		
    hdc=GetWindowDC(hwnd);

    GetClientRect(hwnd, &rect);
    SetRect(&rect, 0, 0, 4, rect.bottom);
		
    brush=LoadBitmap(GetModuleHandle(0), MAKEINTRESOURCE(IDB_BORDER));
    newBrush=CreatePatternBrush(brush);
    oldBrush=SelectObject(hdc, newBrush);

    FillRect(hdc, &rect, newBrush);

    SelectObject(hdc, oldBrush);
    DeleteObject(newBrush);

    UpdateWindow(hwnd);
    ReleaseDC(hwnd, hdc);
    
    break;
		}
  }
  return CallWindowProc(listboxProc, hwnd, msg, wParam, lParam);
}


Thanks for all the help. Onward to learning how to make the list manually. :)
For the record - looking at windows media player (in Windows 7), what you have
looking from left to right is :

1. A treeview window
2. A very thin window
3. A listview window.

When you place your mouse on the very thin window, the cursor changes to a double ended left-right shape. As you grab this very thin strip window and move it from left to right, the windows on the left and right side are resized.
I had wondered if that was a way to do it.
i didn't realise you wanted the border on the left hand side XD, i kept coding it to be on the right.

Glad you got it working, just a note, you have a memory leak because you didn't delete the bitmap you created the brush with:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
brush=LoadBitmap(GetModuleHandle(0), MAKEINTRESOURCE(IDB_BORDER));
    newBrush=CreatePatternBrush(brush);
    DeleteObject(brush); // delete loaded bitmap here
    oldBrush=SelectObject(hdc, newBrush);

    FillRect(hdc, &rect, newBrush);

    SelectObject(hdc, oldBrush);
    DeleteObject(newBrush);


    // UpdateWindow(hwnd); and i'm sure this isn't needed
    ReleaseDC(hwnd, hdc);


i never knew about the SetRect function O.O i'm gonna start using that haha..
Not at all a problem. :)

Many thanks. I guess I should also delete oldBrush as well.

Topic archived. No new replies allowed.