Building a Simple Editor Part 2
Written by Gordon Zeglinski
Preamble
Recently, I was playing around with the source to maplay (a freeware mpeg layer III audio decoder). For fun, I tweaked the code and tried compiling it under VisualAge C++ for Windows and Watcom C/C++ 10.6. According to the NT performance monitor, the VAC++ executable took roughly 20% less cpu (~45% for VAC++ executable and ~65% for Watcom executable on a 133 MHz pentinum).
IBM has two new books out on using VisualAge C++. I haven't read them yet, but a preliminary scan through them indicates focus on using the Visual Builder. I won't do a comprehensive review of them but will give a general opinion of them in the future.
They are: "VisualAge for C++ Visual, Programmers Handbook" ISBN 0-13-614322-9 and "Visual Modeling Technique, Object Technology using Visual Programming" ISBN 0-8053-2574-3
Introduction
In this issue, we revisit the editor to add search capabilities. In particular, we will see how to use canvas controls. Canvas controls position other control windows within themselves. This allows us to create dialog box like windows without using non-portable ".rc" files. In addition, canvas controls are somewhat analogous to layout managers in Java.
VisualAge C++ includes 3 types of canvas controls. They are implemented by the classes IMultiCellCanvas, ISetCanvas and ISplitCanvas. We will use the first two canvas types in this example. Without further delay let's get to it.
Canvas Controls
The IMultiCellCanvas control is divided into an arbitrary number of rows and columns. The width of a column is equal to the largest minimum width of a control in that column. The height of a row is determined in the same manner. All controls in a given column are sized to the same width. Similarly, all controls in a given row have the same height. A row and column can be marked as expandable. This allows the row or column to expand when the canvas expands. For example, the canvas is the client area for a resizeable frame window. When the frame is resized, the expandable rows and columns would increase in size so the canvas fills the entire client. Naturally, the controls in the expanded rows and columns would also resize.
One drawback to making all controls in a given row or column the same size is that controls that are supposed to be narrow become expanded to match the largest control. A practical example would be placing an "OK" button under an MLE. The button would become expanded to be the same width as the MLE. The way to prevent this, is to use an ISetCanvas. An ISetCanvas organizes its child control windows into "decks". Each deck contains controls separated by a definable amount of space. Decks can be oriented either vertically or horizontally. Typically, the ISetCanvas is used to group buttons together. Now that we've covered the basics of these two types of canvas controls, let's look at an example.
Searching the MLE
To search the MLE, we create a simple generic search class as shown below. MLESearch is derived from IFrameWindow and ICommandHandler. Its constructor expects to be given the parent and owner window in addition it needs a pointer to IMultiLineEdit instance it's working on. When an instance of MLESearch is created, a modal "dialog box" appears.
class MLESearch:public IFrameWindow,public ICommandHandler{ public: enum Buttons{FindID=10,FindNextID,DoneID}; MLESearch(IWindow *Par, IWindow *Own,IMultiLineEdit *_edit, int ID=9000); ~MLESearch(); protected: //the search function int FindFnc(); //handle button presses Boolean command(ICommandEvent &event); //pointer to the MLE we're working with IMultiLineEdit *edit; //the main canvas and client window IMultiCellCanvas Canvas; //entry field to accept the search for text IEntryField FindTxt; //static text field to label the entry field IStaticText label; //second canvas to group the buttons ISetCanvas Canvas2; //buttons for user interaction IPushButton Find, FindNext, Done; IString fndText, //text we are looking for srchText; //text in the MLE int StartPos; //position to start search at int CursorPos, //initial cursor position top; //initial top line };
It's important to note the parent owner windows in the constructor. Canvas windows can only position control windows that are their children. The code for the constructor follows:
MLESearch::MLESearch(IWindow *Par, IWindow *Own, IMultiLineEdit *_edit,int ID): IFrameWindow( IResourceId(ID), Par, Own, IRectangle(), dialogBackground|dialogBorder|titleBar, "Search" ), srchText( _edit->text() ), edit( _edit ), Canvas( 1, this, //Frame window is parent this //Frame window is owner ), Canvas2( 2, &Canvas, //main canvas is parent and owner &Canvas ), Find( FindID, &Canvas2, //Set canvas is parent and owner &Canvas2 ), FindNext( FindNextID, &Canvas2, //Set canvas is parent and owner &Canvas2 ), Done( DoneID, &Canvas2, //Set canvas is parent and owner &Canvas2 ), FindTxt( 20, &Canvas, //main canvas is parent and owner &Canvas ), label( 100, &Canvas, //main canvas is parent and owner &Canvas ) { //save initial MLE settings CursorPos=StartPos=edit->cursorPosition(); top=edit->top(); //label the buttons Find.setText("Find"); FindNext.setText("Find Next"); Done.setText("Done"); FindNext.enable(false); //set the label text label.setText("Find Text:"); //put the children of "Canvas" into cells Canvas.addToCell(&label,2,2); Canvas.addToCell(&FindTxt,2,4); Canvas.addToCell(&Canvas2,2,6); //center the Find window over the owner IRectangle orect(Own->rect()); IRectangle rect(orect.minX(), orect.minY(), orect.minX()+Canvas.minimumSize(true).width()+ IMultiCellCanvas::defaultCell().width(), orect.minY()+Canvas.minimumSize().height() ); rect=frameRectFor(rect); IPair c( (orect.width()-rect.width())/2 , (orect.height()-rect.height())/2 ); orect.shrinkBy(c); ISize size( rect.width(), rect.height() ); moveSizeTo(IRectangle(orect.minXMinY(),size ) ); setClient(&Canvas); handleEventsFor(this); show(); showModally(); }
Note: The order in which the control classes are created effects the tab order. In particular, the order in which the control windows appear in the class definition is VERY important. The parent and owner of a control MUST appear in the class definition before the control's definition. For example, see the order in which the canvas and button controls are defined.
The code for the destructor simply resets the cursor and top line position of the MLE. We have seen command handlers before. Therefore, the curious can look at the source code for details on the function "command". The resulting window is shown below.
The actual searching is done in the following function. This function performs a case sensitive forward search.
int MLESearch::FindFnc(){ //do a case sensitive forward search from StartPos int Idx=srchText.indexOf(fndText,StartPos); if(Idx){ //select the found text edit->selectRange(IRange(Idx-1,Idx-1+fndText.length())); StartPos=Idx+fndText.length(); //move cursor to the start of the found text edit->setCursorPosition(Idx-1); //make found text the top line edit->setTop(edit->cursorLinePosition()); } return Idx!=0; //Idx is not 0 is we have a match }
That's it for the find function. Nothing fancy but functional.
Wrapping Things Up
We have seen how to use the IMultiCellCanvas and ISetCanvas classes to create operating system independent dialog box type windows. These canvas controls only manipulate child windows. The order in which these child windows are created determines their tab order.