1<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> 2<html> 3 4<head> 5<title>Windows Programming in Poly/ML</title> 6</head> 7 8<body> 9 10<h1 align="center">Windows Programming in Poly/ML</h1> 11 12<h2 align="center">David C.J. Matthews</h2> 13 14<h5 align="center">David C.J. Matthews 2001. Updated 2009.</h5> 15 16<p>This is a brief introduction to programming with the Windows™ interface in 17Poly/ML. It is arranged as a tutorial around the <a href="mlEdit.html">mlEdit</a> 18example, a small example text editor. It is not intended as a full description of 19writing programs for Windows and is no substitute for a more general guide. If you 20are planning to develop an application using this interface it is probably worth 21purchasing a book or CD_ROM reference for Windows such as the Microsoft Developer Network 22library.</p> 23 24<p>This introduction assumes some familiarity with programming in ML and perhaps some 25experience of other windowing systems, such as X-Windows. It does not assume any 26experience of programming with Windows and, for experienced Windows programmers, it will 27cover familiar ground. It does, though, point out some of the differences between 28the ML interface and that of other languages. The interface functions in ML are very 29similar to the underlying C functions. The main differences are in the types of the 30arguments and a few other changes necessary for ML. For more information see 31the <a href="../Winref/Reference.html">Windows Interface Reference.</a></p> 32 33<h3>Acknowledgements</h3> 34 35<p>The original version of the Windows interface was written at AHL by Panos Aguieris. 36 It uses the Poly/ML <a href="../Reference/Foreign.html">Foreign structure</a> 37 to provide nearly all the interface. It was extensively modified and expanded 38 by David Matthews.</p> 39 40<h3>Index</h3> 41 42<p><a href="#Windows">1. Windows, Messages and Window Procedures</a><br> 43<a href="#Child">2. Creating the child window</a><br> 44<a href="#Parent">3. Creating the parent window</a><br> 45<a href="#Menus">4. Menus</a><br> 46<a href="#Sending">5. Sending Messages</a><br> 47<a href="#Dialogues">6. Dialogues</a><br> 48<a href="#Common">7. Common Dialogues</a><br> 49<a href="#Printing">8. Printing and Painting</a></p> 50 51<h3><a name="Windows">1. Windows, Messages and Window Procedures</a></h3> 52 53<p>When thinking about a window we tend to think of a top-level application or a document 54window. From the point of view of programming, though, this is just one variety. 55 In fact each label, menu and text box is a window but because they form part of a 56bigger application they tend to be overlooked. These are <em>child windows</em>. 57 Nearly every window has to deal with two main areas of concern. There has to 58be a way of displaying the contents of the window and there has to be a way of handling 59user input, whether by the mouse or from the keyboard.</p> 60 61<p>Most communication with windows is by sending <em>messages</em>. A message may be 62sent as a result of user input, such as moving the mouse or typing a character at the 63keyboard. It may also be sent by the application to change the appearance of the 64window or to get information from it. There are over two hundred different message 65types defined by Windows and it is possible to define your own for use within an 66application. Many of these are only relevant in special circumstances and only a few 67are used regularly. It's worth noting in passing that messages can be sent from one 68process to a window in another process and that <em>hidden windows</em>, windows which 69don't appear on the screen but are only ever used to receive messages, are one of the 70standard ways of communicating between processes in Windows.</p> 71 72<p>The appearance of a window is largely governed by how it responds to different kinds of 73messages. Every window has a <em>window procedure</em> which processes messages sent 74to the window. When you create a window you give the <em>class</em> of the window 75you want to create and the new window uses the window procedure for that class. You 76can either use a standard class, such as <a href="../Winref/Class.html#Edit">Edit</a>, or you 77can create your own class using your own window procedure using <a href="../Winref/Class.html#RegisterClassEx">RegisterClassEx</a>. You will nearly always 78need to create at least one class for your top-level window. </p> 79 80<p>To see how this works in practice let's look at an extract from the mlEdit example. 81 </p> 82 83<pre>fun wndProc(hw: HWND, msg: Message, state as SOME{edit, fileName, ...}) = 84 case msg of 85 | WM_CLOSE => 86 (if checkForSave(hw, edit, fileName) then DefWindowProc(hw, msg) else LRESINT 0, state) 87 ..... 88 | _ => (DefWindowProc(hw, msg), state) 89 ..... 90and fun checkForSave ....</pre> 91<p>This extract from the window procedure, wndProc, processes the <a href="../Winref/Message.html#WM_CLOSE">WM_CLOSE</a> 92 message. This message has no parameters and is generated by the system 93 when the user clicks in the close box of the window (the box at the top left 94 of the window with a cross in it). Every message is sent to the window 95 procedure. When writing a window procedure we are often only interested 96 in handling a few of the possibilities so Windows performs a default action 97 if we don't want to handle the message. Even if we do handle it we can 98 pass it on for default processing when we have dealt with it. The default 99 action for WM_CLOSE is to destroy the window by a call to <a href="../Winref/Window.html#DestroyWindow">DestroyWindow</a>. 100 In our application we want to ask the user whether to save the window if the 101 text being edited has been modified. This process also gives the user 102 the opportunity for second thoughts and they may cancel the closure. This 103 is handled by our function checkForSave which returns true if it is safe to 104 close the window. Window procedures in ML return a pair as their result. 105 The first field of the pair is the Windows result value. For many messages 106 a result of LRESINT 0 is suitable. In this example we return SOME(LRESINT 107 0) if we don't want the window to be destroyed. </p> 108 109<p>The full type of a window procedure in ML is<br> 110 <tt>HWND * Message * 'a -> LRESULT * 'a</tt><br> 111The third argument is the <em>state</em> of the window and an updated version of the state 112is returned as part of the result. This allows the window procedure to process a 113message and update the state as part of the process. It is an ML extension which is 114not part of the underlying message system. In our extract we return the previous 115state unchanged.</p> 116 117<p>The mlEdit example works by constructing a parent window using a custom class and our 118own window procedure and then making a child window within it using the system Edit class. 119 The Edit window deals with all the keyboard input so saving us the need to write 120this ourselves. Our window procedure has to process messages to do with menu 121selection and other messages sent to top-level windows. </p> 122 123<pre> WM_SETFOCUS _ => 124 ( 125 SetFocus(SOME edit); 126 (DefWindowProc(hw, msg), state) 127 ) 128 129| WM_SIZE{height, width, ...} => 130 ( 131 MoveWindow{hWnd=edit, x=0, y=0, height=height, width=width, repaint=true}; 132 (DefWindowProc(hw, msg), state) 133 ) 134</pre> 135<p>The <a href="../Winref/Message.html#WM_SETFOCUS">WM_SETFOCUS</a> message is sent when the 136user clicks on a window and is intended to set the keyboard focus to that window (i.e. 137selects the window to receive keyboard input). The edit child window occupies the 138centre of our window so the WM_FOCUS message will be sent directly to it if the user 139clicks within that area. Since we are not interested in receiving characters in our 140top-level window we set the focus to the edit window if the user clicks on the border. 141 While it isn't essential to process this message at all for the mlEdit application 142to work correctly, by doing so we improve its usability.</p> 143 144<p>The <a href="../Winref/Message.html#WM_SIZE">WM_SIZE</a> message is sent if the window is 145resized. Among the parameters to the window are the height and width of the <em>client 146area</em>, the area excluding the border and the menu bar. This is the area we want 147to use for our edit window so we use <a href="../Winref/Window.html#MoveWindow">MoveWindow</a> 148to set its size. Calling MoveWindow causes the edit window to receive a WM_SIZE 149message. The window procedure for the edit window uses this to adjust the format of 150the text within the window to suit the new size. We do not need to be concerned 151about how this happens in our application. WM_SIZE is also sent when a window is 152created. This is convenient and means we do not need to set the size of the edit 153window when we create it.</p> 154 155<h3><a name="Child">2. Creating the child window</a></h3> 156 157<p>So far we have not described how the edit window is created. We need it to be a 158child window of the top-level window and the easiest way to do that is to create it when 159the <a href="../Winref/Message.html#WM_CREATE">WM_CREATE</a> message is received. This 160message is sent as part of the process of creation of the top-level window.</p> 161 162<pre>fun wndProc(hw: HWND, msg: Message, NONE) = 163 ( 164 case msg of 165 WM_CREATE _ => (* Create an edit window and return it as the state. *) 166 let 167 val edit = 168 CreateWindow{class = Class.Edit, name = "", 169 style = Edit.Style.flags[Edit.Style.WS_CHILD, Edit.Style.WS_VISIBLE, Edit.Style.WS_VSCROLL, 170 Edit.Style.ES_LEFT, Edit.Style.ES_MULTILINE, Edit.Style.ES_AUTOVSCROLL], 171 x = 0, y = 0, height = 0, width = 0, relation = ChildWindow{parent=hw, id=99}, 172 instance = Globals.ApplicationInstance(), init = ()}</pre> 173<pre> (* We also set the font for the edit window here. This has been omitted. *) 174 in 175 (LRESINT 0, SOME{edit=edit, devMode=NONE, devNames = NONE, fileName=""}) 176 end 177 178 | _ => (DefWindowProc(hw, msg), NONE) 179 )</pre> 180 181<p>This extract of the window procedure shows the creation of the edit window as part of 182processing WM_CREATE. We use <a href="../Winref/Window.html#CreateWindow">CreateWindow</a> 183which makes a window of a specified class and returns a handle to it. Once we have 184processed the message we set the state to SOME of a record containing a handle to the edit 185window. (Instead of doing this we could use <a href="../WinRef/Dialog.html#GetDlgItem">GetDlgItem</a> 186to find it each time we needed it, but this is easier). At this stage we just pass 187zeros for the size of the window since we will use the WM_SIZE message to set its size. 188 The id value for the child window (we use 99) is irrelevant in this example since 189we never use it. The style does not include horizontal scrolling so the edit window 190uses word-wrapping. </p> 191 192<h3><a name="Parent">3. Creating the parent window</a></h3> 193 194<p>It's now time to see how we create the main window itself. Since we want to use 195our own window procedure we need to register a class even though we will only create a 196single window of this class.</p> 197 198<pre>val polyIcon = ... 199val menu = ... 200val className = "mlEditWindowClass" 201val app = Globals.ApplicationInstance() 202(* Register a class for the top-level window. Use the Poly icon from the application. *) 203val myWindowClass = RegisterClassEx{style = Class.Style.flags[], wndProc = wndProc, hInstance = app, 204 hIcon = SOME polyIcon, hCursor = NONE, hbrBackGround = NONE, menuName = NONE, 205 className = className, hIconSm = NONE}; 206 207 208val w = CreateWindow{class = myWindowClass, name = "mlEdit", style = Window.Style.WS_OVERLAPPEDWINDOW, 209 x = CW_USEDEFAULT, y = CW_USEDEFAULT, height = CW_USEDEFAULT, width = CW_USEDEFAULT, 210 relation = PopupWindow menu, instance = app, init = NONE}; 211in 212ShowWindow(w, SW_SHOW); 213SetForegroundWindow w; 214 215RunApplication(); 216UnregisterClass(className, app) 217end;</pre> 218 219<p>In the mlEdit example we use the icon from the Poly/ML application. It isn't 220necessary to provide an icon: Windows will provide a default one if NONE is given for 221hIcon. We use the WS_OVERLAPPEDWINDOW style for the window which is the standard for 222a top-level window and gives a standard border with a system menu and minimise, maximise 223and close boxes. CW_USEDEFAULT is used for the size and position of the window. 224 The user can always move or resize it once it has been created. We 225create the window with the PopupWindow value and pass the menu to be used. Once the 226window has been created we call ShowWindow to make it visible and SetForegroundWindow to 227make it appear above other windows.</p> 228 229<p>Some messages are sent to the window procedure as a result of calling the functions 230such as CreateWindow. Generally, though, messages are queued and have to be 231explicitly dequeued and passed to the window procedure. In C this is done by a 232message loop with GetMessage and DispatchMessage. In ML we use the <a href="../WinRef/Message.html#RunApplication">RunApplication</a> function which deals with all 233this. RunApplication returns when a <a href="../WinRef/Message.html#WM_QUIT">WM_QUIT</a> 234message is received. To ensure that this happens we have to put this message into 235the queue. The easiest way to do this is to call PostQuitMessage when the window is 236about to go away. We have already seen how DestroyWindow is called as a result of 237processing the WM_CLOSE message. DestroyWindow sends <a href="../WinRef/Message.html#WM_DESTROY">WM_DESTROY</a> and <a href="../WinRef/Message.html#WM_NCDESTROY">WM_NCDESTROY</a> messages to the window procedure 238to allow it to clean up properly. WM_NCDESTROY will be the last message to be sent 239to this window procedure so we call PostQuitMessage while handling it.</p> 240 241<pre>| WM_NCDESTROY => 242 ( 243 PostQuitMessage 0; 244 (DefWindowProc(hw, msg), state) 245 )</pre> 246<p>We need to call <a href="../WinRef/Class.html#UnregisterClass">UnregisterClass</a> when 247RunApplication returns. Classes are automatically unregistered when the application 248terminates but in this context the application is the whole Poly/ML session so if we want 249to run mlEdit again within the same session we need to unregister the class. 250Otherwise the next time we call RegisterClassEx with the same name it will fail because 251the class is already registered.</p> 252 253<h3><a name="Menus">4. Menus</a></h3> 254 255<p>We have already seen that a menu can be set up by passing its handle as part of the 256PopupWindow value to CreateWindow. Let's see how the menu itself is created and how 257it is processed.</p> 258 259<p>There are a number of ways a menu can be created but perhaps the simplest is to build 260it up using calls to <a href="../WinRef/Menu.html#CreateMenu">CreateMenu</a> and <a href="../WinRef/Menu.html#AppendMenu">AppendMenu</a>. The structure we want for our 261menu is a bar containing File, Edit and Help menus each with several menu items. In 262general a menu consists of multiple items, each of which may either be a command item or 263may pull up a sub-menu.</p> 264 265<pre>val menuOpen = 1 266and menuQuit = 2 267and menuSave = 3 268...</pre> 269 270<pre>val fileMenu = 271 let 272 val fileMenu = CreateMenu(); 273 in 274 AppendMenu(fileMenu, [], MenuId menuOpen, MFT_STRING "&Open"); 275 AppendMenu(fileMenu, [], MenuId menuSave, MFT_STRING "&Save"); 276 AppendMenu(fileMenu, [], MenuId menuSaveAs, MFT_STRING "Save &As..."); 277 AppendMenu(fileMenu, [], MenuId 0, MFT_SEPARATOR); 278 AppendMenu(fileMenu, [], MenuId menuPageSetup, MFT_STRING "Page Set&up..."); 279 AppendMenu(fileMenu, [], MenuId menuPrint, MFT_STRING "P&rint..."); 280 AppendMenu(fileMenu, [], MenuId 0, MFT_SEPARATOR); 281 AppendMenu(fileMenu, [], MenuId menuQuit, MFT_STRING "&Quit"); 282 fileMenu 283 end;</pre> 284 285<p>We create the file menu by calling CreateMenu and then AppendMenu for each item. 286Every item, apart from separators, has a different item ID. The values are arbitrary 287but we need to use different values for each because these are the values which will be 288passed to our window procedure when a particular menu item is selected. Separators, 289which appear as horizontal lines when the menu is pulled down, are used to improve the 290layout. The ampersands (&s) precede the character which will be underlined in 291the menu. These provide keyboard shortcuts for menu items. Typically the first 292character is used but sometimes we have to use another character in order to give all the 293items in a menu different characters.</p> 294 295<pre>val menu = CreateMenu(); 296val _ = AppendMenu(menu, [], MenuHandle fileMenu, MFT_STRING "&File"); 297val _ = AppendMenu(menu, [], MenuHandle editMenu, MFT_STRING "&Edit") 298val _ = AppendMenu(menu, [], MenuHandle helpMenu, MFT_STRING "&Help")</pre> 299 300<p>We can create the edit and help menus in exactly the same way. When they have 301been created we can build the full menu. The argument to AppendMenu is MenuHandle 302rather than MenuId since these are sub-menus.</p> 303 304<p>As with all other input selecting a menu item causes the system to send a message to 305the window. It sends a <a href="../WinRef/Message.html#WM_COMMAND">WM_COMMAND</a> 306message with information about the particular menu item. The wId value in the 307messagecontains the identifier which was used when the menu was created. </p> 308 309<pre>| WM_COMMAND{notifyCode = 0, wId, control} => 310 311 if wId = menuQuit 312 then 313 ( 314 if checkForSave(hw, edit, fileName) then DestroyWindow hw else(); 315 (LRESINT 0, state) 316 ) 317 else ...</pre> 318 319<p>The simplest item to process is when Quit is selected. We process it in almost 320the same way as we process WM_CLOSE, except in this case we have to explicitly call 321DestroyWindow since the default action for this message is to do nothing. Note that 322since we process WM_NCDESTROY by calling PostQuitMessage the message loop in 323RunApplication will exit when the window destruction is complete.</p> 324 325<h3><a name="Sending">5. Sending Messages</a></h3> 326 327<p>So far we have seen how we process messages but not how our application can send them. 328 An application sends messages using either <a href="../WinRef/Message.html#SendMessage">SendMessage</a> or <a href="../WinRef/Message.html#PostMessage">PostMessage</a>. The main difference between 329them is that SendMessage does not return until the window procedure has processed the 330message and so it is able to return a result to the caller. It functions essentially 331as a, possibly remote, procedure call.</p> 332 333<p>We can use SendMessage to process some of the menu items.</p> 334 335<pre> else if wId = menuCut 336 then (SendMessage(edit, WM_CUT); (LRESINT 0, state)) 337 else if wId = menuCopy 338 then (SendMessage(edit, WM_COPY); (LRESINT 0, state)) 339 else if wId = menuPaste 340 then (SendMessage(edit, WM_PASTE); (LRESINT 0, state))</pre> 341 342<p>The Cut, Copy and Paste items from the edit menu are handled by sending messages to the 343edit window. The edit window processes these by copying data to and from the 344clipboard. In this example we are not interested in the result that SendMessage 345returns but we use it rather than PostMessage to ensure that the command is fully 346processed before, for example, we accept any characters as input.</p> 347 348<p>Another case where we can use SendMessage is to see whether the text has been modified 349and so whether we need to save it before quitting.</p> 350 351<pre>fun checkForSave(hw, edit, fileName) = 352 case SendMessage(edit, EM_GETMODIFY) of 353 LRESINT 0 => true (* Unmodified - continue. *) 354 | _ => ... (* Save it. *)</pre> 355 356<p>We send the edit window an EM_GETMODIFY message. If the reply is LRESINT 0 (i.e. 357false) it has not been modified and we don't need to do anything. Otherwise we need 358to save the document, or at least ask whether we should save it.</p> 359 360<h3><a name="Dialogues">6. Dialogues</a></h3> 361 362<p>A dialogue box is a special kind of window which is used to present information to the 363user or to request information. They contain one or more <em>controls</em> and a 364button usually labelled OK and often a Cancel button. The simplest form of dialogue 365is the <em>message box</em>. This presents a piece of text and has one or more 366buttons. </p> 367 368<pre>let 369 val res = 370 MessageBox(SOME hw, "Save document?", "Confirm", 371 MessageBoxStyle.MB_YESNOCANCEL) 372in 373 if res = IDYES 374 then .... (* Save document. *) ... 375 else if res = IDNO 376 then true (* Continue anyway. *) 377 else false (* Cancel - don't exit or open. *) 378end</pre> 379 380<p>We use a message box in mlEdit to ask whether we should save the document if it has 381been modifed. The MB_YESNOCANCEL value for the style means that the message box will 382have three buttons: Yes, No and Cancel. The message box is an example of a <em>modal</em> 383dialogue. Modal dialogues are those which need a response before the application can 384continue. The application is disabled until the dialogue has been dismissed by 385clicking one of the buttons. In contrast a modeless dialogue acts just like another 386window and the user can interact with either the application window or the dialogue box. 387 <a href="../WinRef/MessageBox.html#MessageBox">MessageBox</a> returns with an 388identifier which indicates the button which was pressed. If Yes, we need to save 389the document; if no, we don't. If Cancel was pressed we need to cancel the operation 390which caused this dialogue to be presented. For instance, if the user accidentally 391clicked on the Close box of the window and then pressed Cancel we do not pass the WM_CLOSE 392message to the default window procedure. It is good interface design practice to 393provide some way for the user to cancel an action.</p> 394 395<p>A more sophisticated dialogue is used when the user selects "About mlEdit..." 396from the Help menu. MessageBox is not sufficient for this dialogue since we want to 397include an icon in the dialogue box so we use one of the general dialogue functions. 398 The layout of a dialogue can be given either in a <em>resource file</em> or as a <em>template</em>. 399 Resource files are often a convenient way of holding dialogues as well as menus and 400other strings. They lend themselves particularly to producing <em>localized</em> 401versions of programs, i.e. programs with the user interaction tailored for a particular 402language and/or culture, since all the text that needs to be localized can be stored in 403the resource file. A resource file is either an executable file (.EXE) or a dynamic 404library (.DLL). To use a resource file you will need a suitable resource compiler 405which will normally form part of a development environment such as Microsoft Visual C or 406Borland C++. You can then load the resource file and get a handle to it using <a href="../WinRef/Resource.html#LoadLibrary">LoadLibrary</a>. It is possible to make use 407of resources in the Poly/ML program using the instance handle returned by <a href="../WinRef/Globals.html#ApplicationInstance">ApplicationInstance</a>. We actually 408do this in mlEdit to get a handle to the icon.</p> 409 410<pre>(* Borrow the Poly icon from the application program. It happens to be icon id 102. *) 411val polyIcon = Icon.LoadIcon(app, Resource.MAKEINTRESOURCE 102);</pre> 412 413<p>For this example, though, we use a template for the dialogue.</p> 414 415<pre>val pictureId = 1000 (* Could use any number here. *) 416open Static.Style 417val template = 418 {x = 0, y = 0, cx = 210, cy = 94, font = SOME (8, "MS Sans Serif"), menu = NONE, 419 class = NONE, title = "About mlEdit", extendedStyle = 0, 420 style = flags[WS_POPUP, WS_CAPTION], 421 422 items = 423 [{x = 73, y = 62, cx = 50, cy = 14, id = 1, 424 class = DLG_BUTTON (flags[WS_CHILD, WS_VISIBLE, WS_TABSTOP]), 425 title = DLG_TITLESTRING "OK", creationData = NONE, extendedStyle = 0}, 426 427 {x = 7, y = 7, cx = 32, cy = 32, id = pictureId, 428 class = DLG_STATIC (flags[WS_CHILD, WS_VISIBLE, SS_ICON]), 429 title = DLG_TITLESTRING "", creationData = NONE, extendedStyle = 0}, 430 431 {x = 15, y = 39, cx = 180, cy = 21, id = 65535, 432 class = DLG_STATIC (flags[WS_CHILD, WS_VISIBLE, WS_GROUP]), 433 title = DLG_TITLESTRING 434 "mlEdit - An exmple of Windows programming in Poly/ML\ 435 \\nCopyright David C.J. Matthews 2001", 436 creationData = NONE, extendedStyle = 0}] }</pre> 437 438<p>The dialogue contains three items: a button with the title "OK", 439 a static picture with style SS_ICON, and a piece of static text. Along 440 with the layout of the dialogue we also need a dialogue procedure. A dialogue 441 procedure is almost the same as a window procedure. It processes messages 442 sent to the dialogue in the same way as a window procedure does. The only 443 difference is that a dialogue procedure does not call DefWindowProc.</p> 444 445<pre>fun dlgProc(dial, WM_INITDIALOG _, ()) = 446 ( 447 (* Send a message to the picture control to set it to this icon. *) 448 SendMessage(GetDlgItem(dial, pictureId), STM_SETICON{icon=polyIcon}); 449 (LRESINT 1, ()) 450 ) 451 452| dlgProc(dial, WM_COMMAND{notifyCode = 0, wId=1 (* OK button *), ...}, ()) = 453 (* When the OK button is pressed we end the dialogue. *) 454 (EndDialog(dial, 1); (LRESINT 1, ()) ) 455 456| dlgProc _ = (LRESINT 0, ())</pre> 457 458<p>We only process two messages here: WM_INITDIALOG and WM_COMMAND. WM_INITDIALOG is 459sent when the dialogue is created. We use GetDlgItem to get a handle to the static 460picture control and then send it a STM_SETICON message to set the picture. We only 461need to do this when the dialogue is initialised. The static control will take care 462of displaying the picture whenever the dialogue box is visible. WM_COMMAND messages 463are sent by buttons as well as by menus and in this example we process the message by 464calling EndDialog to close the dialogue box. The value of wId in this case is the 465identifier of the OK button.</p> 466 467<p>To construct and display the dialogue box we call DialogBoxIndirect with the template 468and the dialogue procedure. The parent of the dialogue is our main window 469(hw). This window will automatically be disabled until the dialogue box has been 470closed.</p> 471 472<pre>DialogBoxIndirect(app, template, hw, dlgProc, ());</pre> 473 474<h3><a name="Common">7. Common Dialogues</a></h3> 475 476<p>For many purposes a standard dialogue box can be used and Windows provides a number of 477these. For instance, most applications include a way for the user to open a file and 478it would be tedious to have to program this from scratch for each new application. 479It is far easier to use a standard dialogue. More to the point, by using a standard 480dialogue the interface to the user is similar to that of other applications, reducing the 481learning load.</p> 482 483<p>The mlEdit example uses a number of these. <a href="../WinRef/CommonDialog.html#GetOpenFileName">GetOpenFileName</a> and <a href="../WinRef/CommonDialog.html#GetSaveFileName">GetSaveFileName</a> are used to select the 484file to open and for the Save As menu item. <a href="../WinRef/CommonDialog.html#FindText">FindText</a> is used for the Find menu item. 485 <a href="../WinRef/CommonDialog.html#PageSetupDlg">PageSetupDlg</a> and <a href="../WinRef/CommonDialog.html#PrintDlg">PrintDlg</a> are used for the Page Setup and 486Print items. They generally work in the same way, creating a modal dialogue 487requesting the information and returning when the OK or Cancel button is pressed. 488The ML functions take a configuration structure and return an option type. If the 489user cancels the operation they return NONE, if the user presses OK they return a new 490configuration structure with the requested information filled in. FindText works 491differently. It creates a modeless dialogue and instead sends <a href="../WinRef/Message.html#FINDMSGSTRING">FINDMSGSTRING</a> messages to the parent window.</p> 492 493<h3><a name="Printing">8. Printing and Painting</a></h3> 494 495<p>One area we have not touched on is actually how we draw to the screen. By using 496the Edit window to draw the text our application does not actually need to concern itself 497with exactly what happens when an area of the screen is uncovered and how characters in 498the text are converted into dots on the screen. This is all dealt with by the Edit 499window class.</p> 500 501<p>When it comes to printing the file these issues become apparent. Printing to a 502printer and drawing to the screen are handled in exactly the same way in Windows. In 503both cases we need to draw the document into a <em>device context</em>. A device 504context is an abstraction used for printers, the screen and also for <em>bitmaps</em> and <em>metafiles</em> 505in memory. Although there is an obvious difference in the printed page and an image 506on the screen in terms of the physical output as far as preparing the image is concerned 507they are both rectangular areas of dots (<em>pixels</em>). They may differ in size, 508in the range of colours possible and in the number of pixels per inch (<em>resolution</em>). 509 Since we don't need to be concerned with drawing to the screen in this application 510we will focus on printing the file but we could easily arrange the code so that it could 511be used for both.</p> 512 513<p>We get a device context for the printer by giving the PrintDlgFlags.PD_RETURNDC option 514to the print dialogue. It would also be possible to get a device context using 515CreateDC. Once we have the device context we can find the size of the page.</p> 516 517<pre>val _ = SetMapMode(hdc, MM_TEXT) 518val pageWidth = GetDeviceCaps(hdc, HORZRES) 519and pageHeight = GetDeviceCaps(hdc, VERTRES)</pre> 520 521<p>We now need to find a font to use. We want a 10 point Courier font which we 522obtain using <a href="../WinRef/Font.html#CreateFont">CreateFont</a>. This is a fixed 523width font which simplifies calculating the page width.</p> 524 525<pre>val charHeight = ~10 * GetDeviceCaps(hdc, LOGPIXELSY) div 72; 526val hFont = CreateFont{height=charHeight, width=0, escapement=0, orientation=0, 527 weight=FW_DONTCARE, italic=false, underline=false, strikeOut=false, 528 charSet=ANSI_CHARSET, outputPrecision=OUT_DEFAULT_PRECIS, 529 clipPrecision=CLIP_DEFAULT_PRECIS, quality=DEFAULT_QUALITY, 530 pitch=FIXED_PITCH, family=FF_MODERN, faceName="Courier"}</pre> 531 532<p>The character height calculation looks odd, giving us a negative value, but is a 533pecularity of CreateFont. All the other arguments are fairly obvious. </p> 534 535<pre>val oldFont = SelectObject(hdc, hFont); 536 537val textMetric = GetTextMetrics hdc;</pre> 538 539<p>We now select this as the font to use. <a href="../WinRef/DeviceContext.html#SelectObject">SelectObject</a> can be used for various 540other sorts of object, such as pens, brushes and bitmaps. Whenever you select in a 541particularly kind of object the previous object of that kind is returned as the result. 542 It's generally good practice to select it back before you finish with a device 543context. Having selected this font we then call <a href="../WinRef/Font.html#GetTextMetrics">GetTextMetrics</a> to find the width of the font. 544 From this and the page width we can compute the number of characters on a line. 545 The height of the page divided by the height of a character gives us the 546number of characters on a page. We are now ready to print a page.</p> 547 548<p>We get the text from the edit window using <a href="../WinRef/Window.html#GetWindowText">GetWindowText</a>. 549 The print dialogue gives the user the option of printing the currently selected 550area of the window rather than the whole file or a range of pages. To find the 551selection we send the edit window an EM_GETSEL message.</p> 552 553<p>We prepare the page by setting the colours and filling the page with white. These 554are likely to be the defaults anyway but there's no harm in making sure.</p> 555 556<pre>val white = RGB{red=255, blue=255, green=255} 557val black = RGB{red=0, blue=0, green = 0} 558val pageRect = {top=0, left=0, bottom=pageHeight, right=pageWidth} 559 560SetBkColor(hdc, white); 561SetTextColor(hdc, black); 562ExtTextOut(hdc, {x=0, y=0}, [ETO_OPAQUE], SOME pageRect, "", []);</pre> 563 564<p><a href="../WinRef/Font.html#ExtTextOut">ExtTextOut</a> is one of the ways of drawing text 565but it is also a convenient way of filling an area with a colour. The ETO_OPAQUE 566option causes the rectangle to be filled with the background colour.</p> 567 568<p>Actually drawing each line is done using the <a href="../WinRef/Font.html#TabbedTextOut">TabbedTextOut</a> 569function. We extract a line from the document and draw it .</p> 570 571<pre>TabbedTextOut(hdc, {x=0, y= lineNo * #height textMetric}, thisLine, [], 0);</pre> 572 573<p>Each line is drawn beneath the previous one until the page is full or we have drawn all 574the text.</p> 575 576<p>All this is the same whether we are printing a file or drawing to the screen. 577When printing, though there are a few extra function calls we need. Before each page 578we call <a href="../WinRef/Printing.html#StartPage">StartPage</a> and after the page we call <a href="../WinRef/Printing.html#EndPage">EndPage</a>. We also need to bracket the whole 579document with calls to <a href="../WinRef/Printing.html#StartDoc">StartDoc</a> and <a href="../WinRef/Printing.html#EndDoc">EndDoc</a>. When the document is complete we also 580need to restore the original font, delete the Courier font we created and delete the 581device context.</p> 582 583<pre>val jobID = StartDoc(hdc, {docName=fileName, output=NONE, dType=NONE}); 584... 585EndDoc hdc; 586SelectObject(hdc, oldFont); 587DeleteObject hFont; 588DeleteDC hdc;</pre> 589 590<p>We could use this code to draw to the screen if we were not using the edit 591 window. In that case we would process the WM_PAINT message and bracket 592 the calls with <a href="../WinRef/Painting.html#BeginPaint">BeginPaint</a> and 593 <a href="../WinRef/Painting.html#EndPaint">EndPaint</a>.</p> 594 595</body> 596</html> 597