In diesem Tutorial zeige ich dir, wie man den Nachrichten Timer von Windows initalisiert und benutzt. Der Timer ist zwar sehr ungenau, aber dazu später mehr. Dann zeige ich dir in diesem Tutorial etwas verschiedene Fenstertypen.
#define STRICT #include <windows.h> LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
Nun deklarieren wir drei Konstanten. Einmal ist das die Kennziffer des Timers. Was schon
andeutet, dass man mehrere Timer zur gleichen Zeit benutzen kann (da eine Kennziffer immer
zum Unterscheiden benutzt wird). Die Kennziffer muss vom Typ unsigned int
sein.
Die Konstanten SizeX
und SizeY
bestimmen die Größe des
Fensters. Man sollte solche Zahlen immer hinter Konstanten verstecken, damit man bei
Änderung der Zahl nicht den ganzen Code durchsuchen muss (außerdem ist
SizeX
wesentlich Aussagekräftiger als 60
).
const UINT TimerSec = 1; const UINT SizeX = 60; const UINT SizeY = 80; int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { MSG msg; HWND hWnd; WNDCLASS wc; const char szAppName[] = "Stopuhr"; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hInstance = hInstance; wc.lpfnWndProc = WndProc; wc.lpszClassName = szAppName; wc.lpszMenuName = NULL; wc.style = CS_HREDRAW | CS_VREDRAW; RegisterClass(&wc); hWnd = CreateWindow( szAppName, szAppName,
Der nächste Parameter beschreibt den Window Stil (zuvor:
WS_OVERLAPPEDWINDOW
). In diesem Tutorial benutze ich einen anderen Stil, da es
diesmal nicht sinnvoll ist die Größe des Fensters zu verändern zu dürfen.
Eigentlich benutzen wir eine Kombination aus verschiedenen Stilen. WS_OVERLAPPED
bewirkt, dass das Fenster ein Rahmen und eine Titelleiste besitzt. WS_SYSMENU
fügt das System Menü hinzu. So ist auch WS_OVERLAPPEDWINDOW
eine
Zusammenstellung aus mehreren Stilen. Es besteht aus den sechs Stilen
WS_OVERLAPPED
, WS_CAPTION
, WS_SYSMENU
,
WS_THICKFRAME
, WS_MINIMIZEBOX
und WS_MAXIMIZEBOX
.
WS_THICKFRAME
erweitert das Fenster um die Möglichkeit vom Benutzer in der
Größe verändert zu werden.
WS_OVERLAPPED | WS_SYSMENU,
In die folgenden vier Parameter haben wir immer CW_USEDEFAULT
eingesetzt. Dadurch
wurde das Fenster immer in 'default' Größe und Position angezeigt. Nun benutzen wir
für die Größe feste Werte.
CW_USEDEFAULT, CW_USEDEFAULT, SizeX, SizeY, NULL, NULL, hInstance, NULL); ShowWindow(hWnd, iCmdShow); UpdateWindow(hWnd); while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; } LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { static RECT rect;
Nun deklarieren wir drei statische Variablen. Die erste speichert, ob die Uhr läuft, oder nicht. Die nächsten beiden speichern die Zeit.
static BOOL isActive; static int iSec; static int iMin; switch (message) {
Da der Anwendungsbereich kleiner ist, als das angegebene Fenster (Rand, Titelleiste), müssen wir uns die Größe gesondert besorgen. Da diese Werte nicht verändert werden, wird diese Nachricht auch nur einmal bearbeitet werden.
case WM_SIZE: { rect.right = LOWORD(lParam); rect.bottom = HIWORD(lParam); return 0; }
Nun fangen wir die WM_KEYDOWN
Nachricht ab, um auf die Tasteneingabe des
Benutzers reagieren zu können. Unser Programm soll folgende Tastendrücke bearbeiten:
mit der Leertaste startet bzw. stopt man die Stopuhr. Mit der Backspace Taste soll man die Uhr
wieder auf 0:00 stellen können. Gleichzeitig soll die Uhr gestopt werden. Die Escapetaste
beendet das Programm.
case WM_KEYDOWN: { switch (wParam) {
VK_SPACE
steht für die Leertaste. Dann verneinen wir isActive
und prüfen, ob wir die Stoppuhr aktiviert haben.
case VK_SPACE: { isActive = !isActive; if (isActive) {
Die nächst Anweisung ist neu, mit ihr weisen wir Windows an uns in bestimmten
Abständen eine Nachricht vom Typ WM_TIMER
zu schicken. Windows bekommt von
einem Hardware Timer in bestimmten abständen einen Impuls. Für jeden Timer hat
Windows eine Variable, die sie 'runterzählt'. Ist Windows wieder bei Null angekommen,
wird eine Nachricht des Typs WM_TIMER
verschickt mit der Timer ID in
wParam
. Da die Nachricht aber so behandelt wird wie jede andere auch, muss sie
warten bis sie endlich an die Reihe kommt. Wenn das Programm also ein wenig 'hinterher hinkt'
kann die Nachricht schon mal ein wenig später ankommen. Der erste Parameter der Funktion
SetTimer
ist der Handle zu dem Fenster, welches die WM_TIMER
Nachricht bekommen soll. Der zweite ist die ID des Timers (siehe oben), der dritte Parameter
gibt die Zeit zwischen zwei Nachrichten in Milisekunden (von 0 bis 2 hoch 32) an.
Wenn man keine Nachrichten bekommen möchte, sonder möchte, dass immer eine
Funktion aufgerufen wird, muss man im vierten Parameter die Adresse der Funktion angeben.
SetTimer(hWnd, TimerSec, 1000, NULL); } else {
Und nun gleich das Gegenstück zu SetTimer
hinterher. Die
KillTimer
Funktion weist Windows an uns keine weiteren Nachrichten vom Typ
WM_TIMER
mit dieser ID zu schicken. Der erste Parameter ist wieder das Fenster,
an das der Timer die Nachrichten geschickt hat. Als zweiten Parameter wird die ID des Timers
verlangt.
KillTimer(hWnd, TimerSec); } return 0; }
Die Konstante VK_BACK
steht für die Backspacetaste. Wenn diese Taste
gedrückt wurde, dann wird dieser Zweig ausgeführt. Falls der Timer noch aktiv war,
wird er jetzt ausgeschaltet (KillTimer
). Dann werden die Werte für Minuten
und Sekunden wieder auf Null zurückgestellt. Das InvalidateRect
bewirkt,
dass die Daten auf dem Bildschirm sofort aktualisiert werden.
case VK_BACK: { if (isActive) { isActive = FALSE; KillTimer(hWnd, TimerSec); } iMin = iSec = 0; InvalidateRect(hWnd, NULL, TRUE); return 0; }
Wenn die Escapetaste gedrückt wurde (VK_ESCAPE
), dann wird das Programm
durch das Senden der WM_CLOSE
Nachricht beendet.
case VK_ESCAPE: { SendMessage(hWnd, WM_CLOSE, 0, 0); return 0; } } return 0; }
Die folgende Nachricht (WM_TIMER
) wird immer dann dem Fenster geschickt, wenn
wieder die in SetTimer
angegebene Zeit (in Millisekunden) abgelaufen ist. In
wParam
steht die ID des Timers, da wir aber nur einen Timer benutzten, brauchen
wir diesen Wert nicht abfragen. Wenn iSec
kleiner ist als 59 wird es um eins
erhöht. Ansonsten wird iMin
inkrementiert (um eins erhöht) und
iSec
auf Null gesetzt.
case WM_TIMER: { if (iSec < 59) ++iSec; else { iSec = 0; ++iMin; } InvalidateRect(hWnd, NULL, TRUE); return 0; } case WM_PAINT: { PAINTSTRUCT ps; HDC hDC; SIZE size; char sTime[6]; int iLength;
Die Funktion wsprintf
verhält sich genauso, wie die C Funktion
sprintf
(also wie printf
, nur das das Ergebnis in ein Array
geschrieben wird). Das 02i
in wsprintf
gibt an, dass die Integer
Zahl mit mindestens zwei Stellen ausgegeben werden soll und die 0 vor der 2 weist Windows an,
die fehlenden Stellen mit Nullen zu belegen.
iLength = wsprintf(sTime, "%i:%02i", iMin, iSec); hDC = BeginPaint(hWnd, &ps); { GetTextExtentPoint32(hDC, sTime, iLength, &size); TextOut(hDC, rect.right / 2 - size.cx / 2, rect.bottom / 2 - size.cy / 2, sTime, iLength); } EndPaint(hWnd, &ps); return 0; } case WM_DESTROY: { if (isActive) { KillTimer(hWnd, TimerSec); } PostQuitMessage(0); return 0; } } return DefWindowProc(hWnd, message, wParam, lParam); }
Ich weiss nicht, ob du es bemerkt hättest, aber wenn du die Zeit mal unter
Windows 98/95 gemessen hast, wirst du merken, dass die Stoppuhr auf einer Minute
ungefähr drei Sekunden falsch geht (unter Windows NT ist alles in Ordnung). Dies
liegt daran, dass Windows 9x noch Rücksicht auf alte DOS Programme nimmt und deshalb
den Hardware Timer immer noch auf 18.2 Ticks/Sekunde (ca. alle 54 ms) programmiert
hat. Von daher ist die Angabe von Millisekunden in der SetTimer
Funktion nur ein
theoretischer Wert. Denn ein Programm kann unter Windows 9x nur 18,2
WM_TIMER
Nachrichten pro Sekunde bekommen. Wenn man nun 1000 Millisekunden
angibt, dann rundet Windows 9x diesen Wert auch noch auf ein gerades vielfache des
realen Timerwertes, sodass die Genauigkeit immer mehr abnimmt. Da Windows NT den Timer
auf 100 Ticks/Sekunde programmiert hat, ist die Timer Nachricht dort wesentlich
zuverlässiger. Die WM_TIMER
Nachricht ist also als sehr exakter Tacktgeber
nicht zu gebrauchen.
Wenn du meinst, dass irgend eine Stelle in diesem Tutorial ungenau ist oder etwas nicht erklärt wird, dann schreibe mir einfach eine E-Mail.