I have a window, which I want it to behave like a toggle button. Once clicked it will add 4px border and clicking after will make the border disappear. I figured how to make the window behave like a toggle button using BS_PUSHLIKE and Button_SetCheck() but can't seem to figure out how to adjust the border size for this window.
Thanks to all who take their time to help
Maybe you can use MoveWindow to resize the window, and then draw the border yourself, like this,
Draw a borderless window first:
HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
100, 100, 800, 600, nullptr, nullptr, hInstance, nullptr);
LONG lStyle = GetWindowLong(hWnd, GWL_STYLE);
lStyle &= ~(WS_CAPTION | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_SYSMENU);
SetWindowLong(hWnd, GWL_STYLE, lStyle);
Then handle the window border in the WM_LBUTTONDOWN message:
int num = 0;
case WM_LBUTTONDOWN:
{
RECT rcWind;
HDC dc = GetDC(hWnd);
GetWindowRect(hWnd, &rcWind);
if (num >= 0)
{
num--;
RECT rcClient;
MoveWindow(hWnd, rcWind.left - 4, rcWind.top - 4, 8 + rcWind.right - rcWind.left, 8 + rcWind.bottom - rcWind.top, TRUE);
GetClientRect(hWnd, &rcClient);
HPEN hPen = CreatePen(PS_SOLID, 4, RGB(255, 128, 1));
HGDIOBJ hOldPen = SelectObject(dc, hPen);
Rectangle(dc, rcClient.left, rcClient.top, rcClient.right, rcClient.bottom);
DeleteObject(hPen);
}
else if (num < 0)
{
MoveWindow(hWnd, rcWind.left + 4, rcWind.top + 4, rcWind.right - rcWind.left - 8, rcWind.bottom - rcWind.top - 8, TRUE);
num++;
}
}
break;
Related
I have a static text control that should resize along with the window, its text width is smaller than the whole line of text at first, so it clips like I need by default.
I would like to center the text vertically. When it is clipped the whole height is occupied, but when static's width is large enough and the whole text fits into one line it is up high. How can I center it wherever it is?
After some research, I found that you can customize the height of the upper left corner of the text to achieve vertical centering. Of course, the text can be truncated.
When the length of the static control is less than the length of the text, the text will be truncated. So we need to calculate the height and length of the text, with the help of GetTextExtentPoint32. Then DrawText does the rest, DT_WORDBREAK and DT_CENTER should be added.
See my code for specific details:
// Parent Hwnd
HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
0, 0, 800, 600, nullptr, nullptr, hInstance, nullptr);
// Window Process
float nScale_x = 1.0, nScale_y = 1.0;
HWND static_hwnd;
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
RECT rc; // the size of text
RECT rect; // the size of Rectangle
switch (message)
{
case WM_CREATE:
{
static_hwnd = CreateWindow(L"STATIC",
L"Some clipping text example",
WS_CHILD | WS_VISIBLE | WS_TABSTOP | SS_OWNERDRAW,
100, 100, 200, 50,
hWnd,
NULL,
NULL,
NULL);
return 1;
}
case WM_DRAWITEM:
{
LPDRAWITEMSTRUCT pDIS = (LPDRAWITEMSTRUCT)lParam;
if (pDIS->hwndItem == static_hwnd)
{
const wchar_t* text = L"Some clipping text example";
HDC hDC = pDIS->hDC;
RECT rect = pDIS->rcItem;
RECT rc;
int w = rect.right - rect.left;
int h;
if (w >= 180) // the length of "Some clipping text example"
{
h = 16;
}
else if (w < 92) //the length of "Some clipping"
{
h = 16 * 3;
}
else if (w < 180)
{
h = 16 * 2;
}
rc.left = rect.left;
rc.right = rect.right;
rc.top = ((rect.bottom - rect.top) - h) / 2;
rc.bottom = rect.bottom;
HBRUSH bg = (HBRUSH)(::GetStockObject(LTGRAY_BRUSH));
HPEN pn = (HPEN)(::GetStockObject(BLACK_PEN));
::SelectObject(hDC, bg);
//SIZE sz;
//GetTextExtentPoint32(hDC, text, 13, &sz);
::SelectObject(hDC, pn);
::SetTextColor(hDC, RGB(0, 0, 0));
::Rectangle(hDC, rect.left, rect.top, rect.right, rect.bottom);
::DrawText(hDC, text, wcslen(text), &rc, DT_WORDBREAK | DT_CENTER);
}
return TRUE;
}
case WM_SIZE:
{
GetWindowRect(hWnd, &rc);
nScale_x = (rc.right - rc.left) / 800.0;
nScale_y = (rc.bottom - rc.top) / 600.0;
SetWindowPos(static_hwnd, NULL, 100, 100, 200 * nScale_x, 50 * nScale_y, SWP_SHOWWINDOW);
}
...
Debug:
I specify a font and its size for my listview items using win32 api and it works properly in Windows Xp. I install it in Windows 7 and see size of fonts are too small and hard to read although i specified 17 for its size.
I increased default font sizes in Windows 7 but still the fonts in my program are too small.
This is the code that i specify font for the listview items inside Window procedure :
case WM_DRAWITEM:
{
LPDRAWITEMSTRUCT pDIS=(LPDRAWITEMSTRUCT)lParam;
HDC hDC=pDIS -> hDC;
RECT rc = pDIS -> rcItem;
HBRUSH bg = (HBRUSH) (::GetStockObject(DC_BRUSH));
HPEN pn=(HPEN)(::GetStockObject(NULL_PEN));
::SelectObject( hDC , bg );
::SelectObject( hDC , pn );
::SetTextColor( hDC , RGB(0,0,0));
HFONT hF;
hF=CreateFont(17, 0, 0, 0, FW_DONTCARE, FALSE, FALSE, FALSE, ANSI_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH | FF_SWISS, L"Tahoma");
HFONT hOldFont = (HFONT) SelectObject(hDC, hF);
if( (pDIS->itemID % 2) != 0 )
::SetDCBrushColor(hDC, RGB(255,255,255));
else{
::SetDCBrushColor(hDC, RGB(223, 241, 255));
}
::Rectangle( hDC , rc.left , rc.top , rc.right , rc.bottom );
char buffer[1000] = {0};
ListView_GetItemText(pDIS -> hwndItem, pDIS -> itemID, 0, (LPWSTR)buffer, 1000);
::DrawText(hDC, (LPWSTR)buffer, -1, &rc, DT_SINGLELINE | DT_VCENTER);
SelectObject(hDC, hOldFont);
DeleteObject(hF);
}
break;
How can i make Windows display my desired font size and not that small font?
Thanks!
Use SystemParametersInfo to find the default font as follows:
NONCLIENTMETRICS metrics;
metrics.cbSize = sizeof(NONCLIENTMETRICS);
::SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof(NONCLIENTMETRICS),
&metrics, 0);
hfont = CreateFontIndirect(&metrics.lfMessageFont);
Use .lfMessageFont for ListView and other child controls.
This will retrieve the correct font name and font size.
This font size is already adjusted for DPI settings of system and application.
You can create this font once during windows creation. Then assign it as the main font for the window and listview control. This will update the header control for the listview.
HFONT hfont;
...
case WM_CREATE:
{
if (!hfont)
{
NONCLIENTMETRICS metrics;
metrics.cbSize = sizeof(NONCLIENTMETRICS);
::SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof(NONCLIENTMETRICS),
&metrics, 0);
hfont = CreateFontIndirect(&metrics.lfMessageFont);
}
hListView = CreateWindow(WC_LISTVIEW ...);
SendMessage(hWnd, WM_SETFONT, (WPARAM)hfont, MAKELPARAM(TRUE, 0));
SendMessage(hListView, WM_SETFONT, (WPARAM)hfont, MAKELPARAM(TRUE, 0));
...
break;
}
Also, do not use (LPWSTR) cast to hide compiler warnings and errors. Use casting only when you are sure it's applicable. In this case, char and wchar_t are very different storage types, casting may in some special cases, but it's far from reliable.
char buffer[1000] creates a buffer of size 1000 bytes. But you are making a call to ListView_GetItemText to read 1000 Unicode characters, which in this case is 2000 bytes and results in buffer overrun. You can change as follows:
case WM_DRAWITEM:
{
LPDRAWITEMSTRUCT pDIS = (LPDRAWITEMSTRUCT)lParam;
HDC hDC = pDIS->hDC;
RECT rc = pDIS->rcItem;
COLORREF textcolor = RGB(0, 0, 0);
COLORREF bkcolor = RGB(255, 255, 255);
if((pDIS->itemID % 2) == 0)
{
bkcolor = RGB(223, 241, 255);
}
if(pDIS->itemState & ODS_SELECTED)
{
textcolor = RGB(255, 255, 255);
bkcolor = RGB(0, 0, 255);
}
SetDCBrushColor(hDC, bkcolor);
SelectObject(hDC, GetStockObject(DC_BRUSH));
SelectObject(hDC, GetStockObject(NULL_PEN));
SetTextColor(hDC, textcolor);
Rectangle(hDC, rc.left, rc.top, rc.right, rc.bottom);
auto oldfont = SelectObject(hDC, hfont);
wchar_t buffer[1000] = { 0 };
ListView_GetItemText(pDIS->hwndItem, pDIS->itemID, 0, buffer, 1000);
DrawText(hDC, buffer, -1, &rc, DT_SINGLELINE | DT_VCENTER);
SelectObject(hDC, oldfont);
return TRUE;
}
*hfont is not destroyed in above window procedure. It should be cleaned up elsewhere.
I needed to display flashing border in on top of chosen application's window (in my example the application is cmd.exe). I used layered windows for this purpose. Everything works fine, except the one thing: I can't bring target window (in my case - cmd.exe) in front if it is overlapped by another window (if another window is in the foreground). It works when I maximize/minimize target window, but doesn't work in case when target windows is overlapped. I can't restore in clicking on app's icon in the taskbar.
const COLORREF MY_COLOR_KEY = RGB(255, 128, 0);
HWND cmdHanlde = NULL;
constexpr unsigned int timerIdWindowUpdate = 1;
constexpr unsigned int timerIdFrameColor = 2;
bool tick = false;
bool minimized = false;
int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPWSTR lpCmdLine,
_In_ int nCmdShow)
{
WNDCLASSEX wc = {};
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpszClassName = L"MyTransparentFrame";
wc.hCursor = ::LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = NULL;
wc.lpfnWndProc = [](HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) -> LRESULT
{
switch (msg)
{
case WM_PAINT:
{
PAINTSTRUCT ps{};
HDC hdc = BeginPaint(hwnd, &ps);
RECT rc{}; GetClientRect(hwnd, &rc);
HPEN hPen = CreatePen(PS_SOLID, 5, tick ? RGB(255, 128, 1) : RGB(255, 201, 14));
HBRUSH hBrush = CreateSolidBrush(MY_COLOR_KEY);
HGDIOBJ hOldPen = SelectObject(hdc, hPen);
HGDIOBJ hOldBrush = SelectObject(hdc, hBrush);
Rectangle(hdc, rc.left, rc.top, rc.right, rc.bottom);
if (hOldPen)
SelectObject(hdc, hOldPen);
if (hOldBrush)
SelectObject(hdc, hOldBrush);
if (hPen)
DeleteObject(hPen);
if (hBrush)
DeleteObject(hBrush);
EndPaint(hwnd, &ps);
}
break;
case WM_TIMER:
{
if (wp == timerIdWindowUpdate)
{
WINDOWPLACEMENT windowPlacement = { sizeof(WINDOWPLACEMENT), };
if (::GetWindowPlacement(cmdHanlde, &windowPlacement))
{
if (windowPlacement.showCmd == SW_SHOWMINIMIZED
|| !IsWindowVisible(cmdHanlde))
{
minimized = true;
}
else
{
RECT rect = {};
DwmGetWindowAttribute(cmdHanlde, DWMWA_EXTENDED_FRAME_BOUNDS, &rect, sizeof(rect));
MONITORINFO monInfo;
monInfo.cbSize = sizeof(MONITORINFO);
GetMonitorInfoW(MonitorFromWindow(cmdHanlde, MONITOR_DEFAULTTONEAREST), &monInfo);
if (cmdHanlde != NULL && ::IsZoomed(cmdHanlde))
{
rect.left = monInfo.rcWork.left;
rect.top = monInfo.rcWork.top;
rect.bottom = monInfo.rcWork.bottom > rect.bottom ? rect.bottom : monInfo.rcWork.bottom;
rect.right = monInfo.rcWork.right > rect.right ? rect.right : monInfo.rcWork.right;
}
if (minimized)
{
::SetWindowPos(hwnd, cmdHanlde, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
minimized = false;
}
else
{
::SetWindowPos(cmdHanlde, hwnd, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
::SetWindowPos(hwnd, 0, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top,
SWP_SHOWWINDOW);
}
}
}
}
else if (wp == timerIdFrameColor)
{
tick = !tick;
::RedrawWindow(hwnd, NULL, NULL, RDW_INVALIDATE);
}
break;
}
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProcW(hwnd, msg, wp, lp);
}
return 0;
};
RegisterClassEx(&wc);
HWND hwnd = CreateWindowExW(WS_EX_TOOLWINDOW | WS_EX_NOACTIVATE | WS_EX_LAYERED | WS_EX_TRANSPARENT, wc.lpszClassName, L"", WS_POPUP | WS_VISIBLE | WS_DISABLED,
0, 0, 0, 0, nullptr, nullptr, nullptr, nullptr);
::SetTimer(hwnd, timerIdWindowUpdate, 50, NULL);
::SetTimer(hwnd, timerIdFrameColor, 500, NULL);
SetLayeredWindowAttributes(hwnd, MY_COLOR_KEY, 255, LWA_COLORKEY);
ShowWindow(hwnd, SW_SHOW);
cmdHanlde = FindWindow(L"ConsoleWindowClass", L"C:\\WINDOWS\\system32\\cmd.exe");
MSG msg;
while (GetMessage(&msg, nullptr, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (int)msg.wParam;
}
So the question here is how to restore target window after it was overlapped by another window?
if (minimized) {
ShowWindow(hwnd, SW_HIDE);
minimized = false;
} else {
SetWindowPos(hwnd, 0, rect.left, rect.top, rect.right - rect.left,
rect.bottom - rect.top, SWP_NOZORDER);
SetWindowPos(hwnd, GetNextWindow(cmdHandle, GW_HWNDPREV), 0, 0, 0, 0,
SWP_SHOWWINDOW | SWP_NOMOVE | SWP_NOSIZE);
}
My problem is that my button has a background color for some reason, and I do not know why. I do not put the desktop background in the entire area of the button.
case WM_CREATE:
{
hwndButton = CreateWindowEx(0, L"BUTTON", L"Some static text",
WS_CHILD | WS_VISIBLE | BS_OWNERDRAW,
25, 125, 300, 300, hWnd, 0, 0, 0);
}
case WM_DRAWITEM:
{
//RECT r;
LPDRAWITEMSTRUCT lpDIS = (LPDRAWITEMSTRUCT)lParam;
if (hwndButton == lpDIS->hwndItem) {
SetBkColor(lpDIS->hDC, RGB(0, 255, 0));
//FillRect(lpDIS->hDC,&r,CreateSolidBrush(RGB(100,100,200)));
SetTextColor(lpDIS->hDC, RGB(100, 0, 100));
WCHAR staticText[99] = L"test";
TextOut(lpDIS->hDC, lpDIS->rcItem.left, lpDIS->rcItem.top, staticText, 10);
}
Your code only draws as much of the button background as the text requires, use ExtTextOut and specify ETO_OPAQUE to fill the entire space. Either that or use FillRect to actually draw the button background and use ExtTextOut without ETO_OPAQUE to draw with a transparent background.
Having recently started developing with the Windows API, I decided some interactivity was in order. So the plan was to use Owner Drawn buttons that would change background colour when the mouse enters their rectangular area, and changes back to the original colour when the mouse leaves.
I made 3 such buttons and sub-classed them with their own custom window procedure. Now because there is really no such thing as a WM_MOUSEENTER message I used the WM_MOUSEMOVE and some mouse tracking to Fill the window with a certain colour. This basically means the rectangle gets filled every time the cursor moves inside the window.
I also used WM_MOUSELEAVE to fill the button when the mouse pointer moves outside the button's rectangle.
And this seems to work fine at first but after a while the rectangle filling will stop and the background will be stuck in either the hover colour or the default colour.
Here's the Code
#ifndef UWMENU_H_INCLUDED
#define UWMENU_H_INCLUDED
#endif // UWMENU_H_INCLUDED
#include <iostream>
using namespace std;
LRESULT CALLBACK DBWndProc(HWND, UINT, WPARAM, LPARAM);
WNDPROC DBWndProcOld[3];
class UWMenu{
public:
//WINDOW PROCEDURE FOR NEWS BUTTON
static LRESULT CALLBACK DBWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam){
HBRUSH BG_btnOne = CreateSolidBrush(NAV_BTN_DEF_CLR);
HBRUSH BG_btnOneOver = CreateSolidBrush(NAV_BTN_OVER_CLR);
int btnID = GetDlgCtrlID(hwnd);
cout << btnID << "\n";
switch(msg){
case WM_MOUSEMOVE:{
cout << "Mouse is hovering over button\n";
SetCursor(LoadCursor(NULL, IDC_HAND));
HDC hdc = GetDC(hwnd);
RECT rc;
rc.left = 0; //Top left X coordinate of button's rectangular area
rc.top = 0; //Top left Y coordinate of button's rectangular area
rc.right = NAV_BTN_WIDTH; //Bottom right X coordinate of button's rectangular area
rc.bottom = NAV_BTN_HEIGHT; //Bottom right Y coordinate of button's rectangular area
printf("Repainting button background color\n");
FillRect(hdc, &rc, BG_btnOneOver);
SetTextColor(hdc, RGB(255,255,255));
SetBkMode(hdc, TRANSPARENT);
if(btnID == NBID_NEWS){DrawText(hdc, TXT_BN_NEWS, -1, &rc, DT_SINGLELINE | DT_CENTER | DT_VCENTER);}
if(btnID == NBID_TOOLS){DrawText(hdc, TXT_BN_TOOLS, -1, &rc, DT_SINGLELINE | DT_CENTER | DT_VCENTER);}
if(btnID == NBID_EDITOR){DrawText(hdc, TXT_BN_EDITORS, -1, &rc, DT_SINGLELINE | DT_CENTER | DT_VCENTER);}
ReleaseDC(hwnd, hdc);
TRACKMOUSEEVENT tme;
tme.cbSize = sizeof(TRACKMOUSEEVENT);
tme.dwFlags = TME_LEAVE;
tme.hwndTrack = hwnd;
tme.dwHoverTime = HOVER_DEFAULT;
TrackMouseEvent(&tme);
}
break;
case WM_MOUSELEAVE:{
printf("You left me\n");
SetCursor(LoadCursor(NULL, IDC_ARROW));
HDC hdc = GetDC(hwnd);
RECT rc;
rc.left = 0; //Top left X coordinate of button's rectangular area
rc.top = 0; //Top left Y coordinate of button's rectangular area
rc.right = NAV_BTN_WIDTH; //Bottom right X coordinate of button's rectangular area
rc.bottom = NAV_BTN_HEIGHT; //Bottom right Y coordinate of button's rectangular area
FillRect(hdc, &rc, BG_btnOne);
SetTextColor(hdc, RGB(255,255,255));
SetBkMode(hdc, TRANSPARENT);
if(btnID == NBID_NEWS){DrawText(hdc, TXT_BN_NEWS, -1, &rc, DT_SINGLELINE | DT_CENTER | DT_VCENTER);}
if(btnID == NBID_TOOLS){DrawText(hdc, TXT_BN_TOOLS, -1, &rc, DT_SINGLELINE | DT_CENTER | DT_VCENTER);}
if(btnID == NBID_EDITOR){DrawText(hdc, TXT_BN_EDITORS, -1, &rc, DT_SINGLELINE | DT_CENTER | DT_VCENTER);}
ReleaseDC(hwnd, hdc);
TRACKMOUSEEVENT tme;
tme.cbSize = sizeof(TRACKMOUSEEVENT);
tme.dwFlags = TME_CANCEL;
tme.hwndTrack = hwnd;
tme.dwHoverTime = HOVER_DEFAULT;
TrackMouseEvent(&tme);
}
break;
}
return CallWindowProc(DBWndProcOld[btnID-200], hwnd, msg, wParam, lParam);
}
};
The mouse_move messages are being processed as the output from printf() and cout are shown in debug mode when the mouse moves over the buttons.
Is there any way to fix this? Something to do with mouse tracking perhaps?
I think the problem is that the button is sort of getting spammed with the FillRect() while the pointer is inside its rectangle.
Any help is appreciated.
This problem has been solved now. It was due to a GDI leak. The problem was solved by deleting brushes that were being created repeatedly in a parent window procedure.