Windows Programming/Interfacing
The Keyboard
editWhen a key is pressed on the keyboard, the signal travels into the computer, where the kernel gets it. These signals, or "keycodes" as they are called, are raw data that needs to be translated into ASCII characters. The kernel performs this conversion, as well as obtaining other information about the keystroke. The necessary information is encoded into a message, and is sent to the currently active window. Simultaneously, the message is sent to the currently active caret.
Cursor Vs Caret
editInside Windows, there is a large terminology problem. So many different things all need to get their own names, so we can program with them, and keep everything straight. A perfect example of this is the difference between the cursor and the caret. The cursor is the graphical image that represents the mouse. It can either be an arrow for pointing, a hand, an hourglass, or an I-shaped text selector. The caret, on the other hand, is the blinking object that is used to enter text. When you type, the letter appears at the caret, and the caret moves forward by 1 space. It is important to keep these terms straight, because if you confuse them inside your program, you could have a lot of debugging to do.
Keypress Messages
editThere are a few keypress messages that the program can choose to handle. It is important to note that not all of these messages need to be handled in a program, and in fact it is usually a good idea to not handle many of them.
- WM_KEYDOWN
- The WM_KEYDOWN message indicates that a key has been pressed, or that it has been pressed and held down. If the key is held down, the keyboard goes into "Type-Matic" mode and generates keypress events repeatedly at a certain frequency. The kernel will generate a WM_KEYDOWN message for each of these, with the LPARAM message component containing the number of messages sent by the Kernel in succession, so that your program can choose to ignore some of the messages and not miss any of the information. In addition to the Key count, the LPARAM will contain the following information:
Bits of LPARAM Purpose 0-15 Key Count 16-23 Scan Code 29 Context Code 30 Previous State 31 Key Transition
- The scan code is the raw binary signal from the keyboard, which may not correspond to the ASCII value of the character. Notice that all buttons on a keyboard generate a scan code, including action buttons (Shift, ALT, CTRL). Unless you are trying to interact with the keyboard in a special way, you want to ignore the scan code. The Context Code determines if the ALT key is pressed at the same time. If the ALT key is pressed at the same time, the Context code is 1. The Previous State is the state of the button before the message was generated. The Key Transition determines whether the key is being pressed, or if it is being released. Most of the fields in the WM_KEYDOWN message can be safely ignored by most programs, unless you are trying to use Type-Matic functionality, or are trying to interface on a low level with the keyboard.
- WM_KEYUP
- This message is sent when a key that was being pressed has been released. Every key press will generate at least two messages: a Key down (when the button is pressed) and a Key Up (when the button is released). In general for most text-processing applications, the Key Up message can be ignored. The WPARAM is the value of the virtual character code, and the LPARAM is the same as for the WM_KEYDOWN message.
Accelerators
editWindows users will no doubt be familiar with some of the common key combinations that are used with large windows programs. CTRL+C copies an object to the clipboard. CTRL+P prints the current document. CTRL+S saves the current document. There are dozens more, and it seems like each program has its own specific key combinations.
These key combinations are known in the Windows world as "Accelerators". A program that uses accelerators will define an accelerator table. This table will contain all the different key combinations, and the command identifier that they each map to. When an accelerator is pressed on the keyboard, the program doesn't receive the keypress messages, it instead receives a WM_COMMAND message, with the command identifier in the WPARAM field.
To translate the accelerator keypress combinations into WM_COMMAND messages, the function TranslateAccelerator needs to be used in conjunction with the message loop, as such:
while(GetMessage(&msg, NULL, 0, 0)) { if(!TranslateAccelerator(&msg)) { TranslateMessage(&msg); DispatchMessage(&msg); } }
The TranslateAccelerator function will automatically dispatch the WM_COMMAND message to the appropriate window.
Using the Caret
editEach program may only have 1 active caret, and worse than that, the entire system may only have 1 active caret on the screen at a time. When using a caret, the programmer needs to take special care to destroy the caret when it is not in use, and to recreate the caret when needed. This can be accomplished relatively easily by creating the caret on the WM_SETFOCUS message (when the window is made active), and by destroying the caret on the WM_KILLFOCUS and WM_DESTROY messages.
The Mouse
editThe mouse has more messages associated, because the mouse is capable of more unique tasks than the keyboard is. For instance, the mouse has at least 2 buttons (frequently 3 or more), it often has a trackball, and it can be hovered over objects on the screen. Each of these functions of the mouse can be handled via messages, so we are going to need several messages
Mouse Messages
editThere are a number of mouse messages that may be handled by the program.
- WM_LBUTTONDBLCLK
- The user double-clicked the left mouse button.
- WM_LBUTTONDOWN
- The user pressed the left mouse button.
- WM_LBUTTONUP
- The user released the left mouse button.
- WM_MBUTTONDOWN
- The user pressed the middle mouse button.
- WM_MBUTTONUP
- The user released the middle mouse button.
- WM_MOUSEMOVE
- The user moved the mouse cursor within the client area of the window.
- WM_MOUSEWHEEL
- The user rotated or pressed the mouse wheel.
- WM_RBUTTONDOWN
- The user pressed the right mouse button.
- WM_RBUTTONUP
- The user released the right mouse button.
In addition, the LPARAM field will contain information about the cursor location, in X-Y coordinates, and the WPARAM field will contain information about the state of the shift and CTRL keys.
The system will handle the graphical mouse movements, so you don't need to worry that your program is going to lock up the mouse. However, the program is capable of changing the mouse cursor, and sending mouse messages to other windows, if needed.
The Timer
editTimers are used to space the flow of a program via pauses, so that a group of actions can be allowed to process before another group interacts with the result. In a strict sense, the Windows Timer is not a user input device, although the timer can send input messages to the window, so it is generally covered in the same manner as the mouse and the keyboard. Specifically, Charles Petzold's famous book, "Programming Windows", treated the timer as an input device.
A common use of a timer is to notify the program of the end of a pause so that it can erase an image previously painted on the screen, such as in screensavers that display various images from one folder. The native timer function, however, is not considered accurate for games or time-critical responses. The DirectX API is preferred for games.
Each time the specified time interval assigned to the Timer elapses, the system sends a WM_TIMER message to the window associated to the Timer.
The new Timer starts timing the interval as soon as it is created by the SetTimer function. When you create a Timer you retrieve a unique identifier that can be used by the KillTimer function to destroy the Timer. This identifier is also present in the first parameter of WM_TIMER message.
Let's see the functions syntax.
UINT_PTR SetTimer( HWND hWnd, //Handle of the window associated to the timer UINT nIDEvent, //an identifier for the timer UINT uElapse, //the time-out value, in ms TIMERPROC lpTimerFunc //the address of the time procedure (see below) ); BOOL KillTimer( HWND hWnd, // Handle of the window associated to the timer UINT_PTR uIDEvent // the identifier of the timer to destroy );
If you want to reset an existing timer you have to set the first argument to NULL, and the second to an existing timer ID.
You can process WM_TIMER message in two different ways:
- by processing the WM_TIMER message in the window procedure of the window passed as first argument.
- by defining a TimerProc callback function (fourth argument) that process the message instead of a window procedure.
Let's see the first one.
#define IDT_TIMER1 1001 #define IDT_TIMER2 1002 ... SetTimer(hwnd, //handle of the window associated to the timer IDT_TIMER1, //timer identifier 5000, // 5 seconds timeout (TIMERPROC)NULL); //no timer procedure, process WM_TIMER in the window procedure SetTimer(hwnd, IDT_TIMER2, 10000, (TIMERPROC)NULL); ... LRESULT CALLBACK WinProc (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam){ ... case WM_TIMER: switch(wParam) { case IDT_TIMER1: //process the 5 seconds timer break; case IDT_TIMER2: //process the 10 seconds timer break; } ... /* to destroy the timers */ KillTimer(hwnd, IDT_TIMER1); KillTimer(hwnd, IDT_TIMER2);
Now using a TimerProc.
VOID CALLBACK TimerProc( HWND hwnd, // handle of window for timer messages UINT uMsg, // WM_TIMER message UINT idEvent, // timer identifier DWORD dwTime // current system time );
It is called by the system to process an associated Timer's WM_TIMER message. Let's see some code.
#define IDT_TIMER1 1001 ... /* The Timer Procedure */ VOID CALLBACK TimerProc(HWND hwnd, UINT uMsg, UINT idEvent, DWORD dwTime) { MessageBox(NULL, "One second is passed, the timer procedure is called, killing the timer", "Timer Procedure", MB_OK); KillTimer(hwnd, idEvent); } ... /* Creating the timer */ SetTimer(hwnd, IDT_TIMER1, 1000, (TIMERPROC)TimerProc); ...
Timer Messages
editThe timer only comes with a single message, WM_TIMER, and the WPARAM field will contain the timer ID number.