Intro to Windows Programming

From CometWiki

Revision as of 09:32, 7 June 2009 by Badge (Talk | contribs)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

Contents

A Brief History of the Windows API

The Windows API for Internet Basic programmers is now in its second generation. Originally introduced in 1996, the API consisted of the following files, each of which was “included” in your program: • CosWin.ibs – The Driver Library. This file contains IB functions and support code necessary to perform various Windows functions. Your program will call subroutines from this library. The names of the subroutines begin with “COS.”, such as COS.CreateButton. • CosWin.inc - Declaratives for constants, variables, and formats that are used by the code contained in CosWin.ibs • Windows.inc – Definition of constants used to represent all the possible styles, messages, and event notifications for each control available through the API. The constants have been derived from the C/C++ include file “windows.h” which is supplied as part of the C/C++ development platform. Because Windows programs are “event-driven”, the API had to include a method for notifying the IB program when an event occurred. This was accomplished through the use of “call back” subroutines. A call back subroutine is one defined in the IB program that will be executed by the API when a particular event occurs, such as the clicking of a button in the dialog. Since there are a number of types of events that the IB programmer may want to handle, this meant having to define a call back subroutine for each event type. While the API provided the IB programmer with the tools necessary to create true Windows programs for their Comet applications, its implementation was a little cumbersome. Comet 2004 introduced the second generation of the API. The call back mechanism was replaced with the EventSub mechanism, which had proven itself in the implementation of hyperlinks. This greatly simplified event handling. Now all event types could be handled in one place. The new API also included a new file, CometWin.inc. It provides a new layer between the IB program and the API to take care of many of the housekeeping details previously left to IB program. It is the only one of the API files you need to include in your IB program. You will use subroutines and declarations from the original API files, but CometWin.inc will include those files for you. Subroutines in CometWin.inc are named beginning with “CW.”, such as CW.CreateAutoModeDialog. Because CometWin.inc includes both declarative and executable code, you will include it twice in your IB program – once at the top of your declarative section and once at the bottom of your executable section.

Dialog or Direct?

The API accommodates two methods for creating the controls for your Windows program. The method you choose is a matter of personal preference. One method is to use a visual resource editor to design your dialog using “drag-and-drop” tools. We call this the dialog method. When you’re done with the design, the dialog is compiled to a .dll, which is loaded by your IB program. No coding is required to define the layout or style of the controls in the dialog. We support use of the resource editor included in Microsoft’s Visual Studio. The other method we call the “direct” method. In this case, the controls are created directly in your IB program by making calls to the API. We’ll take a look at examples of each, then compare and contrast the two methods. 2

A Discussion of WinTemp.ibs

WinTemp.ibs was designed to be a template program to get you started on your Windows program using the direct method. It creates a window with just OK and CANCEL buttons, but has all the hooks for you to add other controls. The Event Handler As you know, Windows programs are event-driven. Rather than dictating the flow of the process, the program responds to events initiated by the user. Thus it makes sense that the heart of any Windows program is the code that intercepts these events. In WinTemp.ibs, this is a subroutine called EventHandler. When you create your controls, you will tell Comet which events you want to be notified of. Then you tell it the name of the subroutine to call when an event occurs and you wait for your first event: EventSub EventHandler CW.Event$ CW.Source$ Eventwait CW.Event$ and CW.Source$ are API variables that will receive information about what event has occurred. Then, control will be passed to the subroutine referenced in the EventSub statement: EventHandler: gosub CW.ParseEvent ! Determine which type of event occurred select case cosCtlId case IDOK ! User pressed the OK button if cosCommand = BN.CLICKED ! To do: write code to process OK button endif case IDCANCEL ! User pressed the CANCEL button if cosCommand = BN.CLICKED CW.ExitEvents = TRUE ! Signal that we want to stop the event handler endif ! To do: write code for any other cases we expect to process endselect if CW.ExitEvents = FALSE ! Either re-establish or cancel event handler eventsub EventHandler CW.Event$ CW.Source$ else eventsub endif return The first thing your event handler should do is to determine which event occurred. You do this by calling CW.ParseEvent. It loads API variables with information about the event. The two you will use the most are: cosCtlId The ID of the control that generated the event, ex. IDOK – the OK button cosCommand The event, ex. BN.CLICKED – the button was clicked When we return from CW.ParseEvent, we use a SELECT clause to process our event. When we’ve finished processing the event, we need to either re-establish the event wait mechanism so we can get the next event or 3 signal to Comet that we are done. In the example above, the event handler is stopped when the IDCANCEL button is clicked. This causes program flow to continue just after the EventWait statement at the top of our program. So that’s all there is to an event handler. You will add a CASE statement for each control from which you expect an event. Inside each CASE you’ll have code for each event’s cosCommand value. Initializing the API OK, so now that you understand how events are handled, there are a few other details you need to attend to. The very first thing your program should do is initialize the Comet Windows API (CosWin). You do this by calling CW.Open. This will initialize required variables, hide the cursor, and establish the size of your screen, the default font you will use, and the color of the wallpaper behind your controls: !!!!!!!!!!! Initialize CosWin ! Set CW.Width and CW.Height to customize screen size. They are ignored if set to 0. CW.Width = 80 CW.Height = 30 ! Set CW.Font$ to change the view font. It is ignored if set to "" CW.Font$ = "Arial" ! To set your wallpaper color, define values for red, green, and blue. ! The values assigned here define a sort of parchment color. For white, set all to 255. cosRed = 255 & cosGreen = 254 & cosBlue = 236 !! As an alternative, if you want the default dialog color, call COS.GetSysColor !! gosub COS.GetSysColor ! Sets cosRed, cosGreen, and cosBlue CW.Color = (cosBlue*65536) + (cosGreen*256) + cosRed gosub CW.Open !!!!!!!!!!! Initialization complete Among the API variables that are initialized by CW.Open, one that you may find particularly handy is CW.Remote. If your program is running on a Comet Anywhere client, CW.Remote will be set to TRUE. You will need to know this if you will be loading any bitmaps or launching any Windows files. In that case, you will need to first copy the files from the host to the remote. If you’re curious about the details of CW.Open and CW.ParseEvent, you’ll find the code in CometWin.Inc on WDL. Creating Your Controls Now the fun part! Once you’ve got CosWin initialized, you’ll create each of your controls. Each control is really a small window. It is important to keep this in mind when reading about Windows programming. Your controls will be child windows of your Comet window, which is called the parent window. The CtlDemo programs include an example of each control supported by the API. For most of them, the process is pretty much the same. For each control you’ll assign an ID number, the height, width, and screen position, and any desired style characteristics. You’ll use the following API variables: cosCtlId - the numeric identifier for the control 4 cosWidth - the width (in Centiunits) cosHeight - the height (in Centiunits) cosRow - the screen row (in Centiunits) where the control is to be positioned cosCol - the screen column (in Centiunits) cosStyle - the main style characteristics parameter cosExStyle - the extended style parameter For some controls, such as buttons and edit controls you’ll also assign some text to be displayed on the control. Use cosCtlText$ for this. And, for most controls you’ll need to set cosDlgFlags = 0. This is used to tell the API when you’re using additional modifiers. Here’s an example that creates a “Next” button: cosCtlId = IDC.NEXT ! IDC.NEXT is a symbolic constant set to 41001 cosWidth = 800 & cosHeight = 150 ! The button will be 8 chars wide and 1.5 chars high cosRow = 1400 & cosCol = 3600 ! Position button on row 14, column 36 cosStyle = WS.VISIBLE + WS.CHILD + WS.TABSTOP + WS.GROUP + BS.DEFPUSHBUTTON cosExStyle = 0 cosCtlText$ = "Next" cosDlgFlags = 0 cosCWnd$ = cosDlghDlg$ gosub COS.CreateButton One thing that might be puzzling in this code is the cosStyle assignment. This defines characteristics of the button. The style values that begin with “WS.” are generic “Window Styles” and apply to any control. So, for example any time you create a control and you want it to be visible on your screen, you’ll include the WS.VISIBLE style, regardless of the type of control. Then, for specific controls, there may unique styles values. For a button, BS.DEFPUSHBUTTON indicates that this is the default button. A full list of the style values can be found in Windows.inc. Descriptions of the styles available for each control can be found on Microsoft’s web site at http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vclib/html/_mfc_styles_used_by_mfc.asp Whenever a window is created it is assigned a handle to uniquely identify it to Windows. Since each control is really a window, every control has its own handle. The parent window also has a handle. The WS.CHILD style indicates that this control will be a child window. We need to pass the parent’s handle to Comet when we create the new control. cosDlghDlg$ will contain the handle to the parent. We pass it in cosCWnd$. After you create the control, you need to tell Comet which event(s) you want to process. You do this by appending the control ID and event to the CosWin API command filter. So, to request that we be notified any time our “Next” button is clicked, we code: cosCWnd$ = cosDlghDlg$ ! Pass the handle of the parent window, as it owns the command filter cosCtlText$ = intel(IDC.NEXT) + intel(BN.CLICKED) gosub COS.AppendCmdFilter cosCtlText$ is a concatentation of word pairs. The first word is the control’s ID; the second word is the event ID. As a matter of fact, you can send multiple pairs to the command filter in one call. For example, if you had a CANCEL button as well an a NEXT button, you could either call COS.AppendCmdFilter once for each button, or make just one call: cosCWnd$ = cosDlghDlg$ ! Pass the handle of the parent window, as it owns the command filter cosCtlText$ = intel(IDC.NEXT) + intel(BN.CLICKED) + intel(IDCANCEL) + intel(BN.CLICKED) gosub COS.AppendCmdFilter This method of building the command filter is used for most of the controls. A few controls do it a little differently. The List View control is one. Instead of calling COS.AppendCmdFilter, you will call COS.AppendNotifyFilter2. It 5 requires 3 parameters for each control event. You can see examples of how this is done in Ctl10.ibs for the List View control. Shutting down the API After you stop the event handler and are about to exit your program you need to shut down the API and restore your environment. This is done with a simple call to CW.Close: !!!!!!!!!!! Program Exit ProgExit: eventsub ! Cancel the event handler in case F3 or fatal error occurred gosub CW.Close ! Restore environment if RUNSTATE = 6 exit stop CW.Close destroys all of the controls you created, removes your wallpaper, and restores the cursor. 6

A Discussion of DlgTemp.ibs

DlgTemp.ibs was designed to be a template program to get you started on your Windows program where you’ve used a resource editor to create a .dll for your dialog. DlgTemp.ibs can be used to create either a modal or a modeless dialog with just OK and CANCEL buttons, but has all the hooks for you to process other controls in your dialog. The Event Handler Like WinTemp.ibs, the Event Handler is the control center of the program. When using a dialog, however, there are a couple of important differences. First, in addition to catching events for each control, you will also want to detect when the dialog is being loaded and when it is being closed. Look at the CASE ELSE below: EventHandler: gosub CW.ParseEvent ! Determine which type of event occurred select case cosCtlId case IDOK ! User pressed the OK button if cosCommand = BN.CLICKED ! To do: write code to process OK button endif ! To do: write code for any other control events we expect to process case else ! We got an event for something other than our controls gosub CW.IsWindowInitializing if cosState = TRUE ! Dialog is being loaded gosub InitDialog else gosub CW.CheckAutoModeClosing !!!! If the dialog is closing, CW.ExitEvents will be set to TRUE by the API endif endselect if CW.ExitEvents = FALSE ! Either re-establish or cancel event handler eventsub EventHandler CW.Event$ CW.Source$ else eventsub endif return When the dialog is being loaded, you’ll want to initialize the state of your controls such as populating a list box, or setting radio buttons or check boxes. It’s also a good time to establish your command filter. In WinTemp.ibs we did these things at the time we created the control. InitDialog: cosCWnd$ = cosDlghDlg$ cosCtlText$ = intel(IDOK) + intel(BN.CLICKED) gosub COS.AppendCmdFilter ! Setup the command filter ! This would be a good place to load any data you need into the dialog and to ! initialize the state of each control, ie check boxes, radio buttons,etc. 7 cosState = true ! Tell the API to size the CosW window to our dialog gosub CW.AutoSizeDialog cosCWnd$ = cosDlghDlg$ cosState = SW.NORMAL gosub COS.ShowWindow ! Make the dialog visible return Another difference when using a dialog is that clicking either the OK button or CANCEL button will cause the dialog to close automatically. All you need to do is to detect that the dialog is closing by calling CW.CheckAutoModeClosing. If the dialog is closing, it will set CW.ExitEvents to true and your event handler will not be re-established. When the dialog is closing, if you want to know which button was clicked, you can save its ID. It is set by CW.ParseEvent into coswParam. Note: If you want an event other than the clicking of OK or CANCEL to cause the dialog to close, you can call CW.CloseAutoModeDialog when you get that event. Conversely, if you don’t want the dialog to close automatically when either or both OK or CANCEL are clicked, you may disable this by handling the event explicitly. As a matter of fact, if you want to retrieve data from the dialog when OK is clicked you will have to handle that button explicitly. Just tell the command filter you want to handle that button, and add your code to the event handler. This gives you total control. The DlgTemp.ibs example handles the OK button explicitly. Loading Your Dialog

Getting your dialog loaded is really easy. All you need to do is to tell the API the name of your dialog and where to find your .dll. So, at the top of our program, you see: ! Establish BEFORE calling CW.CreateAutoModeDialog or else we'll miss the "dialog is initializing" event eventsub EventHandler CW.Event$ CW.Source$ cosDllNames$ = "DlgTemp.dll" ! DLL filename cosDllDir$ = sub(pstat(PARTITION$),41,3) ! Same dir as this program cosDlgId = IDD.DIALOG1 ! Dialog ID ! If your dialog was designated as “child” it will be modeless. If “popup” it will be modal. gosub CW.CreateAutoModeDialog if cosDlgOpen = FALSE print "Dialog execution failed" go ProgExit endif eventwait Note: If your program is running on a Comet Anywhere client, the call to CW.CreateAutoModeDialog will automatically transfer the .dll from the host to the $(CATOOLS) folder on the remote. In the Resource Editor The Resource Editor is where you design your dialog and create its controls. Running DPBuild.exe will quickly generate a template project for you. When you compile your project to generate the .dll, an ID will be assigned to each control and to the dialog itself. A control’s ID is what’s assigned to cosCtlID; the dialog’s ID is what’s assigned to cosDlgId. We recommend using SET statements to give a meaningful name to each ID. As an alternative to entering these IDs manually, 8 you'll find a program called Inc2Use.exe in the WDL release that will make an include file that declares the IDs for your IB program. Setup Inc2Use to run as part of your "Post-Build Steps" in your project and your IB include file will automatically be updated whenever you make a change to your dialog. Note: This will be done for you if you have created your project through CometDialogProjectBuilder.exe.

Dialog or Direct?

Now that you’ve seen examples of each, which method is best for you? It really is a matter of personal preference. Here are a few thoughts about each method: Dialog • It is really convenient to be able to create your screens by dragging and dropping the controls where you want them. • It's easy to select a group of controls and have them automatically sized and aligned the same. • Assigning attributes to a control is simple. A right click on the control pops up a window with check boxes for the style choices, a place to enter your caption, etc. • Since the code for creating all the controls is encoded in the .dll, your companion IB object will be smaller than a similar program written using the direct method. However, the sum of the two pieces will be larger than the single IB object. • Visual Studio will take care of assigning identifiers to each of your controls for you. Direct • You only need to deal with a single development environment. • You don’t have to package the .dll along with your application. Your IB object contains everything. • You can intersperse other GDI objects such as hyperlinks with your controls. • You can always write debug messages directly to the window. 9 A Beginner’s Guide to Events and Functions for Selected Windows Controls A complete list of events for each control can be found in Windows.inc. They are called “notify” or “notification” codes. A complete list of functions for each control can be found in CosWin.ibs. The Demo programs, WinTemp.ibs, and DlgTemp.ibs are released as part of the “DLG” folder. Control Events Functions Demo Sample Button BN.CLICKED COS.CreateButton any CtlDemo program Check Box BN.CLICKED COS.CreateButton Ctl3.ibs, Dlg3.ibs COS.CheckDlgButton COS.IsDlgButtonChecked Radio Button BN.CLICKED COS.CreateButton Ctl2.ibs, Dlg2.ibs COS.CheckRadioBtn COS.GetCheckedRadioBtn Static COS.CreateStatic Ctl1.ibs Edit cosEN.LOSTFOCUS COS.CreateEdit Ctl5a.ibs, Dlg5a.ibs EN.SETFOCUS COS.EditLimitText List Box LBN.SELCHANGE COS.CreateListBox Ctl4.ibs, Dlg4.ibs LBN.DBLCLK COS.ListAddString COS.ListDelString COS.ListInsertString COS.ListFindString COS.ListGetCount COS.ListResetContent COS.ListSetCurSel COS.ListGetCurSel COS.ListGetText 10 Combo Box CBN.SELCHANGE COS.CreateComboBox Ctl14.ibs, Dlg14.ibs CBN.DBLCLK COS.ComboAddString COS.ComboDeleteString COS.ComboInsertString COS.ComboFindString COS.ComboFindStringExact COS.ComboGetCount COS.ComboResetContent COS.ComboSetCurSel COS.ComboGetCurSel COS.ComboGetText COS.ComboLimitText List View NM.CLICK COS.CreateListControl Ctl10.ibs, Dlg10.ibs NM.DBLCLK COS.ListView.InsertTextColumn NM.RCLICK COS.ListView.SetColumnWidth LVN.COLUMNCLICK COS.ListView.InsertStringItem COS.ListView.SetFullRowSelect COS.ListView.DeleteAllItems COS.ListView.GetNextItem COS.ListView.GetItemText COS.ListView.SortItems All Controls COS.SetDlgItemFocus COS.EnableDlgItem Each function requires setting of parameters before making the call. To determine how to do this for a given function, either look at the sample code from the CtlDemo or DlgDemo program, or search CosWin.ibs for the function name. Comments provided there explain what is required and what will be returned following the call.