Related
I need to get the client rect offsets of a window relative to the non-client rect of that window.
I have tried GetClientRect but this only gets the client rect relative to the client rect itself, meaning: rect.left = 0, rect.top = 0, rect.right = clientWidth and rect.bottom = clientHeight.
So, how to do it?
Thanks in advance.
It sounds like you want the non-client offsets from the edges of the Window into the client rectangle. That is, the left and top internal leading pixels along with the right and bottom trailing pixels. Within these offsets (sizes), you could have the title bar, menu bar and edges that adorn your client rectangle if you were doing this for a top-level window such as a dialog. Windows doesn't calculate this for you, but it provides everything you need to calculate it yourself. Here is some sample code that you might use to perform this calculation from a given window handle:
RECT rcNonCli = {0,0,0,0}; //calculate non-client offsets here
RECT rcWin = {0,0,0,0};
RECT rcClient = {0,0};
POINT ptClient = {0,0};
if (GetWindowRect(hwnd,&rcWin) && GetClientRect(hwnd,&rcClient) && ClientToScreen(hwnd,&ptClient))
{
rcNonCli.left = ptClient.x - rcWin.left;
rcNonCli.top = ptClient.y - rcWin.top;
rcNonCli.right = rcWin.right - ptClient.x - rcClient.right;
rcNonCli.bottom = rcWin.bottom - ptClient.y - rcClient.bottom;
printf("nonclient offsets={%d,%d,%d,%d}\n",rcNonCli.left,rcNonCli.top,rcNonCli.right,rcNonCli.bottom);
}
EXPLANATION:
ClientToScreen() gives us the beginning coordinate of the client area providing the top and left offsets. Once we know this, we can subtract from the complete Window size (from GetWindowRect()), the client size (from GetClientRect()) and the non-client top and left offsets to give us what remains which would be the bottom and right offsets.
As Jonathan Potter commented, I recommend you to use MapWindowPoints.
You can pass the rect obtained by GetClientRect into MapWindowPoints, and set the cPoints parameter to 2, to get the coordinates relative to the window you want.
Here is the sample:
#include <Windows.h>
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
{
static TCHAR szAppName[] = TEXT("hello windows");
HWND hwnd;
MSG msg;
WNDCLASS wndclass;
wndclass.style = CS_HREDRAW | CS_VREDRAW;
wndclass.lpfnWndProc = WndProc;
wndclass.cbClsExtra = 0;
wndclass.cbWndExtra = 0;
wndclass.hInstance = hInstance;
wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
wndclass.lpszMenuName = NULL;
wndclass.lpszClassName = szAppName;
if (!RegisterClass(&wndclass))
{
MessageBox(NULL, TEXT("This program requires Windows NT!"), szAppName, MB_ICONERROR);
}
hwnd = CreateWindow(szAppName,
TEXT("the hello program"),
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
NULL,
NULL,
hInstance,
NULL);
ShowWindow(hwnd, iCmdShow);
UpdateWindow(hwnd);
while (GetMessageW(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
return msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
HDC hdc;
PAINTSTRUCT ps;
RECT rect;
switch (message)
{
case WM_CREATE:
case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);
GetClientRect(hwnd, &rect);
DrawTextW(hdc, (L"Hello,Windows"), -1, &rect, DT_SINGLELINE | DT_CENTER | DT_VCENTER);
MapWindowPoints(hwnd, HWND_DESKTOP, (LPPOINT)&rect, 2);
EndPaint(hwnd, &ps);
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, message, wParam, lParam);
}
You can observe the result of rect after calling the MapWindowPoints. It converts the original coordinates to the coordinates relative to the desktop.
what is the proper way of implementing custom rounded border for EDIT control in pure WinAPI (no MFC)? I need an edit with border like this:
Should I subclass edit control and do custom painting in WM_NCPAINT or something like that?
I guess you have two options:
As you said, you could sub-class and override WM_NCPAINT, etc to provide your own non-client area
Alternatively, you could simply turn off the border styles on the edit control and make the parent window responsible for drawing the frame.
With option #1, you would need to override WM_NCCALCSIZE to make the non-client area of the edit control larger (i.e. make the client area smaller), and then WM_NCPAINT to render your custom frame. You may also need to handle WM_NCHITTEST. And of course you'd need to make the control itself physically larger to account for the extra frame thickness.
It depends on your application design and how many controls like this you wish to use, but if it were me I would go with option #2. Modifying the standard drawing behaviour of system controls, many of which have decades of accumulated kludges and compatibility fixes attached to them, is often not as easy as you might expect.
If you make sure the WS_BORDER and WS_EX_CLIENTEDGE styles aren't set on the edit control, it will have no visible border of its own. Then all you have to do is have the parent window, when processing WM_PAINT, draw the frame around it. Make sure you set the WS_CLIPCHILDREN style on the parent window so that your custom drawing doesn't overwrite the edit control.
Either path would probably work in the end though so it's up to you which way you go.
This is an implementation that works for me.
It subclass the "EDIT" class control and replaces the WM_NCPAINT handler to draw a rectangle with rounded corners for all edit boxes with the WS_BORDER or WS_EX_CLIENTEDGE style. It draws the border on the parent DC.
The diameter of the corner is now fixed (10), I guess that should depend on the font size ...
Thanks to Darren Sessions for the GDI+ example how to draw the rounded rect:
https://www.codeproject.com/Articles/27228/A-class-for-creating-round-rectangles-in-GDI-with
#include <windows.h>
#include <objidl.h>
#include <gdiplus.h>
using namespace Gdiplus;
#pragma comment (lib,"Gdiplus.lib")
inline void GetRoundRectPath(GraphicsPath* pPath, Rect r, int dia)
{
// diameter can't exceed width or height
if (dia > r.Width) dia = r.Width;
if (dia > r.Height) dia = r.Height;
// define a corner
Rect Corner(r.X, r.Y, dia, dia);
// begin path
pPath->Reset();
// top left
pPath->AddArc(Corner, 180, 90);
// top right
Corner.X += (r.Width - dia - 1);
pPath->AddArc(Corner, 270, 90);
// bottom right
Corner.Y += (r.Height - dia - 1);
pPath->AddArc(Corner, 0, 90);
// bottom left
Corner.X -= (r.Width - dia - 1);
pPath->AddArc(Corner, 90, 90);
// end path
pPath->CloseFigure();
}
inline void GetChildRect(HWND hChild, LPRECT rc)
{
GetWindowRect(hChild,rc);
SIZE si = { rc->right - rc->left, rc->bottom - rc->top };
ScreenToClient(GetParent(hChild), (LPPOINT)rc);
rc->right = rc->left + si.cx;
rc->bottom = rc->top + si.cy;
}
inline void DrawRoundedBorder(HWND hWnd, COLORREF rgba = 0xFF0000FF, int radius = 5)
{
BYTE* c = (BYTE*)&rgba;
Pen pen(Color(c[0], c[1], c[2], c[3]));
if (pen.GetLastStatus() == GdiplusNotInitialized)
{
GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
pen.SetColor(Color(c[0], c[1], c[2], c[3]));
}
pen.SetAlignment(PenAlignmentCenter);
SolidBrush brush(Color(255, 255, 255, 255));
RECT rc = { 0 };
GetChildRect(hWnd, &rc);
// the normal EX_CLIENTEDGE is 2 pixels thick.
// up to a radius of 5, this just works out.
// for a larger radius, the rectangle must be inflated
if (radius > 5)
{
int s = radius / 2 - 2;
InflateRect(&rc, s, s);
}
GraphicsPath path;
GetRoundRectPath(&path, Rect(rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top), radius * 2);
HWND hParent = GetParent(hWnd);
HDC hdc = GetDC(hParent);
Graphics graphics(hdc);
graphics.SetSmoothingMode(SmoothingModeAntiAlias);
graphics.FillPath(&brush, &path);
graphics.DrawPath(&pen, &path);
ReleaseDC(hParent, hdc);
}
static WNDPROC pfOldEditWndProc = NULL;
static LRESULT CALLBACK EditRounderBorderWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_NCCREATE:
{
DWORD style = GetWindowLong(hWnd, GWL_STYLE);
if (style & WS_BORDER)
{
// WS_EX_CLIENTEDGE style will make the border 2 pixels thick...
style = GetWindowLong(hWnd, GWL_EXSTYLE);
if (!(style & WS_EX_CLIENTEDGE))
{
style |= WS_EX_CLIENTEDGE;
SetWindowLong(hWnd, GWL_EXSTYLE, style);
}
}
// to draw on the parent DC, CLIPCHILDREN must be off
HWND hParent = GetParent(hWnd);
style = GetWindowLong(hParent, GWL_STYLE);
if (style & WS_CLIPCHILDREN)
{
style &= ~WS_CLIPCHILDREN;
SetWindowLong(hParent, GWL_STYLE, style);
}
}
break;
case WM_NCPAINT:
if (GetWindowLong(hWnd, GWL_EXSTYLE) & WS_EX_CLIENTEDGE)
{
DrawRoundedBorder(hWnd);
return 0;
}
}
return CallWindowProc(pfOldEditWndProc, hWnd, uMsg, wParam, lParam);
}
class CRoundedEditBorder
{
public:
CRoundedEditBorder()
{
Subclass();
}
~CRoundedEditBorder()
{
Unsubclass();
}
private:
void Subclass()
{
HWND hEdit = CreateWindow(L"EDIT", L"", 0, 0, 0, 200, 20, NULL, NULL, GetModuleHandle(NULL), NULL);
pfOldEditWndProc = (WNDPROC)GetClassLongPtr(hEdit, GCLP_WNDPROC);
SetClassLongPtr(hEdit, GCLP_WNDPROC, (LONG_PTR)EditRounderBorderWndProc);
DestroyWindow(hEdit);
}
void Unsubclass()
{
HWND hEdit = CreateWindow(L"EDIT", L"", 0, 0, 0, 200, 20, NULL, NULL, GetModuleHandle(NULL), NULL);
SetClassLongPtr(hEdit, GCLP_WNDPROC, (LONG_PTR)pfOldEditWndProc);
DestroyWindow(hEdit);
}
};
CRoundedEditBorder g_RoundedEditBorder;
LRESULT CALLBACK ParentWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_DESTROY: PostQuitMessage(0); return 0;
}
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
#define WNDCLASSNAME L"RoundedEditBorderTestClass"
int APIENTRY wWinMain(_In_ HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nCmdShow)
{
GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
WNDCLASSEXW wcex = { sizeof(WNDCLASSEX), CS_HREDRAW|CS_VREDRAW,ParentWndProc,0,0,hInstance,NULL,NULL,CreateSolidBrush(GetSysColor(COLOR_BTNSHADOW)),NULL,WNDCLASSNAME,NULL };
RegisterClassExW(&wcex);
HWND hWnd = CreateWindowW(WNDCLASSNAME, L"Rounded Edit Border Test", WS_OVERLAPPEDWINDOW | WS_VISIBLE, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);
CreateWindowEx(0, L"EDIT", L"no border", WS_CHILD | WS_VISIBLE, 10, 10, 200, 24, hWnd, NULL, GetModuleHandle(NULL), NULL);
CreateWindowEx(0, L"EDIT", L"no ex style", WS_CHILD | WS_VISIBLE | WS_BORDER, 10, 50, 200, 24, hWnd, NULL, GetModuleHandle(NULL), NULL);
CreateWindowEx(WS_EX_CLIENTEDGE, L"EDIT", L"Ex_ClientEdge", WS_CHILD | WS_VISIBLE | WS_BORDER, 10, 90, 200, 24, hWnd, NULL, GetModuleHandle(NULL), NULL);
ShowWindow(hWnd, nCmdShow);
MSG msg;
while (GetMessage(&msg, nullptr, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
GdiplusShutdown(gdiplusToken);
return (int)msg.wParam;
}
how can I remove the outline that is drawn when a control such as a button or checkbox is selected?
HWND chk=CreateWindow("button",s,
BS_FLAT|BS_AUTOCHECKBOX|BS_LEFTTEXT|WS_CHILD|WS_VISIBLE,
x,y,w,h,p,id,hInst,NULL);
http://www.thevbzone.com/L3_3.gif
The focus rectangles, like underlines for mnemonics, are shown depending on the UI state. By default, these things are hidden, unless the user initiated the dialog or menu via the keyboard. The idea being that if they're using the keyboard, these visual cues are useful, but if they're using the mouse (or touch), they're just clutter.
The UI state is accessible through SystemParametersInfo using the SPI_GET/SETKEYBOARDCUES, but I don't recommend changing that as it affects the user's experience with all applications, not just yours.
Regular UI controls decide how to draw themselves based on their state and the current UI state. If you want to change the appearance for your application, you'd have to use whatever feature the controls offer for "owner drawing," which can be quite a bit of work, and not all control types offer the appropriate overrides. I don't know of any per-control bit that can suppress the focus rectangles.
If you're using a modern application, the focus indicators are often much cleaner looking than the dashed rectangles you get on the old style buttons. Make sure you've enabled visual styles if you just want the modern look and don't really want to make it harder for your keyboard users to use your application.
This worked for me. After creating the button:
if (hWndButton[i] != NULL)
{
SendMessage(hWndButton[i], WM_CHANGEUISTATE, (WPARAM)(0x10001),(LPARAM)(0));
}
Just replace the WM_SETFOCUS msg of the button class. I've already written all the code. This is called subclassing in win32.
#include <windows.h>
// window callback
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
// redefining button class
LRESULT CALLBACK ButtonProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
WNDPROC oldButtonProc;
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, char* cmdLine, int cmdShow)
{
const char WNDClsName[] = "no-name";
WNDCLASS wc = { };
wc.lpfnWndProc = WindowProc;
wc.hInstance = hInstance;
wc.lpszClassName = WNDClsName;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
RegisterClass(&wc);
HWND hwnd = CreateWindow(
WNDClsName,
"Caption",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
NULL, // parent
NULL, // menu
hInstance, // instance handle
NULL); // additional application data
HWND button1 = CreateWindow(
"button", // class
"", // text
WS_CHILD | WS_BORDER | WS_VISIBLE, // style
CW_USEDEFAULT, CW_USEDEFAULT, // x,y
CW_USEDEFAULT, CW_USEDEFAULT, // w,h
hwnd, // parent
(HMENU) 101,
hInstance,
NULL);
oldButtonProc = (WNDPROC) SetClassLong(button1, GCL_WNDPROC, (LONG)ButtonProc);
DestroyWindow(button1);
button1 = CreateWindow(
"button", // class
"Doit!", // text
WS_CHILD | WS_BORDER | WS_VISIBLE, // style
CW_USEDEFAULT, CW_USEDEFAULT, // x,y
CW_USEDEFAULT, CW_USEDEFAULT, // w,h
hwnd, // parent
(HMENU) 101,
hInstance,
NULL);
ShowWindow(hwnd, cmdShow);
// run the message loop
MSG msg = { };
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
SetWindowLong(button1, GCL_WNDPROC, (LONG)oldButtonProc);
return 0;
}
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_DESTROY:
PostQuitMessage(0);
return 0;
case WM_SIZE:
{
short width = LOWORD(lParam);
short height = HIWORD(lParam);
MoveWindow(GetDlgItem(hwnd, 101), width/2-40, height/2-10, 80, 20, 0);
InvalidateRect(hwnd, 0, 0);
return 0;
}
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
FillRect(hdc, &ps.rcPaint, (HBRUSH) (COLOR_WINDOW+1));
EndPaint(hwnd, &ps);
}
return 0;
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
LRESULT CALLBACK ButtonProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
if (uMsg == WM_SETFOCUS)
{
return 0;
}
return CallWindowProc(oldButtonProc, hwnd, uMsg, wParam, lParam);
}
As described here, if I do not set the BTNS_SHOWTEXT style to a button, will not shown on the button text, but when the mouse hovers over the button, you will see tooltip with the text.
So I do not understand why this code sample text displayed on the button, and not shown a tooltip?
#include <windows.h>
#include <stdlib.h>
#include <CommCtrl.h>
#pragma comment(lib, "comctl32.lib")
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
HINSTANCE instance;
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
instance = hInstance;
WNDCLASSEX wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_APPLICATION));
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wcex.lpszMenuName = NULL;
wcex.lpszClassName = L"Example";
wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_APPLICATION));
RegisterClassEx(&wcex);
HWND hWnd = CreateWindow(L"Example", L"", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT,
500, 500, NULL, NULL, hInstance, NULL);
// Initialize common controls.
INITCOMMONCONTROLSEX icex;
icex.dwSize = sizeof(INITCOMMONCONTROLSEX);
icex.dwICC = ICC_COOL_CLASSES | ICC_BAR_CLASSES;
InitCommonControlsEx(&icex);
// create toolbar
HWND hWndToolbar = CreateWindowEx(0 , TOOLBARCLASSNAME, NULL, WS_CHILD | TBSTYLE_TOOLTIPS,
0, 0, 0, 0, hWnd, (HMENU)0, instance, NULL);
HIMAGELIST hImageList = ImageList_Create(16, 16, ILC_COLOR16 | ILC_MASK, 3, 0);
SendMessage(hWndToolbar, TB_SETIMAGELIST, (WPARAM)0, (LPARAM)hImageList);
SendMessage(hWndToolbar, TB_BUTTONSTRUCTSIZE, (WPARAM)sizeof(TBBUTTON), 0);
TBBUTTON tbb[1] =
{
{ 0, 0, TBSTATE_ENABLED, TBSTYLE_BUTTON, {0}, 0, (INT_PTR)L"New" },
};
SendMessage(hWndToolbar, (UINT) TB_ADDBUTTONS, 1, (LPARAM)&tbb);
SendMessage(hWndToolbar, TB_AUTOSIZE, 0, 0);
ShowWindow(hWndToolbar , SW_SHOW);
// show the main window
ShowWindow(hWnd, nCmdShow);
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (int) msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_CREATE:
return 0;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
}
Quoting from the MSDN article you linked:
Version 5.81. Specifies that button text should be displayed. All buttons can have text, but only those buttons with the BTNS_SHOWTEXT button style will display it. This button style must be used with the TBSTYLE_LIST style and the TBSTYLE_EX_MIXEDBUTTONS extended style.
There are three requirements listed here. Let's tackle them one by one.
Version 5.81
That's a version of common controls that requires a manifest in your executable. If it is missing then you'll get the legacy version, v4.70 from c:\windows\system32. Which doesn't know anything about BTNS_SHOWTEXT. The easiest way to specify the manifest entry is by inserting the /MANIFESTDEPENDENCY linker option with a #pragma in your code. Make it look similar to this:
#include <CommCtrl.h>
#pragma comment(lib, "comctl32.lib")
#pragma comment(linker,"/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
With the last line added. That should be immediately visible if you didn't yet do this, the highlight for the button now shows a gradient. Other parts of your window will similarly have acquired the visual styles theme.
must be used with the TBSTYLE_LIST style
That requires changing your CreateWindowEx call, include that style bit:
// create toolbar
HWND hWndToolbar = CreateWindowEx(0 , TOOLBARCLASSNAME, NULL,
WS_CHILD | TBSTYLE_TOOLTIPS | TBSTYLE_LIST,
0, 0, 0, 0, hWnd, (HMENU)0, instance, NULL);
and the TBSTYLE_EX_MIXEDBUTTONS extended style
That requires using the TB_SETEXTENDEDSTYLE message to turn that extended style bit on:
SendMessage(hWndToolbar, TB_AUTOSIZE, 0, 0);
SendMessage(hWndToolbar, TB_SETEXTENDEDSTYLE, 0, (LPARAM)TBSTYLE_EX_MIXEDBUTTONS);
ShowWindow(hWndToolbar , SW_SHOW);
With the second line added. You'll now see the tooltip as expected when you hover over the toolbar button. You will still need to do something to make the button actually visible to the user, right now it doesn't have a distinguishing feature. A bitmap is the usual approach.
Your code is almost right, it's only missing a single line:
SendMessage(hWndToolbar, TB_SETMAXTEXTROWS, 0, 0);
You can put it somewhere between the creation of the toolbar window and where you display it. It's just more initialization code. For test purposes, I added it before sending the TB_AUTOSIZE message.
Now it works just as described: hover over the toolbar button and you see a tooltip containing the button text. Only one caveat: the button's text is not actually displayed in the button itself.
If you think about it, that kind of makes sense. Basically, there's no point in showing the text in a tooltip if the entire string is already visible on the button itself. The only way that the automatic tooltip is going to be shown is if the button's text doesn't fit. Sending the TB_SETMAXTEXTROWS message ensures that the text will never fit by setting the maximum number of rows available for text to 0.
This is covered in more detail in an MSDN how-to article: How to Display Tooltips for Buttons.
I need to draw a custom caption bar, where I draw the window caption by myself.
HDC hdc = GetWindowDC(hwnd);
if (hdc && prepareTitleBarDC(getWidth(), 27)) {
SetWindowText(hwnd, _T(""));
DefWindowProc(hwnd, WM_NCPAINT, wParam, lParam);
m_titleBar->setSize(getWidth(), 27);
m_titleBar->setBkColor(SkColorSetARGB(0x00, 0x00, 0x00, 0x00));
m_titleBar->paintEvent(m_pTitleBarDC);
FnSkBitmap::SaveSkBitmap(m_pTitleBarDC->canvas(), L"e:\\titlebar.bmp");
HDC hdcPaint = CreateCompatibleDC(hdc);
HBITMAP hbm = CreateCompatibleBitmap(hdc, getWidth(), 27);
SelectObject(hdcPaint, hbm);
FnSkBitmap::DrawSkBitmap(m_pTitleBarDC->bitmap(), hdcPaint);
BLENDFUNCTION bfn = {0};
bfn.BlendOp = AC_SRC_OVER;
bfn.BlendFlags = 0;
bfn.SourceConstantAlpha = 255;
bfn.AlphaFormat = AC_SRC_ALPHA;
AlphaBlend(hdc, 0, 0, getWidth(), 27, hdcPaint, 0, 0, getWidth(), 27, bfn);
}
ReleaseDC(hwnd, hdc);
return 0;
And use AlphaBlend to mix the standard frame with myself, but if I use SetWindowText(_T("")), then the title in Alt+Tab switcher gone.
I try to handle WM_GETTEXT message and return the caption string, but failed. How could I draw the caption text by myself but still make the title in alt+tab switcher?
Since you are already drawing a "custom caption bar" there is no reason to have it actually draw using the actual window's text
there are two ways to accomplish this, one using the traditional DrawCaption from Win9x Win32Api, the other is using the more fresher "theme api"
here is an example that uses both:
#include <Windows.h>
#include <Uxtheme.h>
#include <vssym32.h>
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
const LPCWSTR WINDOW_CLASS = L"Test Window Class";
const LPCWSTR WINDOW_CAPTION = L"This is my test window";
const LPCWSTR CUSTOM_CAPTION = L"Custom Caption Text";
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, PWSTR pCmdLine, int nCmdShow)
{
WNDCLASSEX wndClassEx = {};
wndClassEx.lpszClassName = WINDOW_CLASS;
wndClassEx.hInstance = hInstance;
wndClassEx.lpfnWndProc = WindowProc;
wndClassEx.cbSize = sizeof(wndClassEx);
wndClassEx.hCursor = (HCURSOR) LoadImage(NULL, MAKEINTRESOURCE(IDC_ARROW), IMAGE_CURSOR, 0, 0, LR_SHARED | LR_DEFAULTSIZE);
wndClassEx.style = CS_DBLCLKS | CS_DROPSHADOW;
ATOM registeredClass = RegisterClassEx(&wndClassEx);
HWND hwnd = CreateWindowEx(
0,
WINDOW_CLASS,
WINDOW_CAPTION,
WS_SYSMENU,
200, 200, 500, 300,
NULL, // parent
NULL, // menu
hInstance,
NULL // extra
);
if (hwnd == NULL)
{
return 0;
}
ShowWindow(hwnd, nCmdShow);
MSG msg = {};
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_LBUTTONDBLCLK:
case WM_DESTROY:
PostQuitMessage(0);
return 0;
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
// fill the window with a color
HBRUSH hbrush = CreateSolidBrush(RGB(33, 33, 33));
FillRect(hdc, &ps.rcPaint, hbrush);
DeleteObject(hbrush);
// get a drawing area
RECT rect = {};
GetClientRect(hwnd, &rect);
rect.bottom = rect.top + GetSystemMetrics(SM_CYCAPTION) + GetSystemMetrics(SM_CYEDGE) * 2;
// draw a simple win9x style caption (switch out the window text while drawing)
SetWindowText(hwnd, CUSTOM_CAPTION);
DrawCaption(hwnd, hdc, &rect, DC_GRADIENT | DC_TEXT | DC_ACTIVE | DC_ICON);
SetWindowText(hwnd, WINDOW_CAPTION);
// use theme framework
HTHEME htheme = OpenThemeData(hwnd, L"Window");
// move downwards and then use new APIs for size
rect.top += rect.bottom + 20;
rect.bottom = rect.top + GetThemeSysSize(htheme, SM_CYSIZE) + GetThemeSysSize(htheme, SM_CXPADDEDBORDER) * 2;
// draw the background
DrawThemeBackground(htheme, hdc, WP_CAPTION, CS_ACTIVE, &rect, &ps.rcPaint);
// load the caption font and save the old one
LOGFONTW captionfont = {};
GetThemeSysFont(htheme, TMT_CAPTIONFONT, &captionfont);
HFONT newfont = CreateFontIndirect(&captionfont);
HGDIOBJ oldfont = SelectObject(hdc, newfont);
// center the font and draw
rect.top += GetThemeSysSize(htheme, SM_CXPADDEDBORDER);
DrawThemeTextEx(htheme, hdc, WP_CAPTION, CS_ACTIVE, CUSTOM_CAPTION, -1, DT_CENTER, &rect, NULL);
// cleanup fonts
SelectObject(hdc, oldfont);
DeleteObject(newfont);
// adjust draw location, load icon and draw
rect.left += GetThemeSysSize(htheme, SM_CXPADDEDBORDER) * 2;
rect.top += GetThemeSysSize(htheme, SM_CXPADDEDBORDER);
HICON icon = (HICON) LoadImage(NULL, MAKEINTRESOURCE(IDI_APPLICATION), IMAGE_ICON, 0, 0, LR_SHARED | LR_DEFAULTSIZE);
DrawIconEx(hdc, rect.left, rect.top, icon, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), 0, NULL, DI_NORMAL);
// close theme
CloseThemeData(htheme);
EndPaint(hwnd, &ps);
}
return 0;
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
the resulting window looks like:
You can see that the 2 "custom drawn" title bars display custom text, not the text on the window.
A quick glance at the code will tell you that attempting the theme window caption using a custom routine is a lot more difficult than the legacy. The trade off of course is that it gives you way more control. You'll also take note that I switch out the window text to make it display what I want when using the legacy method. Additionally you need to remember that the legacy method takes its queues on how to draw itself from the styles associated with the window, if your window style has no icon, it will not draw one even if you specify it...
Either of these techniques will accomplish your goal. If I switched this code around not to draw multiple title bars and get rid of the default one created by the window style the result would look like:
you can see here how the task switch still displays the actual window text, and my "custom" caption bar looks like the real deal...
good luck, i hope this helps -ck
on a side note: i am running Windows8 and am not sure why the caption drawing routines are ignoring my theme... perhaps i forgot a directive