Related
For an experiment I want to create lots of small windows. I mean a lot, like a thousand or so.
The windows are small, containing some labels only (AB):
I created a hundred of them as an experiment, but their display is not instantaneous, it is visible as they are put on the screen. Why is that?
I expected for a C/C++ program to be very fast, so that I don't see the windows put on the screen at all. Is it a wrong expectation? Or should I use some kind of lighter window type (I'm no Windows-programmer, so I'm just guessing) which can be put up much faster?
Here's the relevant part of the code:
HWND parent = 0;
for (int i = 0; i < 100; ++i)
{
HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_BORDER,
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);
if (!hWnd)
{
return FALSE;
}
if (parent == 0)
parent = hWnd;
else
SetWindowLong(hWnd, GWL_HWNDPARENT, (long)parent);
SetWindowLong(hWnd, GWL_STYLE, 0);
SetMenu(hWnd, NULL);
SetWindowPos(hWnd, HWND_TOP, 100 + (i * 20), 100, 20, 20, 0);
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
}
...
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
// TODO: Add any drawing code that uses hdc here...
SetBkColor(hdc, RGB(0,255,0));
TextOut(hdc, 1, 1, TEXT("AB"), strlen("AB"));
EndPaint(hWnd, &ps);
}
break;
SetWindowLong, SetMenu, SetWindowPos and ShowWindow can all be removed by giving the same information in the CreateWindowW arguments.
Then you can also remove the call to UpdateWindow.
Here its not the speed of C/C++ that matter, but the Win32 API calls which send windows messages.
I tried looking around and found a few solutions to detect a USB drive insertion, but none that I found were actually working in C.
I wanted to ask, how do I approach this problem?
What is the idea behind the detection process (Like, how is it done) ?
Thank you in advance! :)
The example at link https://msdn.microsoft.com/en-us/library/windows/desktop/aa363432(v=vs.85).aspx, given by Weather Vane, works in plain C, as I said in comments, with simple modifications.
Below there is the working modified code.
#define UNICODE 1
#define _UNICODE 1
#include <windows.h>
#include <stdio.h>
#include <tchar.h>
#include <strsafe.h>
#include <dbt.h>
#pragma comment(lib, "user32.lib" )
#pragma comment(lib, "Shell32.lib" )
void Main_OnDeviceChange( HWND hwnd, WPARAM wParam, LPARAM lParam );
char FirstDriveFromMask( ULONG unitmask ); //prototype
// This GUID is for all USB serial host PnP drivers, but you can replace it
// with any valid device class guid.
GUID WceusbshGUID = { 0x25dbce51, 0x6c8f, 0x4a72,
0x8a,0x6d,0xb5,0x4c,0x2b,0x4f,0xc8,0x35 };
// For informational messages and window titles
PWSTR g_pszAppName;
// Forward declarations
void OutputMessage(HWND hOutWnd, WPARAM wParam, LPARAM lParam);
void ErrorHandler(LPTSTR lpszFunction);
//
// DoRegisterDeviceInterfaceToHwnd
//
BOOL DoRegisterDeviceInterfaceToHwnd(
IN GUID InterfaceClassGuid,
IN HWND hWnd,
OUT HDEVNOTIFY *hDeviceNotify
)
// Routine Description:
// Registers an HWND for notification of changes in the device interfaces
// for the specified interface class GUID.
// Parameters:
// InterfaceClassGuid - The interface class GUID for the device
// interfaces.
// hWnd - Window handle to receive notifications.
// hDeviceNotify - Receives the device notification handle. On failure,
// this value is NULL.
// Return Value:
// If the function succeeds, the return value is TRUE.
// If the function fails, the return value is FALSE.
// Note:
// RegisterDeviceNotification also allows a service handle be used,
// so a similar wrapper function to this one supporting that scenario
// could be made from this template.
{
DEV_BROADCAST_DEVICEINTERFACE NotificationFilter;
ZeroMemory( &NotificationFilter, sizeof(NotificationFilter) );
NotificationFilter.dbcc_size = sizeof(DEV_BROADCAST_DEVICEINTERFACE);
NotificationFilter.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
NotificationFilter.dbcc_classguid = InterfaceClassGuid;
*hDeviceNotify = RegisterDeviceNotification(
hWnd, // events recipient
&NotificationFilter, // type of device
DEVICE_NOTIFY_WINDOW_HANDLE // type of recipient handle
);
if ( NULL == *hDeviceNotify )
{
ErrorHandler(TEXT("RegisterDeviceNotification"));
return FALSE;
}
return TRUE;
}
//
// MessagePump
//
void MessagePump(
HWND hWnd
)
// Routine Description:
// Simple main thread message pump.
//
// Parameters:
// hWnd - handle to the window whose messages are being dispatched
// Return Value:
// None.
{
MSG msg;
int retVal;
// Get all messages for any window that belongs to this thread,
// without any filtering. Potential optimization could be
// obtained via use of filter values if desired.
while( (retVal = GetMessage(&msg, NULL, 0, 0)) != 0 )
{
if ( retVal == -1 )
{
ErrorHandler(TEXT("GetMessage"));
break;
}
else
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
}
//
// WinProcCallback
//
INT_PTR WINAPI WinProcCallback(
HWND hWnd,
UINT message,
WPARAM wParam,
LPARAM lParam
)
// Routine Description:
// Simple Windows callback for handling messages.
// This is where all the work is done because the example
// is using a window to process messages. This logic would be handled
// differently if registering a service instead of a window.
// Parameters:
// hWnd - the window handle being registered for events.
// message - the message being interpreted.
// wParam and lParam - extended information provided to this
// callback by the message sender.
// For more information regarding these parameters and return value,
// see the documentation for WNDCLASSEX and CreateWindowEx.
{
LRESULT lRet = 1;
static HDEVNOTIFY hDeviceNotify;
static HWND hEditWnd;
static ULONGLONG msgCount = 0;
switch (message)
{
case WM_CREATE:
//
// This is the actual registration., In this example, registration
// should happen only once, at application startup when the window
// is created.
//
// If you were using a service, you would put this in your main code
// path as part of your service initialization.
//
if ( ! DoRegisterDeviceInterfaceToHwnd(
WceusbshGUID,
hWnd,
&hDeviceNotify) )
{
// Terminate on failure.
ErrorHandler(TEXT("DoRegisterDeviceInterfaceToHwnd"));
ExitProcess(1);
}
//
// Make the child window for output.
//
hEditWnd = CreateWindow(TEXT("EDIT"),// predefined class
NULL, // no window title
WS_CHILD | WS_VISIBLE | WS_VSCROLL |
ES_LEFT | ES_MULTILINE | ES_AUTOVSCROLL,
0, 0, 0, 0, // set size in WM_SIZE message
hWnd, // parent window
(HMENU)1, // edit control ID
(HINSTANCE) GetWindowLong(hWnd, GWL_HINSTANCE),
NULL); // pointer not needed
if ( hEditWnd == NULL )
{
// Terminate on failure.
ErrorHandler(TEXT("CreateWindow: Edit Control"));
ExitProcess(1);
}
// Add text to the window.
SendMessage(hEditWnd, WM_SETTEXT, 0,
(LPARAM)TEXT("Registered for USB device notification...\n"));
break;
case WM_SETFOCUS:
SetFocus(hEditWnd);
break;
case WM_SIZE:
// Make the edit control the size of the window's client area.
MoveWindow(hEditWnd,
0, 0, // starting x- and y-coordinates
LOWORD(lParam), // width of client area
HIWORD(lParam), // height of client area
TRUE); // repaint window
break;
case WM_DEVICECHANGE:
{
//
// This is the actual message from the interface via Windows messaging.
// This code includes some additional decoding for this particular device type
// and some common validation checks.
//
// Note that not all devices utilize these optional parameters in the same
// way. Refer to the extended information for your particular device type
// specified by your GUID.
//
PDEV_BROADCAST_DEVICEINTERFACE b = (PDEV_BROADCAST_DEVICEINTERFACE) lParam;
(void)b;
TCHAR strBuff[256];
Main_OnDeviceChange(hEditWnd, wParam, lParam);
// Output some messages to the window.
switch (wParam)
{
case DBT_DEVICEARRIVAL:
msgCount++;
StringCchPrintf(
strBuff, 256,
TEXT("Message %d: DBT_DEVICEARRIVAL\n"), msgCount);
break;
case DBT_DEVICEREMOVECOMPLETE:
msgCount++;
StringCchPrintf(
strBuff, 256,
TEXT("Message %d: DBT_DEVICEREMOVECOMPLETE\n"), msgCount);
break;
case DBT_DEVNODES_CHANGED:
msgCount++;
StringCchPrintf(
strBuff, 256,
TEXT("Message %d: DBT_DEVNODES_CHANGED\n"), msgCount);
break;
default:
msgCount++;
StringCchPrintf(
strBuff, 256,
TEXT("Message %d: WM_DEVICECHANGE message received, value %d unhandled.\n"),
msgCount, wParam);
break;
}
OutputMessage(hEditWnd, wParam, (LPARAM)strBuff);
}
break;
case WM_CLOSE:
if ( ! UnregisterDeviceNotification(hDeviceNotify) )
{
ErrorHandler(TEXT("UnregisterDeviceNotification"));
}
DestroyWindow(hWnd);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
// Send all other messages on to the default windows handler.
lRet = DefWindowProc(hWnd, message, wParam, lParam);
break;
}
return lRet;
}
#define WND_CLASS_NAME TEXT("SampleAppWindowClass")
//
// InitWindowClass
//
BOOL InitWindowClass(void)
// Routine Description:
// Simple wrapper to initialize and register a window class.
// Parameters:
// None
// Return Value:
// TRUE on success, FALSE on failure.
// Note:
// wndClass.lpfnWndProc and wndClass.lpszClassName are the
// important unique values used with CreateWindowEx and the
// Windows message pump.
{
WNDCLASSEX wndClass;
wndClass.cbSize = sizeof(WNDCLASSEX);
wndClass.style = CS_OWNDC | CS_HREDRAW | CS_VREDRAW;
wndClass.hInstance = (HINSTANCE)(GetModuleHandle(0));
wndClass.lpfnWndProc = (WNDPROC)(WinProcCallback);
wndClass.cbClsExtra = 0;
wndClass.cbWndExtra = 0;
wndClass.hIcon = LoadIcon(0,IDI_APPLICATION);
wndClass.hbrBackground = CreateSolidBrush(RGB(192,192,192));
wndClass.hCursor = LoadCursor(0, IDC_ARROW);
wndClass.lpszClassName = WND_CLASS_NAME;
wndClass.lpszMenuName = NULL;
wndClass.hIconSm = wndClass.hIcon;
if ( ! RegisterClassEx(&wndClass) )
{
ErrorHandler(TEXT("RegisterClassEx"));
return FALSE;
}
return TRUE;
}
//
// main
//
int __stdcall _tWinMain(
HINSTANCE hInstanceExe,
HINSTANCE hInst, // should not reference this parameter
PTSTR lpstrCmdLine,
int nCmdShow
)
{
//
// To enable a console project to compile this code, set
// Project->Properties->Linker->System->Subsystem: Windows.
//
int nArgC = 0;
PWSTR* ppArgV = CommandLineToArgvW(lpstrCmdLine, &nArgC);
g_pszAppName = ppArgV[0];
if ( ! InitWindowClass() )
{
// InitWindowClass displays any errors
return -1;
}
// Main app window
HWND hWnd = CreateWindowEx(
WS_EX_CLIENTEDGE | WS_EX_APPWINDOW,
WND_CLASS_NAME,
g_pszAppName,
WS_OVERLAPPEDWINDOW, // style
CW_USEDEFAULT, 0,
640, 480,
NULL, NULL,
hInstanceExe,
NULL);
if ( hWnd == NULL )
{
ErrorHandler(TEXT("CreateWindowEx: main appwindow hWnd"));
return -1;
}
// Actually draw the window.
ShowWindow(hWnd, SW_SHOWNORMAL);
UpdateWindow(hWnd);
// The message pump loops until the window is destroyed.
MessagePump(hWnd);
return 1;
}
//
// OutputMessage
//
void OutputMessage(
HWND hOutWnd,
WPARAM wParam,
LPARAM lParam
)
// Routine Description:
// Support routine.
// Send text to the output window, scrolling if necessary.
// Parameters:
// hOutWnd - Handle to the output window.
// wParam - Standard windows message code, not used.
// lParam - String message to send to the window.
// Return Value:
// None
// Note:
// This routine assumes the output window is an edit control
// with vertical scrolling enabled.
// This routine has no error checking.
{
LRESULT lResult;
LONG bufferLen;
LONG numLines;
LONG firstVis;
// Make writable and turn off redraw.
lResult = SendMessage(hOutWnd, EM_SETREADONLY, FALSE, 0L);
lResult = SendMessage(hOutWnd, WM_SETREDRAW, FALSE, 0L);
// Obtain current text length in the window.
bufferLen = SendMessage (hOutWnd, WM_GETTEXTLENGTH, 0, 0L);
numLines = SendMessage (hOutWnd, EM_GETLINECOUNT, 0, 0L);
firstVis = SendMessage (hOutWnd, EM_GETFIRSTVISIBLELINE, 0, 0L);
lResult = SendMessage (hOutWnd, EM_SETSEL, bufferLen, bufferLen);
// Write the new text.
lResult = SendMessage (hOutWnd, EM_REPLACESEL, 0, lParam);
// See whether scrolling is necessary.
if (numLines > (firstVis + 1))
{
int lineLen = 0;
int lineCount = 0;
int charPos;
// Find the last nonblank line.
numLines--;
while(!lineLen)
{
charPos = SendMessage(
hOutWnd, EM_LINEINDEX, (WPARAM)numLines, 0L);
lineLen = SendMessage(
hOutWnd, EM_LINELENGTH, charPos, 0L);
if(!lineLen)
numLines--;
}
// Prevent negative value finding min.
lineCount = numLines - firstVis;
lineCount = (lineCount >= 0) ? lineCount : 0;
// Scroll the window.
lResult = SendMessage(
hOutWnd, EM_LINESCROLL, 0, (LPARAM)lineCount);
}
// Done, make read-only and allow redraw.
lResult = SendMessage(hOutWnd, WM_SETREDRAW, TRUE, 0L);
lResult = SendMessage(hOutWnd, EM_SETREADONLY, TRUE, 0L);
}
//
// ErrorHandler
//
void ErrorHandler(
LPTSTR lpszFunction
)
// Routine Description:
// Support routine.
// Retrieve the system error message for the last-error code
// and pop a modal alert box with usable info.
// Parameters:
// lpszFunction - String containing the function name where
// the error occurred plus any other relevant data you'd
// like to appear in the output.
// Return Value:
// None
// Note:
// This routine is independent of the other windowing routines
// in this application and can be used in a regular console
// application without modification.
{
LPVOID lpMsgBuf;
LPVOID lpDisplayBuf;
DWORD dw = GetLastError();
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
dw,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR) &lpMsgBuf,
0, NULL );
// Display the error message and exit the process.
lpDisplayBuf = (LPVOID)LocalAlloc(LMEM_ZEROINIT,
(lstrlen((LPCTSTR)lpMsgBuf)
+ lstrlen((LPCTSTR)lpszFunction)+40)
* sizeof(TCHAR));
StringCchPrintf((LPTSTR)lpDisplayBuf,
LocalSize(lpDisplayBuf) / sizeof(TCHAR),
TEXT("%s failed with error %d: %s"),
lpszFunction, dw, lpMsgBuf);
MessageBox(NULL, (LPCTSTR)lpDisplayBuf, g_pszAppName, MB_OK);
LocalFree(lpMsgBuf);
LocalFree(lpDisplayBuf);
}
/*+##fnc##----------------------------------------------------------------*//*!
\brief Main_OnDeviceChange
\date Created on Sun Sep 10 15:10:10 2017
\date Modified on Sun Sep 10 15:10:10 2017
\*//*-##fnc##----------------------------------------------------------------*/
void Main_OnDeviceChange(HWND hwnd, WPARAM wParam, LPARAM lParam)
{
PDEV_BROADCAST_HDR lpdb = (PDEV_BROADCAST_HDR)lParam;
TCHAR szMsg[80];
switch (wParam)
{
case DBT_DEVICEARRIVAL:
// Check whether a CD or DVD was inserted into a drive.
if (lpdb->dbch_devicetype == DBT_DEVTYP_VOLUME)
{
PDEV_BROADCAST_VOLUME lpdbv = (PDEV_BROADCAST_VOLUME)lpdb;
if (lpdbv->dbcv_flags & DBTF_MEDIA)
{
StringCchPrintf(szMsg, sizeof(szMsg) / sizeof(szMsg[0]), TEXT("Drive %c: Media has arrived.\n"), FirstDriveFromMask(lpdbv->dbcv_unitmask));
//MessageBox( hwnd, szMsg, TEXT("WM_DEVICECHANGE"), MB_OK );
}
else
{
StringCchPrintf(szMsg, sizeof(szMsg) / sizeof(szMsg[0]), TEXT("Assigned drive letter '%c'\n"), FirstDriveFromMask(lpdbv->dbcv_unitmask));
}
OutputMessage(hwnd, wParam, (LPARAM)szMsg);
}
break;
case DBT_DEVICEREMOVECOMPLETE:
// Check whether a CD or DVD was removed from a drive.
if (lpdb->dbch_devicetype == DBT_DEVTYP_VOLUME)
{
PDEV_BROADCAST_VOLUME lpdbv = (PDEV_BROADCAST_VOLUME)lpdb;
if (lpdbv->dbcv_flags & DBTF_MEDIA)
{
StringCchPrintf(szMsg, sizeof(szMsg) / sizeof(szMsg[0]), TEXT("Drive %c: Media was removed.\n"), FirstDriveFromMask(lpdbv->dbcv_unitmask));
//MessageBox( hwnd, szMsg, TEXT("WM_DEVICECHANGE" ), MB_OK );
}
else
{
StringCchPrintf(szMsg, sizeof(szMsg) / sizeof(szMsg[0]), TEXT("Disconnected drive '%c'\n"), FirstDriveFromMask(lpdbv->dbcv_unitmask));
}
OutputMessage(hwnd, wParam, (LPARAM)szMsg);
}
break;
default:
/*
Process other WM_DEVICECHANGE notifications for other
devices or reasons.
*/
;
}
}
/*------------------------------------------------------------------
FirstDriveFromMask( unitmask )
Description
Finds the first valid drive letter from a mask of drive letters.
The mask must be in the format bit 0 = A, bit 1 = B, bit 2 = C,
and so on. A valid drive letter is defined when the
corresponding bit is set to 1.
Returns the first drive letter that was found.
--------------------------------------------------------------------*/
char FirstDriveFromMask( ULONG unitmask )
{
char i;
for (i = 0; i < 26; ++i)
{
if (unitmask & 0x1)
break;
unitmask = unitmask >> 1;
}
return( i + 'A' );
}
Basically remove reinterpret_cast to C casts. I.e.:
wndClass.hInstance = reinterpret_cast<HINSTANCE>(GetModuleHandle(0));
to
wndClass.hInstance = (HINSTANCE)(GetModuleHandle(0));
And add unused parameters name in functions definition (that is not allowed in standard C). I.e. change from:
int __stdcall _tWinMain(
HINSTANCE hInstanceExe,
HINSTANCE, // should not reference this parameter
PTSTR lpstrCmdLine,
int nCmdShow
)
to:
int __stdcall _tWinMain(
HINSTANCE hInstanceExe,
HINSTANCE hInst, // you **must define this parameter** even if it's not referenced
PTSTR lpstrCmdLine,
int nCmdShow
)
These simple modifications allow the use of almost all MS samples under plain-C compilers.
I have also added volume info.
To explain here in detail how the device notification framework works is just a waste of time. The MS documentation is complete and exhaustive, you can find all information on MSDN.
The translation I made give you the opportunity to study and test on your development environment in plain C, and this will allow to make experimentation.
Anyway the very basic essence is: the application (your program) registers itself with the notification server, that in turn, from now on until you unregister it, send all windows OS notification messages to your application. Each notification carries specific info data in specialized structures.
The full set of structure supplied with each notification (documented on MSDN) gives you indeep details on the type of change.
Working on a simple c GUI library, I'm starting with the winapi backend and having some problems right now calculating the preferred sizes of controls. I'm comparing my results with those of Windows.Forms.
Right now, I'm using values from Design Specifications and Guidelines - Visual Design Layout (like Buttons and TextBoxes being 14 "Dialog Logical Units" high) for calculating the pixel sizes in the winapi implementation, while keeping everything default with Windows Forms. I created these simple demo implementations:
Windows Forms (demo.cs):
using System.Drawing;
using System.Windows.Forms;
namespace W32CtlTest
{
public class Demo : Form
{
private FlowLayoutPanel panel;
private Button button;
private TextBox textBox;
public Demo() : base()
{
Text = "winforms";
panel = new FlowLayoutPanel();
button = new Button();
button.Text = "test";
button.Click += (sender, args) =>
{
Close();
};
panel.Controls.Add(button);
textBox = new TextBox();
panel.Controls.Add(textBox);
Controls.Add(panel);
}
protected override Size DefaultSize
{
get
{
return new Size(240,100);
}
}
public static void Main(string[] argv)
{
if (argv.Length < 1 || argv[0] != "-s")
{
Application.EnableVisualStyles();
}
Application.Run(new Demo());
}
}
}
compile with C:\Windows\Microsoft.NET\Framework\v4.0.30319\csc.exe /out:demo.exe /lib:C:\Windows\Microsoft.NET\Framework\v4.0.30319 /reference:System.Windows.Forms.dll,System.Drawing.dll demo.cs
Win32 API (demo.c):
#include <string.h>
#include <windows.h>
#include <commctrl.h>
static HINSTANCE instance;
static HWND mainWindow;
static HWND button;
static HWND textBox;
#define WC_mainWindow L"W32CtlTestDemo"
#define CID_button 0x101
static NONCLIENTMETRICSW ncm;
static HFONT messageFont;
static TEXTMETRICW messageFontMetrics;
static int buttonWidth;
static int buttonHeight;
static int textBoxWidth;
static int textBoxHeight;
/* hack to enable visual styles without relying on manifest
* found at http://stackoverflow.com/a/10444161
* modified for unicode-only code */
static int enableVisualStyles(void)
{
wchar_t dir[MAX_PATH];
ULONG_PTR ulpActivationCookie = 0;
ACTCTXW actCtx =
{
sizeof(actCtx),
ACTCTX_FLAG_RESOURCE_NAME_VALID
| ACTCTX_FLAG_SET_PROCESS_DEFAULT
| ACTCTX_FLAG_ASSEMBLY_DIRECTORY_VALID,
L"shell32.dll", 0, 0, dir, (LPWSTR)124,
0, 0
};
UINT cch = GetSystemDirectoryW(dir, sizeof(dir) / sizeof(*dir));
if (cch >= sizeof(dir) / sizeof(*dir)) { return 0; }
dir[cch] = L'\0';
ActivateActCtx(CreateActCtxW(&actCtx), &ulpActivationCookie);
return (int) ulpActivationCookie;
}
static void init(void)
{
INITCOMMONCONTROLSEX icx;
icx.dwSize = sizeof(INITCOMMONCONTROLSEX);
icx.dwICC = ICC_WIN95_CLASSES;
InitCommonControlsEx(&icx);
ncm.cbSize = sizeof(ncm);
SystemParametersInfoW(SPI_GETNONCLIENTMETRICS, ncm.cbSize, &ncm, 0);
messageFont = CreateFontIndirectW(&ncm.lfStatusFont);
HDC dc = GetDC(0);
SelectObject(dc, (HGDIOBJ) messageFont);
GetTextMetricsW(dc, &messageFontMetrics);
SIZE sampleSize;
GetTextExtentExPointW(dc,
L"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
52, 0, 0, 0, &sampleSize);
ReleaseDC(0, dc);
buttonWidth = MulDiv(sampleSize.cx, 50, 4 * 52);
buttonHeight = MulDiv(messageFontMetrics.tmHeight, 14, 8);
textBoxWidth = 100;
textBoxHeight = MulDiv(messageFontMetrics.tmHeight, 14, 8);
instance = GetModuleHandleW(0);
}
static LRESULT CALLBACK wproc(HWND w, UINT msg, WPARAM wp, LPARAM lp)
{
switch (msg)
{
case WM_CREATE:
button = CreateWindowExW(0, L"Button", L"test",
WS_CHILD|WS_VISIBLE|BS_PUSHBUTTON,
2, 2, buttonWidth, buttonHeight,
w, (HMENU)CID_button, instance, 0);
SendMessageW(button, WM_SETFONT, (WPARAM)messageFont, 0);
textBox = CreateWindowExW(WS_EX_CLIENTEDGE, L"Edit", L"",
WS_CHILD|WS_VISIBLE|ES_AUTOHSCROLL,
6 + buttonWidth, 2, textBoxWidth, textBoxHeight,
w, 0, instance, 0);
SendMessageW(textBox, WM_SETFONT, (WPARAM)messageFont, 0);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
case WM_COMMAND:
switch (LOWORD(wp))
{
case CID_button:
DestroyWindow(w);
break;
}
break;
}
return DefWindowProcW(w, msg, wp, lp);
}
int main(int argc, char **argv)
{
if (argc < 2 || strcmp(argv[1], "-s"))
{
enableVisualStyles();
}
init();
WNDCLASSEXW wc;
memset(&wc, 0, sizeof(wc));
wc.cbSize = sizeof(wc);
wc.hInstance = instance;
wc.lpszClassName = WC_mainWindow;
wc.lpfnWndProc = wproc;
wc.hbrBackground = (HBRUSH) COLOR_WINDOW;
wc.hCursor = LoadCursorA(0, IDC_ARROW);
RegisterClassExW(&wc);
mainWindow = CreateWindowExW(0, WC_mainWindow, L"winapi",
WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 240, 100,
0, 0, instance, 0);
ShowWindow(mainWindow, SW_SHOWNORMAL);
MSG msg;
while (GetMessageW(&msg, 0, 0, 0) > 0)
{
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
return (int)msg.wParam;
}
compile with gcc -odemo.exe -O2 demo.c -lgdi32 -lcomctl32
The test code is also available on github
It looks like this on windows 10, with visual styles enabled in the upper row and disabled in the lower row:
One thing I soon found out is that Windows.Forms doesn't use the message font (as I had expected) but instead uses the DEFAULT_GUI_FONT Although that's not the right thing to do, I changed my win32 code accordingly so I can compare the results better:
For completeness, here is what it looks like on windows 7 without visual styles:
Now my questions are:
Is it correct to use the message font? So, Windows.Forms definitely got this one "wrong"?
Obviously Windows.Forms uses the 14 DLU height for Buttons, but some smaller height for TextBoxes. This contradicts the Design Specifications. So is Windows.Forms wrong here as well? Or should TextBoxes in fact be smaller, so the text doesn't look like it's "hanging from the ceiling"? I think this does look better the way Windows.Forms does it.
Comparing visual styles enabled/disabled, I find that without visual styles, I get the same height for my button and my text box, but with visual styles enabled on windows 10, the text box is actually higher. Is there something like "theme specific metrics" and if so, how can I use that to correct my calculations?
This is only a partial answer I'm adding here for reference:
Indeed, using the DEFAULT_GUI_FONT is wrong according to this blog entry by Raymond Chen. So, no need to trust winforms to do "the right thing".
The Design Specifications indicate that Edit Controls should be the same height as Buttons (14 DLU). To convert these to pixel sizes, the Dialog Base Units (DBU) are needed, and while GetDialogBaseUnits() only returns them for the system font, there are MSDN articles describing how to calculate them for other fonts.
1 vertical DBU corresponds to 8 DLU, so an Edit control will be 6 DLU higher than the text it contains. This doesn't look so nice, because the Edit control doesn't center the text vertically but instead aligns it at the top. winforms avoids this by calculating a smaller size for an Edit control. The drawback is that an Edit control will not align nicely next to a Button.
I found a kind of "hacky" solution to that problem by shrinking the client area of the Edit control in an overridden window proc. The following code compares the results (and contains controls using the system font for completeness):
#include <stdlib.h>
#include <string.h>
#include <windows.h>
#include <commctrl.h>
typedef struct PaddedControl
{
WNDPROC baseWndProc;
int vshrink;
} PaddedControl;
static HINSTANCE instance;
static HWND mainWindow;
static HWND buttonSF;
static HWND textBoxSF;
static HWND buttonMF;
static HWND textBoxMF;
static HWND buttonMFC;
static HWND textBoxMFC;
static PaddedControl textBoxMFCPadded;
#define WC_mainWindow L"W32CtlTestDemo"
static NONCLIENTMETRICSW ncm;
static HFONT messageFont;
static TEXTMETRICW messageFontMetrics;
static int controlHeightSF;
static int controlHeightMF;
static int buttonWidthSF;
static int buttonWidthMF;
/* hack to enable visual styles without relying on manifest
* found at http://stackoverflow.com/a/10444161
* modified for unicode-only code */
static int enableVisualStyles(void)
{
wchar_t dir[MAX_PATH];
ULONG_PTR ulpActivationCookie = 0;
ACTCTXW actCtx =
{
sizeof(actCtx),
ACTCTX_FLAG_RESOURCE_NAME_VALID
| ACTCTX_FLAG_SET_PROCESS_DEFAULT
| ACTCTX_FLAG_ASSEMBLY_DIRECTORY_VALID,
L"shell32.dll", 0, 0, dir, (LPWSTR)124,
0, 0
};
UINT cch = GetSystemDirectoryW(dir, sizeof(dir) / sizeof(*dir));
if (cch >= sizeof(dir) / sizeof(*dir)) { return 0; }
dir[cch] = L'\0';
ActivateActCtx(CreateActCtxW(&actCtx), &ulpActivationCookie);
return (int) ulpActivationCookie;
}
static void init(void)
{
INITCOMMONCONTROLSEX icx;
icx.dwSize = sizeof(INITCOMMONCONTROLSEX);
icx.dwICC = ICC_WIN95_CLASSES;
InitCommonControlsEx(&icx);
ncm.cbSize = sizeof(ncm);
SystemParametersInfoW(SPI_GETNONCLIENTMETRICS, ncm.cbSize, &ncm, 0);
messageFont = CreateFontIndirectW(&ncm.lfStatusFont);
LONG sysDbu = GetDialogBaseUnits();
HDC dc = GetDC(0);
SelectObject(dc, (HGDIOBJ) messageFont);
GetTextMetricsW(dc, &messageFontMetrics);
SIZE sampleSize;
GetTextExtentExPointW(dc,
L"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
52, 0, 0, 0, &sampleSize);
ReleaseDC(0, dc);
controlHeightSF = MulDiv(HIWORD(sysDbu), 14, 8);
controlHeightMF = MulDiv(messageFontMetrics.tmHeight, 14, 8);
buttonWidthSF = MulDiv(LOWORD(sysDbu), 50, 4);
buttonWidthMF = MulDiv(sampleSize.cx, 50, 4 * 52);
instance = GetModuleHandleW(0);
}
static LRESULT CALLBACK paddedControlProc(
HWND w, UINT msg, WPARAM wp, LPARAM lp)
{
PaddedControl *self = (PaddedControl *)GetPropW(w, L"paddedControl");
WNDCLASSEXW wc;
switch (msg)
{
case WM_ERASEBKGND:
wc.cbSize = sizeof(wc);
GetClassInfoExW(0, L"Edit", &wc);
RECT cr;
GetClientRect(w, &cr);
cr.top -= self->vshrink;
cr.bottom += self->vshrink;
HDC dc = GetDC(w);
FillRect(dc, &cr, wc.hbrBackground);
ReleaseDC(w, dc);
return 1;
case WM_NCCALCSIZE:
if (!wp) break;
LRESULT result = CallWindowProcW(self->baseWndProc, w, msg, wp, lp);
NCCALCSIZE_PARAMS *p = (NCCALCSIZE_PARAMS *)lp;
int height = p->rgrc[0].bottom - p->rgrc[0].top;
self->vshrink = 0;
if (height > messageFontMetrics.tmHeight + 3)
{
self->vshrink = (height - messageFontMetrics.tmHeight - 3) / 2;
p->rgrc[0].top += self->vshrink;
p->rgrc[0].bottom -= self->vshrink;
}
return result;
}
return CallWindowProcW(self->baseWndProc, w, msg, wp, lp);
}
static LRESULT CALLBACK wproc(HWND w, UINT msg, WPARAM wp, LPARAM lp)
{
switch (msg)
{
case WM_CREATE:
buttonSF = CreateWindowExW(0, L"Button", L"sysfont",
WS_CHILD|WS_VISIBLE|BS_PUSHBUTTON,
4, 4, buttonWidthSF, controlHeightSF,
w, 0, instance, 0);
buttonMF = CreateWindowExW(0, L"Button", L"msgfont",
WS_CHILD|WS_VISIBLE|BS_PUSHBUTTON,
4, 8 + controlHeightSF, buttonWidthMF, controlHeightMF,
w, 0, instance, 0);
SendMessageW(buttonMF, WM_SETFONT, (WPARAM)messageFont, 0);
buttonMFC = CreateWindowExW(0, L"Button", L"msgfont adj",
WS_CHILD|WS_VISIBLE|BS_PUSHBUTTON,
4, 12 + controlHeightSF + controlHeightMF,
buttonWidthMF, controlHeightMF,
w, 0, instance, 0);
SendMessageW(buttonMFC, WM_SETFONT, (WPARAM)messageFont, 0);
textBoxSF = CreateWindowExW(WS_EX_CLIENTEDGE, L"Edit", L"abcdefgh",
WS_CHILD|WS_VISIBLE|ES_AUTOHSCROLL,
8 + buttonWidthSF, 4, 100, controlHeightSF,
w, 0, instance, 0);
textBoxMF = CreateWindowExW(WS_EX_CLIENTEDGE, L"Edit", L"abcdefgh",
WS_CHILD|WS_VISIBLE|ES_AUTOHSCROLL,
8 + buttonWidthMF, 8 + controlHeightSF,
100, controlHeightMF,
w, 0, instance, 0);
SendMessageW(textBoxMF, WM_SETFONT, (WPARAM)messageFont, 0);
textBoxMFC = CreateWindowExW(WS_EX_CLIENTEDGE, L"Edit", L"abcdefgh",
WS_CHILD|WS_VISIBLE|ES_AUTOHSCROLL,
8 + buttonWidthMF, 12 + controlHeightSF + controlHeightMF,
100, controlHeightMF,
w, 0, instance, 0);
memset(&textBoxMFCPadded, 0, sizeof(PaddedControl));
textBoxMFCPadded.baseWndProc = (WNDPROC)SetWindowLongPtr(
textBoxMFC, GWLP_WNDPROC, (LONG_PTR)paddedControlProc);
SetPropW(textBoxMFC, L"paddedControl", &textBoxMFCPadded);
SetWindowPos(textBoxMFC, 0, 0, 0, 0, 0,
SWP_NOOWNERZORDER|SWP_NOSIZE|SWP_NOMOVE|SWP_FRAMECHANGED);
SendMessageW(textBoxMFC, WM_SETFONT, (WPARAM)messageFont, 0);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
}
return DefWindowProcW(w, msg, wp, lp);
}
int main(int argc, char **argv)
{
if (argc < 2 || strcmp(argv[1], "-s"))
{
enableVisualStyles();
}
init();
WNDCLASSEXW wc;
memset(&wc, 0, sizeof(wc));
wc.cbSize = sizeof(wc);
wc.hInstance = instance;
wc.lpszClassName = WC_mainWindow;
wc.lpfnWndProc = wproc;
wc.hbrBackground = (HBRUSH) COLOR_WINDOW;
wc.hCursor = LoadCursorA(0, IDC_ARROW);
RegisterClassExW(&wc);
mainWindow = CreateWindowExW(0, WC_mainWindow, L"fontdemo",
WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 240, 180,
0, 0, instance, 0);
ShowWindow(mainWindow, SW_SHOWNORMAL);
MSG msg;
while (GetMessageW(&msg, 0, 0, 0) > 0)
{
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
return (int)msg.wParam;
}
The last row of controls using this hack is the best I could achieve so far:
As you can see, a problem that still persists is that the heights of Button and Edit controls look different with the visual styles theme of Windows 10. So I'd still be happy to see a better answer to this question.
I'm fairly new to WINAPI, and I need some help doing text output. I have an array of pixels that I write to with functions and then periodically blit onto the screen using the following functions:
DWORD WINAPI tickThreadProc(HANDLE handle) {
ShowWindow( hwnd, SW_SHOW );
HDC hdc = GetDC( hwnd );
hdcMem = CreateCompatibleDC( hdc );
HBITMAP hbmOld = (HBITMAP)SelectObject( hdcMem, hbmp );
int delay = 1000 / fps;
InitPhys();
LoadIMGs();
for ( ;; ) {
onFrame( pixels );
BitBlt( hdc, gLeft, gTop, width, height, hdcMem, 0, 0, SRCCOPY );
// Wait
Sleep( delay );
// Physics
SimPhys();
}
SelectObject( hdcMem, hbmOld );
DeleteDC( hdc );
return 0;
}
void MakeSurface(HWND hwnd) {
BITMAPINFO bmi;
bmi.bmiHeader.biSize = sizeof(BITMAPINFO);
bmi.bmiHeader.biWidth = width;
bmi.bmiHeader.biHeight = -height; // Order pixels from top to bottom
bmi.bmiHeader.biPlanes = 1;
bmi.bmiHeader.biBitCount = 32; // last byte not used, 32 bit for alignment
bmi.bmiHeader.biCompression = BI_RGB;
bmi.bmiHeader.biSizeImage = 0;
bmi.bmiHeader.biXPelsPerMeter = 0;
bmi.bmiHeader.biYPelsPerMeter = 0;
bmi.bmiHeader.biClrUsed = 0;
bmi.bmiHeader.biClrImportant = 0;
bmi.bmiColors[0].rgbBlue = 0;
bmi.bmiColors[0].rgbGreen = 0;
bmi.bmiColors[0].rgbRed = 0;
bmi.bmiColors[0].rgbReserved = 0;
HDC hdc = GetDC( hwnd );
// Create DIB section to always give direct access to pixels
hbmp = CreateDIBSection( hdc, &bmi, DIB_RGB_COLORS, (void**)&pixels, NULL, 0 );
DeleteDC( hdc );
// Create a new thread to use as a timer
hTickThread = CreateThread( NULL, 0, &tickThreadProc, NULL,0, NULL );
}
This is modified off some code I found on the internet. The pixel struct has 4 ints for r, g, b, and a.
I need to do text output and loading a picture for text is impractical. Any help?
First of all, if you use GetDC to get a handle to device context, you must use ReleaseDC when you're done with it. DeleteDC is only for device contexts that you created.
To draw text to this window, you can use functions like TextOut or DrawText using that DC (before you release it).
PAINTSTRUCT is for handling WM_PAINT messages (which is the more common way to draw to a Window). It looks like you're instead trying to draw directly from another thread on a regular basis. GDI isn't very good at dealing with multiple threads, so you might have some problems with this approach. But if your BitBlts are working, then a TextOut should work as well.
I have been playing with C and win32 recently and the following has me balked:
case WM_PAINT:
g_crntRect = (RECT*) malloc(sizeof(RECT));
GetWindowRect(hwnd, g_crntRect);
hpen = CreatePen(PS_SOLID, 1, RGB(255,25,5));
hdc = BeginPaint (hwnd, &ps) ;
oldPen = SelectObject(hdc, hpen);
drawRects(hwnd, hdc);
//Rectangle(hdc, 0, 0, 840, 525);
SelectObject(hdc,oldPen);
DeleteObject(hpen);
EndPaint (hwnd, &ps) ;
return 0 ;
So, if I call my own method above for drawing rectangles, it draws nothing, however the call to draw the rectangle in the WM_PAINT that I have commented above succeeds without a problem.
Here is my method:
BOOL drawRects(HWND hwnd, HDC hdc)
{
char buffer[50];
BOOL res = FALSE;
RECT tempRect = {0};
char quadStr[6] = "";
int i = 0;
quadStr[i]='*';
OutputDebugString("Going to draw");
for (i = 1; i <= 4; i++)
{
//get rect for each quadrent from the parent
OutputDebugString("inside for");
getRect(g_crntRect, &tempRect, i);
OutputDebugString("got rectr");;
res = Rectangle(hdc, tempRect.right, tempRect.top, tempRect.right, tempRect.bottom);
if (res == FALSE)
{
OutputDebugString("false");;
sprintf(buffer, "Error: %ld", GetLastError());
OutputDebugString(buffer);
}
else
{
OutputDebugString("drew");;
}
quadStr[i]='*';
printRect(quadStr, &tempRect);
}
return TRUE;
}
Looking at debug output, everything seems fine. Proper values are being passed into the Rectangle method. However, I wonder if I am not passing HDC correctly?
Any ideas?
Looks like a simple typo. In your method you have:
res = Rectangle(hdc, tempRect.right, tempRect.top, tempRect.right, tempRect.bottom);
The second parameter should be tempRect.left not tempRect.right. You're trying to draw a zero-width rectangle.
Change
BOOL drawRects(HWND hwnd, HDC* hdc)
to
BOOL drawRects(HWND hwnd, HDC hdc)
A Windows handle is actually a pointer, so there is no need to pass it by reference. But if you do, you would need to call your function as drawRects(hwnd, &hdc);
In general you don't paint outside the WM_PAINT handler. If you want your window to be updated, just call InvalidateRect on the area you want to be redrawn. That will trigger the WM_PAINT call which will then repaint the window.
Is there a reason you need to paint the window outside the paint handler?