500 likes | 744 Views
Windows Programming. GDI, Keyboard, Mouse, Hit testing. Overview. Windows distributes messages to programs A function (named in main program, e.g., wndproc) is called (and an instance is created - instantiated) to process events Event-driven control flow architecture
E N D
Windows Programming GDI, Keyboard, Mouse, Hit testing
Overview • Windows distributes messages to programs • A function (named in main program, e.g., wndproc) is called (and an instance is created - instantiated) to process events • Event-driven control flow architecture • … and “program driven” control flow • Typically, programs are structured so that updating occurs when window is redrawn upon receiving a WM_PAINT message • Petzold’s text uses c programs to illustrate • Which is good because it’s real … • However, requires fairly extensive experience with c to appreciate all nuances • First, one more time on the big picture …
Yet Again, … Windows Distributes Events to Programs’ Event Queues Program 1 Program 2 Program 3 Event queue 1 Event queue 2 Event queue 3 Windproc 1 Windproc 2 Windproc 3 User moves a mouse across the screen: • Events: • Mouse clicks, window creation, window resize, “needs to be redrawn”, … • Programmer handles events • or MS Windows does • “default win proc”
Interaction Handling: Event Driven • Program handles events, thus event-driven programming While (!quit) { // program waits for user action wait_on (user_action) // just sit there until event occurs switch (user_action) { case mouse_button_1_clicked // do whatever upon mouse click // other cases to handle input (events) case mouse_button_2_clicked . : } } • When > 1 user action occurs before completing processing of another event, put event in queue, the event, or message, queue • In fact message for just about everything, mouse move, resize, etc.
Interaction Handling: MS Windows Code • Each “case” represents an event, and what to do when it occurs WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam) { HDC hdc ; PAINTSTRUCT ps ; RECT rect ; switch (iMsg) { case WM_CREATE : // process WM_CREATE message return 0 ; case WM_PAINT : needs redraw – “invalid” // process WM_PAINT message because … return 0 ; case WM_DESTROY : // process WM_DESTROY message return 0 ; } return DefWindowProc (hwnd, iMsg, wParam, lParam) ; }
Msg Handling Routine Called Repeatedly • A programming detail …, static variables – wndproc on and off stack WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam) { HDC hdc ; PAINTSTRUCT ps ; RECT rect ; switch (iMsg) { case WM_CREATE : // process WM_CREATE message return 0 ; case WM_PAINT : // process WM_PAINT message return 0 ; case WM_DESTROY : // process WM_DESTROY message] return 0 ; } return DefWindowProc (hwnd, iMsg, wParam, lParam) ; }
Hello Windows – Beep Homework WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam) { HDC hdc ; PAINTSTRUCT ps ; RECT rect ; switch (iMsg) { case WM_CREATE : // PlaySound ("hellowin.wav", NULL, SND_FILENAME | SND_ASYNC) ; return 0 ; case WM_PAINT : hdc = BeginPaint (hwnd, &ps) ; GetClientRect (hwnd, &rect) ; DrawText (hdc, "Hello, Windows 98!", -1, &rect, DT_SINGLELINE | DT_CENTER | DT_VCENTER) ; EndPaint (hwnd, &ps) ; return 0 ; case WM_DESTROY : PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, iMsg, wParam, lParam) ; }
“Invalid Rectangles” and WM_PAINT • “Events”, or messages, indicate window size, etc. changes and when part of the window (client area) has been “destroyed” • Indicated by a WM_PAINT message • Window procedure receives a WM_PAINT message when: • Another window moved off window, resized, “built in” scrolling • Other times when Windows cannot recreate saved region (e.g., dialog box) • Program itself causes a WM_PAINT message to be “sent to itself” by calling the InvalidateRect or InvalidateRgn functions • Sometimes, Windows saves area of display and then restores it: • Mouse cursor or icon moved across client area • Valid and Invalid Rectangles • "invalid region" or "update region" • WM_PAINT is also a way for a program to “control itself”, • i.e., pass control to the WM_PAINT part of program
FYI - Alternating Rectangles: Program Driven Control Flow When mouse is clicked in window (client area), alternates drawing rectangle to left, then right of center WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam) { static int Bleft; // where to draw, could be global switch (iMsg) { case WM_SIZE: // …. And client area is still “invalid” // determine window client area size and use to find center for drawing cxClient = LOWORD (lParam) ; iXcenter = cxClient / 2; cyClient = HIWORD (lParam) ; iYcenter = cyClient / 2; return 0; case WM_LBUTTONUP: InvalidateRect (hwnd, NULL, TRUE) ; // marks window client area as “invalid” return 0 ; // resulting in WM_PAINT message sent to wind case WM_PAINT : hdc = BeginPaint (hwnd) ; // Begin/EndPaint “validates” rectangle --- so another WM_PAINT not generated if (bLeft) { Rectangle (hdc, iXcenter - 15, iYcenter - 15, iXcenter, iYcenter); BLeft=1; } // true else { Rectangle (hdc, iXcenter, iYcenter, iXcenter + 15, iYcenter + 15); BLeft=0; } // false EndPaint (hwnd, &ps) ; return 0 ; case WM_DESTROY : PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, iMsg, wParam, lParam) ; }
Review: Just Getting Text Out …. int iLength ; TCHAR szBuffer [40] ; case WM_CREATE: hdc = GetDC (hwnd) ; GetTextMetrics (hdc, &tm) ; cxChar = tm.tmAveCharWidth ; cyChar = tm.tmHeight + tm.tmExternalLeading ; ReleaseDC (hwnd, hdc) ; return 0 ; [ other program lines ] iLength = wsprintf ( szBuffer, TEXT ("The sum of %i and %i is %i"),iA, iB, iA + iB) ; TextOut (hdc, x, y, szBuffer, iLength) ; TextOut (hdc, x, y, szBuffer, wsprintf (szBuffer, TEXT ("The sum of %i and %i is %i"), iA, iB, iA + iB)) ; “It ain't pretty, but it works.” Petzold, 1999
Overview • GDI • Graphics Device Interface • Mouse • A pointing device • Decoding and using x, y location • Programmer control of EDA • “Hit testing”
Device Independent Prog. & Windows GDI • Device independent programming: 1. Hardware meaning: • Specify graphics (and text in Windows) in terms independent of hardware • Device drivers, etc., handle • In “early days”, actually loaded registers and wrote to memory map of display • In this class (and elsewhere), also “independent of window size, etc.” 2. Software meaning: • Things should display, input, etc. regardless of size of window, type of input device etc. • Graphics Device Interface (GDI) • Window specific term • Handles all screen (and printer) output
GDI: Graphics Device Interface • Yes, deals with graphics, but also window system and manager things • Types of (so called, GDI) function calls: • Get (or create) & release (or destroy) device context - which we’ve seen • Obtain information about the device context - which we will see • Draw something - which we’ve seen, e.g., Rectangle (hdc, … • Work with GDI objects, e.g., CreatePen (hdc) – which we will see • For “graphics” drawing four categories of calls: • Lines and curves (arcs) • Filled areas • Bitmaps (more later) • Text • There are no GDI 3D graphics – can use OpenGL • And for lots of other things • Mapping modes, metafiles, regions, paths, clipping, palettes, printing, … • Device contexts have various attributes • Which can be examined and set, e.g., (drawing) pen width and style
A Quick Look at GDI Attributes – Petzold …FYI, quick look, see Petzold for full program // same basic structure and programming techniques as sysmetric.c WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam) { static int cxChar, cxCaps, cyChar ; char szBuffer[10] ; HDC hdc ; int i ; PAINTSTRUCT ps ; TEXTMETRIC tm ; switch (iMsg) { case WM_CREATE: hdc = GetDC (hwnd) ; GetTextMetrics (hdc, &tm) ; …… ReleaseDC (hwnd, hdc) ; return 0 ; case WM_PAINT: // <- a brutal, devilish 2 lines of code // GetDeviceCaps (hdc,evcaps[i].iIndex) hdc = BeginPaint (hwnd, &ps) ; for (i = 0 ; i < NUMLINES ; i++) { TextOut (hdc, 0, cyChar * i, devcaps[i].szLabel, lstrlen (devcaps[i].szLabel)) ; TextOut (hdc, 14 * cxCaps, cyChar * i, devcaps[i].szDesc, lstrlen (devcaps[i].szDesc)) ; SetTextAlign (hdc, TA_RIGHT | TA_TOP) ; TextOut (hdc, 14*cxCaps+35*cxChar,... wsprintf (szBuffer, TEXT ("%5d"), GetDeviceCaps (hdc,evcaps[i].iIndex))) SetTextAlign (hdc, TA_LEFT | TA_TOP) ; } EndPaint (hwnd, &ps) ; return 0 ;
Device Capabilities: More Than You Ever … Values and the functions to change or obtain their values:
Drawing Dots and Lines, 1 • Finally, something easy … • Setting Pixels • SetPixel (hdc, x, y, crColor) ; • crColor = GetPixel (hdc, x, y) ; • Straight Lines • LineTo Draws a straight line. • Polylineand PolylineTo Draw a series of connected straight lines. • PolyPolyline Draws multiple polylines. • Arc Draws elliptical lines. • PolyBezierand PolyBezierTo Draw Bezier splines. • ArcTo and AngleArc Draw elliptical lines. • PolyDraw Draws a series of connected straight lines and Bezier splines.
Drawing Dots and Lines, 2 • Enclosed area • Rectangle Draws a rectangle. • Ellipse Draws an ellipse. • RoundRect Draws a rectangle with rounded corners. • Pie Draws a part of an ellipse that looks like a pie slice. • Chord Draws part of an ellipse formed by a chord. • Others • MoveToEx (hdc, xBeg, yBeg, NULL) ; • LineTo (hdc, xEnd, yEnd) ;
Petzold’s SINEWAVE Program Again, FYI, quick look, see Petzold for full program
// Main – nothing much here, except data structures #define NUM 1000 #define TWOPI (2 * 3.14159) int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { static TCHAR szAppName[] = TEXT ("SineWave") ; HWND hwnd ; MSG msg ; WNDCLASS wndclass ; wndclass.style = CS_HREDRAW | CS_VREDRAW ; wndclass.lpfnWndProc = WndProc ; wndclass.cbClsExtra = 0 ; wndclass.cbWndExtra = 0 ; wndclass.hInstance = hInstance ; wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ; wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ; wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ; wndclass.lpszMenuName = NULL ; wndclass.lpszClassName = szAppName ; if (!RegisterClass (&wndclass)) { MessageBox (NULL, TEXT ("Program requires Windows NT!"), szAppName, MB_ICONERROR) ; return 0 ; } hwnd = CreateWindow (szAppName, TEXT ("Sine Wave Using Polyline"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,NULL, NULL, hInstance, NULL); ShowWindow (hwnd, iCmdShow) ; UpdateWindow (hwnd) ; while (GetMessage (&msg, NULL, 0, 0)) …. Cont’d
// Main – nothing much here, except data structures #define NUM 1000 #define TWOPI (2 * 3.14159) // Illustrates using 1) using an array of points and 2) some line drawing commands (MoveTo, LineTo, Polyline) LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { static int cxClient, cyClient ; HDC hdc ; int i ; PAINTSTRUCT ps ; POINT apt [NUM] ; switch (message) { caseWM_SIZE: cxClient = LOWORD (lParam) ; cyClient = HIWORD (lParam) ; return 0 ; case WM_PAINT: hdc = BeginPaint (hwnd, &ps) ; MoveToEx (hdc, 0, cyClient / 2, NULL) ; LineTo (hdc, cxClient, cyClient / 2) ; for (i = 0 ; i < NUM ; i++) {apt[i].x = i * cxClient / NUM ; // “over” as f(client-size), up as below for sine curve apt[i].y = (int) (cyClient / 2 * (1 - sin (TWOPI * i / NUM))) ; } Polyline (hdc, apt, NUM) ; return 0 ; case WM_DESTROY: PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, message, wParam, lParam) ; } The program has an array of 1000 POINT structures. As the for loop is incremented from 0 through 999, the x fields of the POINT structure are set to incrementally increasing values from 0 to cxClient. The program sets the y fields of the POINT structure to sine curve values for one cycle and enlarged to fill the client area. The whole curve is drawn using a single Polyline call. Because the Polyline function is implemented at the device driver level, it is faster than calling LineTo 1000 times.
Using Pens, 1 • Stock Pens – which are used to draw things … below shows typical use // there are “stock” pens, which are solid - others can be dashed, etc. HPEN hPenWhite, hPenBlack; hPenWhite = GetStockObject (WHITE_PEN); // might do just once hPenBlack = GetStockObject (BLACK_PEN); // this is default : : hdc = GetDC (hwnd); SelectObject (hdc, hPenWhite); // now, all drawing in white // draw something ReleaseDC(hdc); // until release device context : : hdc = GetDC (hwnd); SelectObject (hdc, hPenBlack); // now, all drawing in black // draw something ReleaseDC(hdc); // until release device context NOTE: Can draw a figure black and then redraw it in white to erase
Using Pens, 2 The general form for creating pens: hPen = CreatePen (iPenStyle, iWidth, rgbColor) Where: iPenStyle = PS_SOLID, PS_DASH, PS_DOT, ... iWidth = 1, ... rgbColor: 0 = black or use the RGB macro: RGB (r, g, b) e.g., RGB (0, 0, 255) for a dark blue pen hPenSolidBlack = CreatePen (PS_SOLID, 1, RGB(0,0,0) ) hPenSolidWhite = CreatePen (PS_SOLID, 1, RGB(255, 255, 255) ) hPenDashedBlack = CreatePen (PS_DASH, 1, 0);
Other Basic GDI Things • Drawing Modes Will look at this later in detail for moving items • Drawing Filled Areas • Mapping Mode Which can be pretty esoteric, but useful • Rectangles, Regions, and Clipping Most of which is beyond scope of this course • Bits and Blts Which we’ll come back to (also ROPs - raster operations) • Metafiles • Lots more about text and fonts Maybe check out the display of text formatting examples
Keyboard, 1 • Basicly, pretty straightforward • Information about keystrokes passed to application in message queue (like everything else) • Keystroke information sent to window with the focus • Focus • Like, “active window” (Windows highlights title bar) • That window to which the interface device event will be sent • The window on top of all others • In Windows determined by mouse click in window, tabbing, …
Keyboard, 2 • Basic program logic for handling keystrokes • Programmer handling keystroke – not windows, so have to deal with display of letter, movement, etc. WndProc(HWND hwnd,UINT iMsg,WPARAM wParam,LPARAM lParam) { // the, now familiar, structure of handling messages switch (iMsg) { case WM_CREATE: : return 0; case WM_PAINT: : return 0; case WM_KEYDOWN: // like it says, iMsg = WM_KEYDOWN switch (wParam) // wParam holds value (#define’d) of key { case VK_RETURN: // here, if “return/enter” key // handle like anything break: } return0; } return DefWindowProc ( ...); }
SendMessage - If you really want/have to SendMessage (hwnd, WM_USER, “whatever”, “whatever”) - “backdoor control flow event/communication” The SendMessage function sends the specified message to a window or windows. The function calls the window procedure for the specified window and does not return until the window procedure has processed the message. The PostMessage function, in contrast, posts a message to a thread's message queue and returns immediately. LRESULT SendMessage( HWNDhWnd, // handle of destination window UINTMsg, // message to send WPARAMwParam, // first message parameter LPARAMlParam // second message parameter ); Parameters hWnd Identifies the window whose window procedure will receive the message. If this parameter is HWND_BROADCAST, the message is sent to all top-level windows in the system, including disabled or invisible un-owned windows, overlapped windows, and pop-up windows; but the message is not sent to child windows. Msg Specifies the message to be sent. wParam Specifies additional message-specific information. LParam Specifies additional message-specific information. Return Value The return value specifies the result of the message processing and depends on the message sent.
Mouse • A pointing device • Other pointing devices handled similarly • Program gets information about: 0. (actually, gets it both when mouse is in window’s client area & when it’s not) - usually don’t care about mouse when not in window, so just ignore 1. Location: x and y locations - in fact not exactly every single pixel level move rather, depends on system parameters (how fast sample) - again, even when window not have focus, typically not a problem 2. Movements (up & down) of left, and possibly right and middle buttons - and double clicks 3. And a few other things
Mouse: Windows Details Program gets information about: 0. Information both when mouse not in window’s client area: e.g., WM_NCLBUTTONDOWN • Location, x and y locations: - Message (i.e., iMsg by our conventions) = WM_MOUSEMOVE xPos = LOWORD(lParam); // horizontal position of cursor yPos = HIWORD(lParam); // vertical position of cursor - Information about state (down) of buttons: fwKeys = wParam; // key flags MK_CONTROL, MK_LBUTTON, MK_MBUTTON, MK_RBUTTON, MK_SHIFT if (wParam) = MK_CONTROL | MK_SHIFT // (or together) 2. Movements (up & down) of left, and possibly right & middle buttons: Messages: WM_LBUTTONDOWN, WM_LBUTTONUP WM_RBUTTONDOWN, WM_RBUTTONUP WM_MBUTTONDOWN, WM_MBUTTONUP - & double clicks (when windclass.style set: ... | CS_DBLCLKS) Messages: WM_LBUTTONDBLCLK, WM_RBUTTONDBLCLK, WM_MBUTTONDBLCLK
Example Program – Write mouse-loc, 1 Write the location of all “mouse move” messages and left button down events to screen
Example Program – Write mouse-loc, 2 WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam) { char szBuffer[50]; HDC hdc ; int x, y ; switch(iMsg) { case WM_MOUSEMOVE : x = LOWORD (lParam); y = HIWORD (lParam); // “decode” lParam to get hdc = GetDC (hWnd); // x and y client area coordinates nLength = sprintf (szBuffer, "x = %d, y=%d", x, y); TextOut (hdc, 20, 20, szBuffer, nLength); // write x and y locations ReleaseDC (hWnd, hdc); // to upper left corner of screen return 0 ; caseWM_LBUTTONDOWN : x = LOWORD (lParam); y = HIWORD (lParam); hdc = GetDC (hWnd); nLength = sprintf (szBuffer, "x = %d, y=%d", x, y); TextOut (hdc, 20, 200, szBuffer, nLength); // write x and y locations ReleaseDC (hWnd, hdc); // to upper middle-ish of screen return 0 ; caseWM_DESTROY : PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, iMsg, wParam, lParam) ; }
Review: Event-Driven Architecture: You Handle or Let Windows Handle The basic idea (last time or two): 1. Events associated with a window (from Create to Size to Mouseclick) 2. Are distributed by Windows to the “window procedure” associated with a window 3. Events arrive in a “window procedure’s” event queue, and are either 4. Handled a .explicitly by the “window procedure”, or are b. by Windows
Event-Driven Architecture: Invalid Regions or Rectangle (infinite loops?) When a region or rectangle becomes “invalid”, e.g., after moving a window off, Windows notifies wndproc via WM_PAINT message Ifwndproc is handling WM_PAINT messages (and it is), thenwndproc must “validate” the region or rectangle else Windows will continue to generate WM_PAINT messages which can result in the equivalent of an “event-controlled infinite loop”! This is an important, and not necessarily intuitive idea!
Event-Driven Details of Invalid Regions/Rectangles WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam) { HDC hdc ; PAINTSTRUCT ps ; RECT rect ; switch (iMsg) { case WM_CREATE : PlaySound ("hellowin.wav", NULL, SND_FILENAME | SND_ASYNC) ; return 0 ; case WM_PAINT : // whenever needs to be redrawn, as decided by Windows (e.g., when resized, window moved off, created, ...) // (because there is nothing in this program which causes any rect/region to be “invalid”) hdc = BeginPaint (hwnd, &ps) ; // Begin-End|Paint pair validates rect/reg GetClientRect (hwnd, &rect) ; DrawText (hdc, "Hello, Windows 95!", -1, &rect,DT_SINGLELINE | DT_CENTER | DT_VCENTER) ; EndPaint (hwnd, &ps) ; return 0 ; case WM_DESTROY : PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, iMsg, wParam, lParam) ; }
EDA: Handle Button Clicks, Overview The 2 separate events (left button down and left button up) are distributed to the message queue of wndproc wndproc can then do “whatever” when those events occur
EDA: Program Contol Flow by Generating WM_PAINT Message // Important! // Program causes message to be sent to itself // When receives “self-caused” message, then draw (paint) WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam) { HDC hdc ; PAINTSTRUCT ps ; switch (iMsg) { case WM_LBUTTONDOWN // set static “state control” variables, draw InvalidateRect (hwnd, NULL, TRUE) ;// marks win client area as “invalid” return 0 ; // results in WM_PAINT msg sent to wind case WM_PAINT : hdc = BeginPaint (hwnd) ; // Begin/EndPaint “validates” rectangle : : : : EndPaint (hwnd, &ps) ; return 0 ; } return DefWindowProc (hwnd, iMsg, wParam, lParam) ; }
EDA: Handle 2 Button Clicks, down-left, up-right When left mouse button is pressed down in window, figure is drawn to left of center When left mouse button is released(up) in window, figure is drawn to right of center Uses “state” variable to keep track of where figure has been drawn WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam) { HDC hdc ; PAINTSTRUCT ps ; static int cxClient, cyClient, iXcenter, iYcenter; static bool bDrawLeft=False, bDrawRight=FALSE; // must be static as wndproc on/off stack switch (iMsg) { case WM_SIZE: // determine window client area size and use to find center for drawing : case WM_LBUTTONDOWN // set static “state control” variables, and cause draw InvalidateRect (hwnd, NULL, TRUE) ; // marks window client area as “invalid” bDrawLeft = TRUE; // use static var to communicate within program bDrawRight = FALSE; return 0 ; // resultins in WM_PAINT message sent to wind case WM_LBUTTONUP: // set static “state control” variables, and cause draw InvalidateRect (hwnd, NULL, TRUE) ; // marks window client area as “invalid” bDrawRight = TRUE; // use static var to communicate within program bDrawLeft = FALSE; return 0 ; // resultins in WM_PAINT message sent to wind
EDA: Handle 2 Button Clicks, down-left, up-right case WM_LBUTTONDOWN // set static “state control” variables, and cause draw InvalidateRect (hwnd, NULL, TRUE) ; // marks window client area as “invalid” bDrawLeft = TRUE; bDrawRight = FALSE; : case WM_LBUTTONUP: InvalidateRect (hwnd, NULL, TRUE) ; // marks window client area as “invalid” bDrawRight = TRUE; bDrawLeft = FALSE; : case WM_PAINT : hdc = BeginPaint (hwnd) ; // Begin/EndPaint “validates” rectangle if (bDrawLeft) // (at create, don’t want to draw anything!) Rectangle (hdc, iXcenter - 15, iYcenter - 15, iXcenter, iYcenter); if (bDrawRight) Rectangle (hdc, iXcenter, iYcenter, iXcenter + 15, iYcenter + 15); EndPaint (hwnd, &ps) ; return 0 ; case WM_DESTROY : PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, iMsg, wParam, lParam) ; }
A Tricky Example Program, Draw using ary of Points, 1 FYI, quick look, see Petzold for full program • When there is a Mouse_move message and the Left_button is down, store that position (in an array of points) and make a dot at the position • When the user lets the Left_button_up, generate a paint message for the window with InvalidateRect • When the WM_PAINT message arrives, for each point in the array of pts draw a line from each point to every other point (a complete graph)
A Tricky Example Program, Draw using ary of Points, 2 FYI, quick look, see Petzold for full program WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam) { static POINT points[MAXPOINTS] ; static int iCount ; HDC hdc ; PAINTSTRUCT ps ; int i, j ; switch (iMsg) { case WM_LBUTTONDOWN : // erase the scr & setup iCount = 0 ; InvalidateRect (hwnd, NULL, TRUE) ; return 0 ; case WM_MOUSEMOVE : // get location of mouse and store in points[] if (wParam & MK_LBUTTON && iCount < 1000) // check mouse down { points[iCount ].x = LOWORD (lParam) ; // get x and y of mouse points[iCount++].y = HIWORD (lParam) ; hdc = GetDC (hwnd) ; SetPixel (hdc, LOWORD (lParam), HIWORD (lParam), 0L) ; // turn pixel on at point ReleaseDC (hwnd, hdc) ; } return 0 ; case WM_LBUTTONUP : // when mouse button released, invalidate rect, & erase screen InvalidateRect (hwnd, NULL, FALSE) ; return 0 ; ( cont’d)
A Tricky Example Program, Draw using ary of Points, 3 FYI, quick look, see Petzold for full program case WM_PAINT : // when invalidated rect (WM_PAINT sent / in queue) draw lines defined by points[] hdc = BeginPaint (hwnd, &ps) ; SetCursor (LoadCursor (NULL, IDC_WAIT)) ; ShowCursor (TRUE) ; for (i = 0 ; i < iCount - 1 ; i++) // for each point for (j = i + 1 ; j < iCount ; j++) // x and y { MoveTo (hdc, points[i].x, points[i].y) ; // draw line starting at LineTo (hdc, points[j].x, points[j].y) ; // and finishing at } ShowCursor (FALSE) ; SetCursor (LoadCursor (NULL, IDC_ARROW)) ; EndPaint (hwnd, &ps) ; return 0 ; // to be complete: case WM_DESTROY : PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, iMsg, wParam, lParam) ; }
Hit Testing Hit testing - “Determining if an event occurs in a region” Petzold uses quite generally, Also more conventionally in “checker” program (which is complicated)
Hit Testing Example Is there a left button click in Region 1? case WM_LBUTTONDOWN: x = LOWORD (lParam); // “decode” lParam to get y = HIWORD (lParam); // x and y client area coordinates if ( ( x > 10 && x < 50 ) && // ck if point of click is in region ( y > 10 && y < 30 ) ) { // there is a hit }
Hit Testing, Some Windows Structures typedef struct _RECT { LONG left; LONG top; LONG right; LONG bottom; } RECT; typedef struct tagPOINT { // pt LONG x; LONG y; } POINT; */ RECT rectObj; POINT pClick; rectObj.left = 10; rectObj.right = 50; rectObj.top = 20; rectObj.bottom = 30; case WM_LBUTTONDOWN: pClick.x = LOWORD (lParam); // “decode” lParam to get pClick.y = HIWORD (lParam); // x and y client area coordinates if ( (pClick.x > rectObj.left && pClick.x < rectObj.right ) && // check (pClick.y > rectObj.top && pClick.y < rectObj.bottom ) ) { // there is a hit }
Hit Testing, A Windows Macro and Function RECT rectObj; POINTS pClick; // different type (but ok) rectObj.left = 10; rectObj.right = 50; rectObj.top = 20; rectObj.bottom = 30; case WM_LBUTTONDOWN: pClick = MAKEPOINTS (lParam); // use macro to “decode” lParam to get if ( PtInRect (&rectObj, pClick) ) // use Windows fn (or make your own) { // there is a hit }
Hit Testing, Multiple Objects RECT rectObjs[10]; // now, an array of rectangles (rectangular regions) POINTS pClick; rectObjs[0].left = 10; rectObjs[0].right = 50; rectObjs[0].top = 20; rectObjs[0].bottom = 30; rectObjs[1].left = 10; rectObjs[1].right = 50; rectObjs[1].top = 30; rectObjs[1].bottom = 50; // other assignments nObjects = 3; case WM_LBUTTONDOWN: pClick = MAKEPOINTS (lParam); // use macro to “decode” lParam to get for (i=0; i<nObjects; i++) if (PtInRect (&rectObj[i], pClick)) { // there is a hit iObjectHit = i; break; }
Moving a Figure Across Screen To move a figure across the screen Recall, “it ain’t pretty, but it works” To have a figure appear that it is moving: Draw figure at x, y Erase figure at x,y Draw figure at x1, y1 Erase figure at x1,y1 Draw figure at x2, y2 Erase figure at x2,y2 . : for (i=1; i<end; i++) { Draw figure at x,y [i] // draw in some color Erase figure at x,y[i] // draw in white (or background color) } Draw figure at x,y [end]
Dragging Figure Across Screen To “drag” a figure: When user has pressed mouse button down in figure/object (M_LBUTTONDOWN - hit test) When user moves mouse (WM_MOUSEMOVE) 1. Erase figure at old location 2. Draw figure at new location (until button is released) In practice, to keep things transparent/simple, often use a state variable, e.g., bDrag to indicate that user has pressed button in figure Windows note: In fact Windows does provide messages for that work more concisely, e.g., might use MK_LBUTTON (instead of a state variable, bDragging), but there are subtleties about moving out of window, and not unusual to do with a “dragging” state
Dragging Example(using a state variable, bDragging) static int old_x, old_y, bDragging, … case WM_CREATE: bDragging = FALSE; : case WM_LBUTTONDOWN // check if button press on region, i.e., a hit if (bClickOnFigure) { bDragging = TRUE; pDragStartX = LOWORD(lParam); pDragStartY = HIWORD(lParam); // might change cursor to indicate dragging state } : case WM_LBUTTONUP: bDragging = FALSE // might change cursor back to show selection or other state : case WM_MOUSEMOVE: // there is a new mouse pointer position, so save old x and y // set new mouse x and y if (bDragging) } // erase old figure at old x and old y // - use white/background-color pen // (re)draw figure at new x and new y // - use black pen, with dashed line } :
End • Should have everything needed for homework assignment • Keep it simple …