File: jan_tutorial.tar.gz Author: Be Developer Support (devsupport@be.com) Release: 2.0 Compatibility: DR8 Location: Samples Description: Tutorial covering BeOS programming basics Notes: Scope ===== This tutorial is intended for the beginning Be developer. Knowledge of C++ is assumed. The following topics will be covered: * Creating projects * Basic application framework * Creating windows, and views within windows (view hierarchy) * Graphics and dynamically resizable views Note: Reading/reviewing The BeBook while reading this tutorial will enhance your learning experience..... Introduction ============ The January program displays the calendar for the month of January in a window. In addition, the calendar dynamically redraws as the window is resized. Source File description ----------------------- * GMCGraphics.cpp Defines an object, BTSMonthGraphic, which can be displayed by inserting it into a view. * MonthView.cpp A view that contains an object of type BTSMonthGraphic. * January.cpp Contains the main routine and definition of the application window, BTSApplication window. * StringLabel.cpp Utility class, BTSSTringLabel, for displaying a string, using centering and styling. * January.rsrc Resource file containing Icon and other information about January. Projects ======== The BeOS development environment allows developers to create project files that can be used in conjunction with the IDE, for a graphical presentation of the project and compilation information and commands. For those from the UNIX world, makefiles and command-line compiling are also supported. The January project gives examples of both. CodeWarrior users should open the "January.proj" file at this time, while UNIX fans should open up a terminal and cd to the January directory. Application Framework ===================== The base application class is BApplication. Every application must instantiate an instance of BApplication. A reference to this object is automatically placed in the the global variable be_app, for convenience. In the case of January, it's not necessary to override the default application class behavior. Also, we want a window to open automatically when we start the application. So, our main routine looks like this: main() { BTSApplicationWindow *aWindow; BRect aRect; // We can use a standard BApplication object, because // we don't have anything special to add to what it does // by default. BApplication *myApplication; myApplication = new BApplication('JANR'); // set up a rectangle and instantiate a new window aRect.Set(20, 20, 200, 200); aWindow = new BTSApplicationWindow(aRect); // Once everything is setup, start running the application. myApplication->Run(); // Delete the application object before exiting. delete(myApplication); return(0); } Application Specific Windows ---------------------------- Any application that creates windows will have to subclass the BWindow class. In this case, our window class is BTSApplicationWindow. The definition of the class looks like this: class BTSApplicationWindow : public BWindow { public: BTSApplicationWindow(BRect frame); virtual bool QuitRequested(); }; Only two methods are defined. Overriding the constructor allows us to insert the code that creates the calendar within the window, as follows: BTSApplicationWindow::BTSApplicationWindow(BRect frame) : BWindow(frame, "January", B_TITLED_WINDOW, 0) { BRect aRect = frame; // rect same size as window. BTSMonthView *aView; // Move rect so top left is a (0,0) aRect.OffsetTo(B_ORIGIN); aView = new BTSMonthView(aRect, "MonthView"); // Lock the window before altering contents. Lock(); // add view to window AddChild(aView); // Unlock after done altering window contents. Unlock(); // make window visible Show(); } We create an object of type BTSMonthView, making it the same size as the window, then insert it into the window. Every view can be thought of as a container which can hold other views. This is called a view hierarchy. At the root of every view hierarchy is an object that is either a BWindow or a subclass of BWindow. While the BWindow itself is not a view, it has a private member that is a view. This view is not accessible to programmers. So, you do not draw directly into a window, but rather you create a view of your own, draw into it, and insert it into the window's view hierarchy. And that's exactly what is done here. The other unusual thing is the Lock() and Unlock() calls around the AddChild() call. It is necessary to Lock a window whenever you change its internal state. Examples of this are: changing the view hierarchy, calling Draw() explicitly, or resizing the window. These calls are necessary to keep other threads from also changing the window at the same time, resulting in unpredictable results. However, functions that are called directly by the system (Draw(), WindowActivated(), MessageReceived() ) don't need to perform a Lock(), as the system does it before calling the function. Everone should read the section in BView chapter of the Interface Kit documentation entitled "Locking the Window". All of the drawing actually occurs within the BTSMonthView. Let's ignore that for now. The only other method in BTSApplicationWindow is QuitRequested: bool BTSApplicationWindow::QuitRequested() { // Tell the application to quit! be_app->PostMessage(B_QUIT_REQUESTED); return(TRUE); } When a user closes a window by any means, the BWindow object's hook function QuitRequested() is called. In this case, the proper response is to quit the program entirely, by posting the "B_QUIT_REQUESTED" message to the application. There are a lot of other hook functions like this for common window activities;check the "Responding to the User" section of the Interface Kit documentation. Any application that displays a user interface should exit when the last window closes. If this is not done, the application's main menu will disappear, and the user will be unable to quit the program in an acceptable way. If you need this type of functionality, you should create a "dummy" BWindow object and hide it, only deleting it when the program exits. If you have multiple windows, you need to keep track of the window count and exit the program when the count gets to 0. This problem will disappear in a later release of the BeOS. At this point, we have a basic application framework created; these same definitions of main and of the application window could be used for any single-window application. The only thing that would need to be changed is the view that we would be inserting into the window in the window's constructor. Graphics and dynamically resizing views ======================================= The graphics work is all done within the BTSMonthView object. Here's the constructor: BTSMonthView::BTSMonthView(BRect frame, char *name) : BView(frame, name, B_FOLLOW_ALL, B_FULL_UPDATE_ON_RESIZE|B_WILL_DRAW|B_FRAME_EVENTS) { fBackgroundGraphic = new BTSMonthGraphic(this); SetViewColor(B_TRANSPARENT_32_BIT); } All that needs to be done in the constructor is to create the graphics object representing the calendar, BTSMonthGraphic. Note that we pass BTSMonthGraphic a 'this' pointer to the view. This is because BTSMonthGraphic is not a view subclass itself, so it needs a graphics context in order to draw. This is given by the view. In many cases, it is more convenient to just make the graphics object itself a subclass of BView. However, for simple graphics that don't interact with the user (such as text labels), subclassing from BView is unnecessary, and the approach of passing the object a drawing context is faster, more efficient, and makes the graphics object more general purpose. Note the flags passed at the end of the BView constructor. There are a lot of flags that can be passed to a view to customize its behavior; see the interface kit documentation for more information. The particular flags used here specify the following: FOLLOW_ALL - When the view is resized from any direction grow the view in that direction. FULL_UPDATE_ON_RESIZE - Invalidate the entire view and not just the freshly exposed portion. We want this because otherwise we would only redraw new portions, when in our case we want the entire view to be drawn if the size changes. FRAME_EVENTS - We want to be notified when the view's size changes. If we don't use this flag, then the FrameResized method will not be called and we'll never know when to change the size of the graphic. WILL_DRAW - The view wants to have its Draw() function called in response to an update event. Dynamic resizing is easy; since we specified FRAME_EVENTS, as the window is resized, we get a stream of calls to the view's FrameResized() hook function, and in turn we resize the embedded graphic: void BTSMonthView::FrameResized(float new_width, float new_height) { fBackgroundGraphic->SetSize(BPoint(new_width, new_height)); } Drawing ------- Finally, drawing! the calendar graphic is represented by a grid. As the window resizes, the grid spacing changes. Based on the new grid spacing, the font size and text for the various labels on the calendar are selected. We can follow all the action starting with the SetSize() method, which is called by BTSMonthView's Draw() method: void BTSMonthGraphic::SetSize(BPoint size) { fSize = size; Invalidate(); } void BTSMonthGraphic::Invalidate() { // Do the resizing magic // Re-calculate the various size things CalculateParameters(); CalculateDayLabels(); // Make sure the view draws itself fImageNeedsUpdate = 1; } OK...without going into too much detail, CalculateParameters() determines the new grid size and spacing. CalculateDayLabels() determines what the labels should be for the days of the week; if the grid spacing is large, "Sunday" will work; if it's small, it might be just "Su". fImageNeedsUpdate is a flag that tells the Draw() function of the graphic that it needs to do some work. With views, the Draw() function is called automatically by the enclosing container when an update message is received that includes some or all of the region occupied by the view. In this case, our graphic object is not a view, so we must maintain this information ourselves. The Draw() method itself is fairly straightforward. The background is drawn, the calendar outline is drawn, the month and day names are drawn, and the days of the month are drawn. All text is represented by StringLabel objects which were created in the Init() method. The StringLabel object, like BTSMonthGraphic, is not a view, but rather knows how to draw itself within a view. Drawing on the BeOS is not done by the application itself, but by the application server. The app server is a separate process, one copy of which is always running. (You can see this by opening up a terminal window and entering the command "ps | grep app_server". All drawing commands are communicated to the app server for execution. To speed this up, line drawing commands can be bundled in arrays so that the app server can execute them in a more rapid fashion. This is done by using the BeginLineArray(), AddLine(), and EndLineArray() methods. After calling BeginLineArray(), calls to AddLine() add commands to the array. Calling EndLineArray() informs the app server that the entire array of line drawing commands should be executed. The following piece of code from BTSMonthGraphic::Draw() illustrates this: fView->BeginLineArray(7); for (counter = 1; counter < 7; counter++) { BPoint startPoint(xStart+(counter*GridCellSize.x)-2,yStart); BPoint endPoint(xStart+(counter*GridCellSize.x)-2,yEnd); fView->AddLine(startPoint, endPoint, blackColor); } fView->EndLineArray(); The code above results in only one large communication with the app server, instead of 7 small ones. However, calling AddLine() too many times without calling EndLineArray() may result in worse performance, due to the memory overhead of the array. (The BeBook recommends no more than 256). The separation of the actual drawing from the drawing commands brings up an important problem; how does the programmer know when a drawing command has actually executed? In many cases the app server may not execute drawing commands as soon as they are received. In order to force the app server to complete all drawing requests for a given view, the Sync() method of the view should be called. This method is synchronous, and when it returns you can be sure that all draw commands for the view have been executed. This is particularly important when doing interactive drawing; for example, if the user is dragging a graphic around within a window, if Sync() is not called after drawing the graphic, the dragging may appear jumpy. BTSStringLabel -------------- BTSStringLabel is an object that can remember the font parameters necessary to draw it, including position within the view, font name, size, shear, rotation, and color.Rather than have separate views for each piece of text, the BTSStringLabel is associated with a view. When it comes time to draw, the BTSStringLabel object changes the view's font parameter to match its own parameters,then draws its associated string: void BTSStringLabel::Draw(BView* aView) { if (fNeedsCalculation) Recalculate(aView); // Setting font info aView->SetFontName(fFontInfo.name); aView->SetFontSize(fFontInfo.size); aView->SetFontShear(fFontInfo.shear); aView->SetFontRotation(fFontInfo.rotation); aView->SetHighColor(fColor); aView->MovePenTo(fStartPoint); aView->DrawString(fString); } IconWorld ========= IconWorld is used to do basic resource configuration for your app. The IconWorld section in the BeOS User's Guide describes it very well. For mac developers, think of it as ResEdit for the BeOS, although it has far fewer resources. Open January.rsrc using IconWorld. Note under the "App Info" menu that the flag "Single Launch" is set. This means that one copy of the application can be running per executable file, as opposed to "Exclusive Launch", which means one application can be running on the system, period. Of course "Multiple Launch" means the same app can be launched multiple times on the same system. In this case, the selection of "Single Launch" was arbitrary, but for some apps it may be very important. Note the icon's type is 'BAPP', which means it's associated with the application. If this app had documents associated with it, we could create another icon to represent the documents, and give the icon a type that matched the signature of the document files. Close IconWorld. Building and Running the Project ================================ You can refer to the BeOS User's Guide and Metrowerks documentation for complete details on building applications. Below is a short summary of the steps. You now have a basic application framework that you can use as a jumping off point for your own application. No employees of Be or their children were involved in vomiting episodes (projectile or otherwise) during the writing of this tutorial. We hope the same is true for you. For UNIX users -------------- in the January directory, type "make". If you want, you can open up the makefile and check it out. The only thing that may look unfamiliar is the lines: copyres $(RESOURCE_FORK) $@ setfile $@ The first line just appends the resource information to the end of the file. The second line informs the BeOS that the newly created program is an executable file. For CodeWarrior users --------------------- Choose "Make" from the project menu. Choose "Run" if you want to run it.