I've been trying to make a simple window using WinAPI, it compiles and links with some warnings, when I open the program the window shows up, but when I try to interact with it, a windows warning sound is played and it doesn't allow me to use the window, not even close it, I have to use task manager.
To compile it I'm using Microsoft's "cl.exe" with the command line:
cl /c window.c
and the warnings are:
window.c(39): warning C4133: 'function': incompatible types, from 'LPSTR' to 'HINSTANCE'
window.c(40): warning C4133: 'function': incompatible types, from 'LPSTR' to 'HINSTANCE'
window.c(41): warning C4047: '=': 'HBRUSH' differs in levels of indirection from 'int'
To link I use crinkler with the commandline:
crinkler /NODEFAULTLIB /ENTRY:main /SUSBYSTEM:WINDOWS /TINYHEADER /TINYIMPORT /OUT:c.exe window.obj kernel32.lib user32.lib
and the warning is:
: warning LNK: Entry point not at start of section, jump necessary
The code I'm using is:
#include <windows.h>
#define WINDOW_STYLE WS_VISIBLE+WS_OVERLAPPEDWINDOW-WS_THICKFRAME-WS_MAXIMIZE
#define WINDOW_WIDTH 640
#define WINDOW_HEIGHT 480
static const char* window_name = "My Window";
RECT window_rect = {0, 0, WINDOW_HEIGHT, WINDOW_WIDTH};
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch(uMsg)
{
case WM_CLOSE:
PostQuitMessage(0);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
}
return DefWindowProcA(hwnd, uMsg, wParam, lParam);
}
int main()
{
int ret = 0;
HMODULE instance = GetModuleHandleA(NULL);
AdjustWindowRect(&window_rect, WINDOW_STYLE, FALSE);
WNDCLASSEXA window_class;
window_class.cbSize = 48;
window_class.style = CS_OWNDC|CS_HREDRAW|CS_VREDRAW;
window_class.lpfnWndProc = WindowProc;
window_class.cbClsExtra = 0;
window_class.cbWndExtra = 0;
window_class.hInstance = instance;
window_class.hIcon = LoadIconA(IDI_APPLICATION, 0);
window_class.hCursor = LoadCursorA(IDI_APPLICATION, 0);
window_class.hbrBackground = COLOR_WINDOW;
window_class.lpszMenuName = 0;
window_class.lpszClassName = window_name;
window_class.hIconSm = 0;
if (RegisterClassExA(&window_class)) {
HWND window = CreateWindowExA(
0,
window_name,
window_name,
WINDOW_STYLE,
CW_USEDEFAULT,
CW_USEDEFAULT,
window_rect.right - window_rect.left,
window_rect.bottom - window_rect.top,
0,
0,
instance,
NULL);
ShowWindow(window, SW_SHOW);
UpdateWindow(window);
MSG msg;
while(1)
{
if (PeekMessageA(&msg, 0, 0, 0, PM_REMOVE)) {
if (msg.message == WM_QUIT) {
ExitProcess(0);
break;
} else {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
}
} else {
ret = 1;
}
ExitProcess(0);
return ret;
}
Does anybody know how to fix it? Thanks.
The following line doesn't do what you think it does:
#define WINDOW_STYLE WS_VISIBLE+WS_OVERLAPPEDWINDOW-WS_THICKFRAME-WS_MAXIMIZE
While somewhat common, and occasionally even producing the correct value, it is a very brittle way to implement bit manipulations. In this particular case, the expression expands to:
0x10000000L + 0x00CF0000L - 0x00040000L - 0x01000000L
This produces the value 0xFFCB0000L1 (due to accidentally using WS_MAXIMIZE instead of WS_MAXIMIZEBOX). This translates to a window with the following styles:
WS_POPUP
WS_CHILD (note, that this is already mutually exclusive with WS_POPUP)
WS_ICONIC
WS_VISIBLE
WS_DISABLED
...
As documented:
WS_DISABLED: The window is initially disabled. A disabled window cannot receive input from the user. To change this after a window has been created, use the EnableWindow function.
That explains why you cannot interact with your window. The fix is simple: Use bitwise operators for bit manipulations instead of arithmetic operators:
#define WINDOW_STYLE (WS_VISIBLE | WS_OVERLAPPEDWINDOW & ~WS_THICKFRAME & ~WS_MAXIMIZE)
1 Leaving the fact, that the behavior for signed overflow is undefined aside.
Related
MSDN describes that lParam of WM_MOUSEMOVE is 2 shorts due to it needing to be compatible with virtual coordinates because it acts as a redirected event if capture is set, which is clear. However, negative coordinates are still received under normal circumstances when moving the mouse slightly outside of the window, to be exact a bonus 5 pixels in all directions except up (which is where the caption is, and it'd go to WM_NCMOUSEMOVE instead).
I initially suspected this was to do with the drop shadow technically being part of the window (like with the output of AdjustWindowRectEx's rectangle including it) since you can receive events in some of the shadow, but the values of that don't match up, and clamping the values to the client area's size doesn't feel intended. Where do the bonus 5 pixels (on my system, Windows 10 Education 2004) come from, especially considering that shouldn't even be a part of the client area to my knowledge, and is there a clean/intended way to dodge unwanted values?
I've seen some discussion about the area you can grab to resize the window potentially being related, but my window isn't resizable (doesn't have a thick frame).
Edit: After tinkering, it seems this is related to the window style. Here's a reproducible sample (hopefully on other machines, too):
#include <Windows.h>
#include <stdio.h>
// Remove `^ WS_THICKFRAME` and the bug vanishes!
#define WINDOW_STYLE ((WS_OVERLAPPEDWINDOW | WS_VISIBLE) ^ WS_THICKFRAME)
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
if (message == WM_MOUSEMOVE) {
POINTS mouse = MAKEPOINTS(lParam);
printf("WM_MOUSEMOVE # x=%hd, y=%hd\n", mouse.x, mouse.y);
} else if (message == WM_DESTROY) {
PostQuitMessage(0);
}
return DefWindowProcW(hWnd, message, wParam, lParam);
}
int main(void) {
WNDCLASSEXW wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = GetModuleHandle(NULL);
wcex.hIcon = NULL;
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wcex.lpszMenuName = NULL;
wcex.lpszClassName = L"SO_Example";
wcex.hIconSm = NULL;
RECT windowRect = { 0, 0, 600, 600 };
AdjustWindowRectEx(&windowRect, WINDOW_STYLE, FALSE, 0);
ATOM windowClass = RegisterClassExW(&wcex);
HWND hWnd = CreateWindowExW(
0,
(LPCWSTR)windowClass,
L"hello stackoverflow!",
WINDOW_STYLE,
CW_USEDEFAULT, CW_USEDEFAULT,
windowRect.right - windowRect.left,
windowRect.bottom - windowRect.top,
NULL,
NULL,
GetModuleHandle(NULL),
NULL
);
MSG msg;
while (GetMessageW(&msg, NULL, 0, 0) != 0) {
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
return 0;
}
Here's that sample in action: https://i.imgur.com/LfCe9od.mp4
It appears 5 is the border size (1) and the border padding (4) added up (aka the area the user can use to resize the window), so it's safe to discard values outside of the client rectangle, even if it's a little strange that Win32 is reporting events in that area.
I am searching for the fastest possible way in C to determine if any key on the keyboard has been pressed.
I am not looking for how to determine if a specific key is pressed (in that case, GetAsyncKeyState() would work).
Also, it needs to work in the background, so without the program window having focus (the program will be running in the background).
EDIT: The program will react on every keypress with a sound output. I want it to output a sound everytime I type something (like in Word and such). That's also why it needs to run in the background. I want it to be fast, so I can minimize the delay between keypress and sound output.
EDIT2: I am searching for something else than Windows Hooks. While it does work for getting key presses in the background, I am looking for something that is faster (least delay possible).
For example: GetAsyncKeyState() works for reacting on specific keypresses, while the program window doesn't have focus. I am looking for something like that, but with the ability to react on any key press, not a specific one.
As comment, you could use RegisterRawInputDevices as this sample.
Create a Message-Only Window.
Set RAWINPUTDEVICE.hwndTarget to the window create in step 1, so that you don't need to focus on the window.
Call GetRawInputData to get the input data.
Sample(removed the error checking):
#include <windows.h>
#include <iostream>
using namespace std;
LRESULT CALLBACK WindProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam)
{
if (Msg == WM_INPUT)
{
HRAWINPUT hRawInput = (HRAWINPUT)lParam;
RAWINPUT input = { 0 };
UINT size = sizeof(input);
GetRawInputData(hRawInput, RID_INPUT,&input,&size,sizeof(RAWINPUTHEADER));
printf("vkey: %x, flag: %d\n",input.data.keyboard.VKey, input.data.keyboard.Flags);
}
return DefWindowProc(hWnd, Msg, wParam, lParam);
}
int main()
{
WNDCLASSEX wcx = { 0 };
wcx.cbSize = sizeof(WNDCLASSEX);
wcx.lpfnWndProc = WindProc;
wcx.hInstance = GetModuleHandle(NULL);
wcx.lpszClassName = TEXT("RawInputClass");
RegisterClassEx(&wcx);
HWND hWnd = CreateWindowEx(0, TEXT("RawInputClass"), NULL, 0, 0, 0, 0, 0, HWND_MESSAGE, NULL, GetModuleHandle(NULL), NULL);
RAWINPUTDEVICE rid = { 0 };
rid.usUsagePage = 0x01;
rid.usUsage = 0x06; //keyboard
rid.dwFlags = RIDEV_INPUTSINK;
rid.hwndTarget = hWnd;
RegisterRawInputDevices(&rid, 1, sizeof(RAWINPUTDEVICE));
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
I created an application by slightly modifying the Magnification API sample provided by Microsoft to implement a custom transformation of each frame captured and displayed in the Magnifier window.
I used the MagSetImageScalingCallback to set the my function as a callback. The callback is invoked without problem and the source and destination image can be easily manipulated because the raw bits are passed to the callback as pointers (srcdata and destdata).
The window is refreshed with a timer which is set to 16 ms (~60Hz). The refresh is invoked by using the InvalidateRect API. The problem is that when the magnifier window suffer from flickering. This happens especially when the start menu appears, if "Peek" is enabled or every time there is a window on foreground which has dynamic content.
I tried to intercept the WM_ERASEBKGND and invoke the InvalidateRect with FALSE as third parameter but this didn't help. I tried to add an UpdateWindow invocation before the invalidate but nothing changed.
The Magnifier application provided with Windows 10 doesn't have the same problem. I'm wondering why this is happening and how can I get rid of flickering.
In order to reproduce che problem download the Magnification API sample from the link above then replace the content in the file MagnifierSample.cpp with this source code:
// Ensure that the following definition is in effect before winuser.h is included.
#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0501
#endif
#include <windows.h>
#include <wincodec.h>
#include <magnification.h>
// For simplicity, the sample uses a constant magnification factor.
#define RESTOREDWINDOWSTYLES WS_POPUP
// Global variables and strings.
HINSTANCE hInst;
const TCHAR WindowClassName[]= TEXT("MagnifierWindow");
const TCHAR WindowTitle[]= TEXT("Screen Magnifier Sample");
const UINT timerInterval = 16; // close to the refresh rate #60hz
HWND hwndMag;
HWND hwndHost;
RECT magWindowRect;
RECT hostWindowRect;
// Forward declarations.
ATOM RegisterHostWindowClass(HINSTANCE hInstance);
BOOL SetupMagnifier(HINSTANCE hinst);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
void CALLBACK UpdateMagWindow(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime);
BOOL isFullScreen = FALSE;
//
// FUNCTION: WinMain()
//
// PURPOSE: Entry point for the application.
//
int APIENTRY WinMain(_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE /*hPrevInstance*/,
_In_ LPSTR /*lpCmdLine*/,
_In_ int nCmdShow)
{
if (FALSE == MagInitialize())
{
return 0;
}
if (FALSE == SetupMagnifier(hInstance))
{
return 0;
}
ShowWindow(hwndHost, nCmdShow);
UpdateWindow(hwndHost);
// Create a timer to update the control.
UINT_PTR timerId = SetTimer(hwndHost, 0, timerInterval, UpdateMagWindow);
// Main message loop.
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
// Shut down.
KillTimer(NULL, timerId);
MagUninitialize();
return (int) msg.wParam;
}
//
// FUNCTION: HostWndProc()
//
// PURPOSE: Window procedure for the window that hosts the magnifier control.
//
LRESULT CALLBACK HostWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_ERASEBKGND:
return TRUE;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
//
// FUNCTION: RegisterHostWindowClass()
//
// PURPOSE: Registers the window class for the window that contains the magnification control.
//
ATOM RegisterHostWindowClass(HINSTANCE hInstance)
{
WNDCLASSEX wcex = {};
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = HostWndProc;
wcex.hInstance = hInstance;
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(1 + COLOR_BTNFACE);
wcex.lpszClassName = WindowClassName;
return RegisterClassEx(&wcex);
}
static BOOL CALLBACK myCallBack(HWND hwnd, void *srcdata, MAGIMAGEHEADER srcheader, void *destdata, MAGIMAGEHEADER destheader, RECT unclipped, RECT clipped, HRGN dirty) {
UINT row;
UINT column;
BYTE *matrix;
memset(destdata, 0, destheader.cbSize);
matrix = (BYTE *) destdata;
//Set alpha bits for the resulting image to 255 (fully opaque)
for (row = 0; row < destheader.height; row++) {
for (column = 3; column < destheader.width; column += 4) {
matrix[(row * destheader.width) + column] = 0xFF;
}
}
Sleep(20);
return TRUE;
}
//
// FUNCTION: SetupMagnifier
//
// PURPOSE: Creates the windows and initializes magnification.
//
BOOL SetupMagnifier(HINSTANCE hinst)
{
// Set bounds of host window according to screen size.
hostWindowRect.top = 0;
hostWindowRect.bottom = GetSystemMetrics(SM_CYSCREEN);
hostWindowRect.left = 0;
hostWindowRect.right = GetSystemMetrics(SM_CXSCREEN);
// Create the host window.
RegisterHostWindowClass(hinst);
hwndHost = CreateWindowEx(WS_EX_TOPMOST | WS_EX_LAYERED, WindowClassName, WindowTitle, RESTOREDWINDOWSTYLES, 0, 0, hostWindowRect.right, hostWindowRect.bottom, NULL, NULL, hInst, NULL);
if (!hwndHost)
{
return FALSE;
}
// Make the window opaque.
SetLayeredWindowAttributes(hwndHost, 0, 255, LWA_ALPHA);
SetWindowLong(hwndHost, GWL_EXSTYLE, GetWindowLong(hwndHost, GWL_EXSTYLE) | WS_EX_LAYERED | WS_EX_TRANSPARENT | WS_EX_TOPMOST | WS_EX_NOACTIVATE);
// Create a magnifier control that fills the client area.
magWindowRect = hostWindowRect;
hwndMag = CreateWindow(WC_MAGNIFIER, TEXT("MagnifierWindow"), WS_CHILD | WS_VISIBLE, magWindowRect.left, magWindowRect.top, magWindowRect.right, magWindowRect.bottom, hwndHost, NULL, hInst, NULL );
if (!hwndMag)
{
return FALSE;
}
MagSetImageScalingCallback(hwndMag, &myCallBack);
// Set the source rectangle for the magnifier control.
MagSetWindowSource(hwndMag, magWindowRect);
return 1;
}
//
// FUNCTION: UpdateMagWindow()
//
// PURPOSE: Sets the source rectangle and updates the window. Called by a timer.
//
void CALLBACK UpdateMagWindow(HWND /*hwnd*/, UINT /*uMsg*/, UINT_PTR /*idEvent*/, DWORD /*dwTime*/)
{
// Reclaim topmost status, to prevent unmagnified menus from remaining in view.
SetWindowPos(hwndHost, HWND_TOPMOST, 0, 0, magWindowRect.right, magWindowRect.bottom, SWP_SHOWWINDOW | SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOREDRAW);
// Force redraw.
InvalidateRect(hwndMag, NULL, FALSE);
}
Notice that I added the code to set the callback
BOOL ret = MagSetImageScalingCallback(hwndMag, &myCallBack);
then I created this callback:
static BOOL CALLBACK myCallBack(HWND hwnd, void *srcdata, MAGIMAGEHEADER srcheader, void *destdata, MAGIMAGEHEADER destheader, RECT unclipped, RECT clipped, HRGN dirty)
The Sleep statement "simulate" the time taken by my custom transformation.
If you open a youtube video with Edge and then you run the MagnifierSample.exe executable you should see a black screen flickering sometime and when the screen flickers you should see the content behind the magnifier window (the black screen). This is exactly what is happening in my application. The sleep value is set to 20 but I don't known how long the callback is actually taking. I just guessed that it might take more than 16 ms.
To reproduce the problem perform the following steps:
Run the MagnifierSample.exe manually (don't use the debugger to run it)
You should see a black screen
Press the window key
Set a window with a video or a dynamic content in foreground
Click on the black window to let the taskbar disappear
You should see the screen flashing sometime
Is it possible to set a callback similar to the MagSetImageScalingCallback for a normal window handle ? I know I can use the WindowProc callback to intercept the messages sent to the window however I don't have access to the rawbits unless I use CreateDIBSection, SelectObject etc ...but at that time the image has been already sent to the destination window and I don't have the chance to transform it.
I wrote this small program which displays a list view and makes items and subitems editable.
I want to change this to make only the subitems editable. And I would like to make the list view window procedure stand on itself, that I don't have to forward WM_NOTIFY messages every time as I'm doing now in WndProcMain. And the purpose is that I don't use only one list view with editable subitems in my program, I'm going to use it in many different windows.
The LVN_ENDLABELEDIT notification is processed by WndProcList because the bEditing has to be changed. This flag is used for WM_PAINT when subitems have to be edited. This is a fix, otherwise the text in the first subitem disappears because it thinks the first item is being edited. However, I would like to also receive a message like LVN_ENDLABELEDIT in the window procedure of the list view owner window (in this case WndProcMain), because I want to manipulate the user input also.
Please ask if you have questions.
Thanks in advance
Midas
WNDPROC wpOrigEditProc;
RECT rcSubItem;
LRESULT CALLBACK WndProcEditList(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
switch (uMsg) {
case WM_WINDOWPOSCHANGING:
{
WINDOWPOS *pos = (WINDOWPOS*) lParam;
pos->x = rcSubItem.left;
pos->cx = rcSubItem.right;
}
break;
default:
return CallWindowProc(wpOrigEditProc, hWnd, uMsg, wParam, lParam);
}
return 1;
}
LRESULT CALLBACK WndProcList(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
static HWND hEdit;
static RECT rc;
static LVITEM lvI;
static unsigned char bEditing = 0;
switch (uMsg) {
case WM_NOTIFY:
switch (((NMHDR*) lParam)->code) {
case NM_CLICK:
lvI.iItem = ((NMITEMACTIVATE*) lParam)->iItem;
lvI.iSubItem = ((NMITEMACTIVATE*) lParam)->iSubItem;
break;
case NM_DBLCLK:
SendMessage(hWnd, LVM_EDITLABEL, lvI.iItem, 0);
break;
case LVN_BEGINLABELEDIT:
{
char text[32];
bEditing = 1;
hEdit = (HWND) SendMessage(hWnd, LVM_GETEDITCONTROL, 0, 0);
rcSubItem.top = lvI.iSubItem;
rcSubItem.left = LVIR_LABEL;
SendMessage(hWnd, LVM_GETSUBITEMRECT, lvI.iItem, (long) &rcSubItem);
rcSubItem.right = SendMessage(hWnd, LVM_GETCOLUMNWIDTH, lvI.iSubItem, 0);
wpOrigEditProc = (WNDPROC) SetWindowLong(hEdit, GWL_WNDPROC, (long) WndProcEditList);
lvI.pszText = text;
lvI.cchTextMax = 32;
SendMessage(hWnd, LVM_GETITEMTEXT, lvI.iItem, (long) &lvI);
SetWindowText(hEdit, lvI.pszText);
}
break;
case LVN_ENDLABELEDIT:
bEditing = 0;
SetWindowLong(hEdit, GWL_WNDPROC, (long) wpOrigEditProc);
if (!lvI.iSubItem) return 1;
lvI.pszText = ((NMLVDISPINFO*) lParam)->item.pszText;
if (!lvI.pszText) return 1;
SendMessage(hWnd, LVM_SETITEMTEXT, lvI.iItem, (long) &lvI);
break;
default:
return CallWindowProc((WNDPROC) GetClassLong(hWnd, GCL_WNDPROC), hWnd, uMsg, wParam, lParam);
}
break;
case WM_PAINT:
if (bEditing) {
RECT rcItem;
if (lvI.iSubItem > 0) {
rcItem.left = LVIR_LABEL;
if (SendMessage(hWnd, LVM_GETITEMRECT, lvI.iItem, (long) &rcItem))
ValidateRect(hWnd, &rcItem);
}
}
default:
return CallWindowProc((WNDPROC) GetClassLong(hWnd, GCL_WNDPROC), hWnd, uMsg, wParam, lParam);
}
return 0;
}
LRESULT CALLBACK WndProcMain(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
static HWND hList;
static RECT rc;
switch (uMsg) {
case WM_NOTIFY:
switch (((NMHDR*) lParam)->code) {
case NM_CLICK:
case NM_DBLCLK:
case LVN_BEGINLABELEDIT:
case LVN_ENDLABELEDIT:
return CallWindowProc(WndProcList, ((NMHDR*) lParam)->hwndFrom, uMsg, wParam, lParam);
}
break;
case WM_CREATE:
{
LVCOLUMN lvc;
LVITEM lvI;
unsigned int i;
float vertex;
char text[32];
hList = CreateWindow(WC_LISTVIEW, 0, WS_CHILD | WS_VISIBLE | LVS_REPORT | LVS_EDITLABELS, rc.left, rc.top, rc.right, rc.bottom, hWnd, 0, hInstance, 0);
SendMessage(hList, LVM_SETEXTENDEDLISTVIEWSTYLE, 0, LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES);
SetWindowLong(hList, GWL_WNDPROC, (long) WndProcList);
lvc.mask = LVCF_WIDTH;
lvc.cx = 30;
SendMessage(hList, LVM_INSERTCOLUMN, 0, (LPARAM) &lvc);
lvc.mask = LVCF_TEXT;
lvc.pszText = "Vertex";
SendMessage(hList, LVM_INSERTCOLUMN, 1, (LPARAM) &lvc);
SendMessage(hList, LVM_SETCOLUMNWIDTH, 1, LVSCW_AUTOSIZE_USEHEADER);
lvI.mask = LVIF_TEXT;
lvI.pszText = text;
for (i = 0; i < 10; i++) {
vertex = (float) i;
lvI.iItem = i;
lvI.iSubItem = 0;
sprintf(text, "%d", i);
SendMessage(hList, LVM_INSERTITEM, 0, (LPARAM) &lvI);
lvI.iSubItem = 1;
sprintf(text, "%f, %f, %f", vertex - 1, vertex, vertex + 1);
SendMessage(hList, LVM_SETITEM, 0, (LPARAM) &lvI);
}
}
break;
case WM_SIZE:
GetClientRect(hWnd, &rc);
MoveWindow(hList, rc.left, rc.top, rc.right, rc.bottom, 1);
SendMessage(hList, LVM_SETCOLUMNWIDTH, 1, LVSCW_AUTOSIZE_USEHEADER);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
return 0;
}
I posted an answer related to the question but it was in C#. Never had much of expertise in winapi rather than playing with it as a hobbyist long ago, The answer at the end of this post looks promising -- http://cboard.cprogramming.com/windows-programming/122733-%5Bc%5D-editing-subitems-listview-win32-api.html
The first problem that cropped up for me in trying to compile your code is the fact that you either aren't compiling for Unicode, or you're using the non-Unicode sprintf function to format text. That's the first thing that needs to be fixed: Windows applications have been fully Unicode for over a decade. Replace every instance of char variable declarations with wchar_t (or TCHAR), prefix your string literals with L (or surround them with the TEXT() macro), and quickly replace the calls to sprintf with calls to wsprintf. As the documentation indicates, there are certainly better functions to use than wsprintf, but the same is true for sprintf, and this gets the code to compile with minimum effort.
The other thing that looks non-idiomatic to me is your use of the Get/SetClassLong and Get/SetWindowLong functions. Nowadays, I always write code with 64-bit portability in mind, and therefore I'd replace those with the Get/SetClassLongPtr and Get/SetWindowLongPtr macros, which automatically resolve to the correct function call, depending on if you're compiling for x86 or x64. This isn't a deal-breaker, though.
You first asked if there was a way to handle the WM_NOTIFY message directly from the sub-classed control by forwarding them automatically. Unfortunately, this is not possible. The Win32 model is such that parents always own their children, and it is thus their responsibility to handle events. I agree with your intuition on the separation of concerns, but the only way to make this happen is to explicitly forward the message from the parent to the appropriate child control yourself. Frameworks like MFC (which encapsulate the Win32 API) do this for you apparently automatically, still have to forward the notification messages from the parent to the child. They do it using something called "message reflection", which you can read about here. There's nothing stopping you from implementing something similar in your own application, but at a certain point you have to stop and ask yourself if it isn't worth using one of the many available GUI frameworks just for this sort of thing.
Anyway, as I understand it, the main thrust of your question is this:
I want to change this to make only the subitems editable.
That seems like a pretty simple fix. All you have to do is check in the LVN_BEGINLABELEDIT notification message handler that the user has, in fact, requested to edit a sub-item. Since you've used it elsewhere in your code, you know that the LVITEM.iSubItem member gives you either the one-based index of the sub-item, or 0 if the structure refers to an item rather than a sub-item.
So insert this line to ensure that lvI.iSubItem is not equal to 0 at the top of the LVN_BEGINLABELEDIT handler:
if (lvI.iSubItem == 0) return TRUE; // prevent editing
As the documentation for the LVN_BEGINLABELEDIT message indicates, returning FALSE allows the user to edit the label, and returning TRUE prevents them from editing. Since we return TRUE, we prevent the edit of anything but sub-items before the edit even starts.
It looks to me like you already tried to do something similar in the LVN_ENDLABELEDIT notification message handler with this line:
if (!lvI.iSubItem) return 1;
but that's too late! If the edit is already ending, then you've already given the user the impression that they were able to edit the main item, which you don't want to do. Take that line out, and it should work as expected.
Note that your implementation does have at least one glaring bug: you don't prevent the user from modifying the contents of a sub-item to a string longer than 32 characters, but your code populating the editing control only accepts a string up to 32 characters long:
TCHAR text[32];
// ... snip ...
lvI.pszText = text;
lvI.cchTextMax = 32;
SendMessage(hWnd, LVM_GETITEMTEXT, lvI.iItem, (long) &lvI);
SetWindowText(hEdit, lvI.pszText);
Writing that code the correct way would be (and I suspect that's why you haven't done it the right way) a giant pain in the ass. Typically, I will create a string buffer that I think is long enough, try to get the text of the sub-item, and check the return value of the LVM_GETITEMTEXT message. The return value tells me how many characters were copied into the string buffer. If the number of characters copied indicates that it entirely filled the available space in the string buffer, I'll make the buffer larger (perhaps doubling the size), and then try to send the LVM_GETITEMTEXT message again. As I recall, MFC does something similar. Told you it was a pain, but it's worth getting these things right.
A simpler solution (although more limiting) would be to prevent the user from ever setting the length of one of the sub-items to a string of text longer than 32 characters. Then you wouldn't have to worry about handling long input because you'd know it would never be there, and the user would never be confused about the behavior of your control. To do that, send the edit control an EM_LIMITTEXT message at the end of your LVN_BEGINLABELEDIT handler:
case LVN_BEGINLABELEDIT:
{
// ... snip ...
lvI.cchTextMax = 32;
SendMessage(hWnd, LVM_GETITEMTEXT, lvI.iItem, (long) &lvI);
SetWindowText(hEdit, lvI.pszText);
// (begin new code)
SendMessage(hEdit, EM_LIMITTEXT, lvI.cchTextMax, 0);
}
Now the user can't enter more than the allowed number of characters, so your code knows that you will never have to deal with any more than that being there (unless you write code to place them there yourself, in which case...).
All of that said, I think I agree with Hans:
Ugh, you'll fight glitches forever. There's little point with grid controls universally available.
I want to build a software test automation software and I'm playing around with Windows Hooks for that.
So I built the following C code. Can anyone tell me how to correct it ?
#include "windows.h"
// the call back function
LRESULT CALLBACK JournalRecordProc(int code, WPARAM wParam, LPARAM lParam)
{
HHOOK hhk = 0;
if (code > 0)
{
// save Data in File
}
if (code < 0)
{
// work done: now pass on to the next one that does hooking
CallNextHookEx(hhk, code, wParam, lParam);
}
/*
if (code == )
{
// ESC button pressed -> finished recording
UnhookWindowsHookEx(hhk);
}
*/
}
int main()
{
int iRet = 0;
HHOOK hHook = 0;
HINSTANCE hMod = 0;
HOOKPROC (*hHookProc)(int, WPARAM, LPARAM);
hHookProc = &JournalRecordProc;
// type of hook, callback function handle, hinstance [dll ?], 0 for systemwide
hHook = SetWindowsHookEx(WH_JOURNALRECORD, hHookProc, hMod, 0);
return iRet;
}
When I compile this I get the compiler errors:
error C2440: '=': 'LRESULT (__stdcall
*)(int,WPARAM,LPARAM)' kann nicht in 'HOOKPROC (__cdecl
*)(int,WPARAM,LPARAM)' konvertiert werden (could not be converted)
error C2440: 'Funktion': 'HOOKPROC (__cdecl *)(int,WPARAM,LPARAM)' kann nicht in 'HOOKPROC' konvertiert werden (could not be converted)
warning C4024: 'SetWindowsHookExA': Unterschiedliche Typen für formalen und übergebenen Parameter 2
There's no need to declare a separate hHookProc variable - just pass your procedure to SetWindowsHookEx directly:
hHook = SetWindowsHookEx(WH_JOURNALRECORD, JournalRecordProc, hMod, 0);
You're also going to need a valid module handle:
HINSTANCE hMod = GetModuleHandle(NULL);
Having made those edits, and made your JournalRecordProc return a value, it all now compiles and works for me (in that SetWindowsHookEx succeeds, anyway).