RichEdit Control

The final control I want to discuss under the topic of simple controls is the RichEdit control. This control is classified as a simple control because it's simple for the programmer to use—but there is nothing simple about the way the RichEdit control operates. The RichEdit control is an editor object that can read and write Rich Text Format (RTF) strings.

RTF is a nonproprietary format supported by most word processors including Microsoft Word. RTF isn't limited to the Windows world; most UNIX editors can read and write RTF files as well. While you can't express every formatting feature in the world in RTF—for example, you can't save version history in RTF format—as the name implies, RTF is rich in its expressiveness.

Building an RTF File Editor

The following RTFEditor program builds a fairly powerful RTF editor using an edit box for a file filter, a list box in Single Select mode, a rich edit box, and two button controls. The arrangement of objects within the form is shown in Figure 6-9.

Screenshot

Screenshot-9. The RichEdit control has no trouble reading an .RTF file generated in Microsoft Word, although it can't display text in different colors.

Forms Designer work

To build RTFEditor, drag the RichEdit control from the Toolbox to the Forms Designer and resize it until it takes up most of the available space in the form. Then rename it to richEdit and anchor it against all four sides of the form. Anchoring the control in this way ensures that the rich edit box will change size proportionately as the user resizes the form. Next set the control's wordWrap property to false and its scrollBars property to Both. This will cause scroll bars to appear whenever the text is too large to fit within the visible window.

Now add an Edit control below the RichEdit control. This Edit control's purpose is to enable the user to enter a file name to open or to save to. Finish the app's interface by adding an Open button and a Save button along the bottom of the form. Anchor these buttons and the file name Edit control to the bottom, left, and right borders of the form.

code

The code resulting from the form design work and the code I added are shown here.

import com.ms.wfc.app.*;
import com.ms.wfc.core.*;
import com.ms.wfc.ui.*;
import com.ms.wfc.html.*;
/**
 * This class demonstrates the RichEdit control.
 */
public class Form1 extends Form
{
 public Form1()
 {
 // Required for Visual J Plus Plus Form Designer support
 initForm();
 }
 .
 .
 .
 private void openButton_click(Object source, Event e)
 {
 // get the file name from the edit box
 String fileName = fileNameEdit.getText();
 // now read the file into the RichEdit control
 loadRichEdit(fileName);
 }
 private void closeButton_click(Object source, Event e)
 {
 String fileName = fileNameEdit.getText();
 try
 {
 richEdit.saveFile(fileName);
 }
 catch(Exception ex)
 {
 fileNameEdit.setText(ex.getMessage());
 }
 }
 /**
 * Given a file name, open the file into the
 * RichEdit control.
 */
 private void loadRichEdit(String fileName)
 {
 // open the specified file
 try
 {
 richEdit.loadFile(fileName);
 }
 // first catch invalid file type
 catch(WFCInvalidArgumentException e)
 {
 richEdit.setText("Invalid (non-RTF) file");
 }
 // handle all others the same way
 catch(Exception e)
 {
 richEdit.setText(e.getMessage());
 }
 }
 /**
 * NOTE: The following code is required by the Visual J Plus Plus form
 * designer. It can be modified using the form editor. Do not
 * modify it using the code editor.
 */
 Container components = new Container();
 Edit fileNameEdit = new Edit();
 Button openButton = new Button();
 Button saveButton = new Button();
 RichEdit richEdit = new RichEdit();
 private void initForm()
 {
 // …generated by the Forms Designer…
 }
 /**
 * The main entry point for the app. * …
 */
 public static void main(String args[])
 {
 app.run(new Form1());
 }
}


The openButton_click() method, which is invoked when the user selects Open, begins by reading the file name from the fileNameEdit object. It then passes this name to loadRichEdit() for processing.

The loadRichEdit() method uses the convenient RichEdit.loadFile() method to read the file into the RichEdit control. This method can throw one of two exceptions depending on the problem it detects. If loadFile() can find and open the file, but then determines that the file isn't in RTF format, it throws the exception WFCInvalidArgumentException. In this case, the loadRichEdit() method outputs a specific error message. If loadFile() can't open and read the file, it throws an I/O exception. In this case, loadRichEdit() relies on the error message in the exception.

The closeButton_click() method uses the RichEdit control's saveFile() method to save the contents of the RichEdit control to disk.

To test the RTFEditor program, I created a file in Microsoft Word containing several features. I then saved the file in RTF format and opened the file with RTFEditor. The result is shown in Figure 6-9. As you can see, RTFEditor has no trouble with the different fonts, font sizes, and font weights. However, in Word the second line is highlighted in green, but RTFEditor isn't capable of displaying lines in different colors (although it can display text in different colors).

Adding Drag-and-Drop Capability

An important feature of any text editor is the ability to support drag-and-drop actions. By this I mean two capabilities that are quite distinct. One is the ability to drag a text file and drop it onto the text editor program's executable file (or its icon) to begin program execution and immediately load the file. Let's call this capability initial drag-and-drop. A second capability meant by drag-and-drop is to drag a file to the running text editor program and drop it there to load the file in that program. Let's call this in-progress drag-and-drop.

Implementing initial drag-and-drop

Windows handles initial drag-and-drop just as if the user had executed the program from the DOS command prompt. For example, if the user drags file A.rtf and drops it on app B.EXE, Windows executes B and passes it the name A.rtf as a single argument just as if the user had entered the following DOS command:

B A.rtf


If you have been reading the chapters in order, you will remember that even our console apps in Part I had the ability to read arguments to the program using the args array. Thus, you already know how to implement initial drag-and-drop.

Implementing in-progress drag-and-drop

Initial drag-and-drop was easy to handle because the program was not already running. A program is already underway during in-progress drag-and-drop. The only way to get the attention of a running program is through events.

When the user begins dragging a file, Windows pastes the name of the file onto the Clipboard. This data stays on the Clipboard the entire time the mouse is dragging the file.

When the mouse moves onto our RTFEditor, Windows generates messages that Visual J Plus Plus v6 converts to three different event types: dragEnter, dragOver, and dragDrop. Any control that implements drag-and-drop must handle these three events.

The dragEnter event is generated as the mouse pointer enters the region occupied by the control. The dragOver event occurs repeatedly as the mouse moves within the control. Finally, the dragDrop event occurs when the user releases the object. (There is also a dragExit event, which occurs if the mouse leaves the control without dropping the object, but we won't be using that event.)

problem

Let's add the ability to process a file dropped on the RTFEditor form. This includes both initial drag-and-drop and in-progress drag-and-drop.

Part of the problem is already solved for us. The RichEdit control appears only after the RTFEditor program has started; therefore, it has no need for initial drag-and-drop capability. However, we will have to add the initial drag-and-drop capability to the RTFEditor program itself. In addition, the RichEdit control handles in-progress drag-and-drop already, but the form that contains this control has no in-progress drag-and-drop capability of its own. We will need to add this capability to the form.

Properties window

Before we can begin adding drag-and-drop event handlers, we must inform WFC that we intend to allow drag-and-drop on the form. Select the form in the Forms Designer. In the Properties window, set the allowDrop property to true. The RichEdit control always allows drops.

Switch to the active properties in the Properties window, and select the form again. Now double-click dragEnter to create the Form1_dragEnter() method; double-click dragOver to create the Form1_dragOver() method; and double-click dragDrop to create the Form1_dragDrop() method.

Code changes to support drag-and-drop

The following example shows the code for the RTFEditor app.

import com.ms.wfc.app.*;
import com.ms.wfc.core.*;
import com.ms.wfc.ui.*;
import com.ms.wfc.html.*;
import com.ms.wfc.io.*;
/**
 * This class demonstrates the RichEdit control with
 * drag-and-drop capability.
 */
public class Form1 extends Form
{
 public Form1(String[] args)
 {
 // Required for Visual J Plus Plus Form Designer support
 initForm();
 // if there is a file name present…
 if (args.length == 1)
 {
 // load it (This probably means that the user
 // has dropped the file name onto the program's
 // name in Windows Explorer or on top of the program's
 // icon in a window. I call this initial drag-and-
 // drop capability.)
 loadRichEdit(args[0]);
 }
 }
 .
 .
 .
 /**
 * Open the file contained in the fileNameEdit text field.
 */
 private void openButton_click(Object source, Event e)
 {
 // get the file name from the edit box
 String fileName = fileNameEdit.getText();
 // now read the file into the RichEdit control
 loadRichEdit(fileName);
 }
 /**
 * Close the file specified in the fileNameEdit text field.
 */
 private void closeButton_click(Object source, Event e)
 {
 String fileName = fileNameEdit.getText();
 try
 {
 richEdit.saveFile(fileName);
 }
 catch(Exception ex)
 {
 fileNameEdit.setText(ex.getMessage());
 }
 }
 /**
 * Given a file name, open the file into the
 * RichEdit control.
 */
 private void loadRichEdit(String fileName)
 {
 // make sure the Edit control displays
 // the file name
 fileNameEdit.setText(fileName);
 // open the specified file
 try
 {
 richEdit.loadFile(fileName);
 }
 // first catch invalid file type
 catch(WFCInvalidArgumentException e)
 {
 richEdit.setText("Invalid (non-RTF) file");
 }
 // handle all others the same way
 catch(Exception e)
 {
 richEdit.setText(e.getMessage());
 }
 }
 // --------handle the in-progress drag-and-drop--------------
 /**
 * Record the drag-and-drop state. If dragEffect is NONE,
 * a FileName was not found on the Clipboard, meaning that
 * there's nothing to copy.
 */
 private int dragEffect;
 /**
 * When the user drags the mouse into the form, check to
 * see whether there is a file name on the Clipboard. If not,
 * set the dragEffect to NONE. This precludes a drop
 * operation. If there is, set the effect to COPY (since
 * we want to copy the file name from the Clipboard).
 * If there is more than one file name, just take the
 * first one.
 */
 private void Form1_dragEnter(Object source, DragEvent e)
 {
 // for now, assume that there is no file name on
 // the Clipboard
 e.effect = DragDropEffect.NONE;
 // if there is a FileName data type in the Clipboard…
 IDataObject ido = e.data;
 if (ido.getDataPresent("FileName"))
 {
 // get the file name
 // (there may be more than one)…
 Object o = ido.getData(DataFormats.CF_HDROP);
 // as an array of file names
 String[] fileNames = (String[])o;
 // just take the first file name
 // (if there's more than one, ignore the rest)
 String fileName = fileNames[0];
 // now store that name in the fileNameEdit object
 // (we will need that name when the drop occurs)
 fileNameEdit.setText(fileName);
 // tell the drag operation that this succeeded
 e.effect = DragDropEffect.COPY;
 }
 // save the dragEffect (whether it's successful
 // or not)
 dragEffect = e.effect;
 }
 /**
 * Keep setting the dragEffect as long as the mouse is
 * hovering over the form during a drag operation.
 */
 private void Form1_dragOver(Object source, DragEvent e)
 {
 e.effect = dragEffect;
 }
 /**
 * When the user drops an RTF file onto the Form,
 * act like the user clicked the Open button. (The
 * file name should have been put in the fileNameEdit
 * text edit field already by the dragEnter event.)
 */
 private void Form1_dragDrop(Object source, DragEvent e)
 {
 // if the dragEffect is COPY…
 if (dragEffect == DragDropEffect.COPY)
 {
 // proceed with drop operation
 openButton_click(null, null);
 }
 }
 /**
 * NOTE: The following code is required by the Visual J Plus Plus form
 * designer. It can be modified using the form editor. Do not
 * modify it using the code editor.
 */
 Container components = new Container();
 Edit fileNameEdit = new Edit();
 Button openButton = new Button();
 Button saveButton = new Button();
 RichEdit richEdit = new RichEdit();
 private void initForm()
 {
 //…generated by the Forms Designer…
 }
 /**
 * The main entry point for the app. *
 * @param args - the name of the file to open (optional)
 */
 public static void main(String args[])
 {
 app.run(new Form1(args));
 }
}


The loadRichEdit(), openButton_click(), and closeButton_click() methods are unchanged from previous versions of RTFEditor. The Form1() constructor has been updated to handle the arguments passed to main(). If there is one argument present, the constructor assumes it is the name of a file and calls loadRichEdit() to load the file into the RichEdit control. This is all that is required to handle initial drag-and-drop.

The methods below the //---handle in-process drag-and-drop--- comment are new to this version of RTFEditor.

The first addition is the data member dragEffect. Its meaning will become clear shortly.

Next comes the Form1_dragEnter() method, which is invoked as the dragged mouse pointer enters Form1. This method has two arguments: the source object and a DragEvent object. The Form1_dragEnter() method begins by setting the effect member of DragEvent to DragDropEffect.NONE. (This happens to be the default value of effect, but it doesn't hurt to include this statement for demonstration purposes.) Setting effect to NONE tells Visual J Plus Plus v6 to not allow the drop event. In addition, this sets the mouse pointer to that funny-looking circle with a slash through the middle, like a European "don't enter" sign (assuming that Windows is using the default pointer set).

The Form1_dragEnter() method continues by examining the data member of DragEvent. The data member implements the IDataObject interface, which means that it supports a number of methods all related to retrieving and storing data to and from the Clipboard.

NOTE
The IDataObject interface is also used to represent objects being pasted from the Clipboard, as we'll see in .

Since the Clipboard can contain almost any type of object, Form1_dragEnter() can't assume that something on the Clipboard is the name of a file. The call to ido.getDataPresent("FileName") returns true if an object of type FileName is present on the Clipboard. If true is returned, Form1_dragEnter() reads the object off the Clipboard by calling ido.getData().

There are two ways to invoke getData(). The call getData("FileName") is the older, now deprecated way to read a file name from the Clipboard. This method returns the name of the file in the older DOS-compatible 8.3 naming format. Calling getData(DataFormats.CF_HDROP) returns the file name in the newer Win32 format. (DataFormats is an enumerated class containing a number of different format indicators.)

The object returned by getData() is an array of String objects. Each String in the array contains the name of a file. The getData() method returns an array because the user can select a number of files and drop them all at the same time. Since RTFEditor can only open a single file, Form1_dragEnter() selects the first file name and ignores the rest. This file name is then stored in the fileNameEdit object for two reasons. The first reason is to display the name of the file the user is about to drop. The second reason is that this is the file that RTFEditor will open in the event the drop is performed.

The Form1_dragEnter() method then updates the effect field in DragEvent to DragDropEffect.COPY. This has two important effects. First, it changes the mouse pointer to a plus sign to indicate that a drop on this object is allowed (again, assuming the default mouse pointers). Second, updating the effect field to COPY allows the Form1_dragDrop() method to drop the file onto the form.

Finally, the drag effect is saved in the local dragEffect data member. Thus, if Form1_dragEnter() found a "FileName" object on the Clipboard, dragEffect is set to COPY; if not, dragEffect is set to NONE.

The Form1_dragOver() method simply updates the effect data member of the DragEvent to the value stored by Form1_dragEnter(). This ensures that the mouse pointer continues to appear as the copy symbol.

Now that the Form1_dragEnter() method has done all the dirty work of retrieving the name of the file to open from the Clipboard, all Form1_dragDrop() has to do is invoke the openButton_click() method, exactly as if the user had clicked the Open button. (The test for dragEffect equal to COPY is not really necessary, since if effect were set to NONE the drop wouldn't be allowed anyway, but it doesn't hurt anything to be specific.) The openButton_click() method loads the file named in the fileNameEdit object into the RichEdit control.

result

Screenshot-10 shows the user dragging the file RichEditTest1.rtf onto the RTFEditor form. The small arrow with a plus sign in the lower-right corner of the form indicates that RTFEditor is prepared to accept a drop at that location. The edit box at the bottom of the RTFEditor form contains the name of the file about to be dropped. (Both the cursor and the edit box were set by the Form1_dropEnter() method.)

Java Click to view at full size.

Screenshot-10. Dragging an .RTF file onto the RTFEditor form changes the cursor to the copy-style plus sign and updates the edit box with the name of the file.

Screenshot-11 shows the result of dropping the RichEditTest1.rtf file onto the RTFEditor form. Notice that the file is now opened in the RichEdit window and the name and path are displayed in the edit box, ready to be used by the Save button once the user has completed editing the file.

Java Click to view at full size.

Screenshot-11. Dropping the .RTF file onto the RTFEditor form opens the file in the RichEdit window.

Extending drag-and-drop

The drag-and-drop capability shown here involves the dragging of a file onto the RTFEditor form. The Form1_dragEnter() method reflected this fact in that what it checked for was an object of type "FileName". As I have already mentioned, Windows supports drag-and-drop of different types of objects other than file names. For example, the user might cut a section of RTF text from one app and drop it onto our RTFEditor.

Extending the file drag-and-drop code shown here to other types of objects is a matter of checking for a different type of object on the Clipboard and recognizing the format of the object returned. demonstrates the passing of data other than straight text through the Clipboard in the context of the cut-and-paste operation. Comments