blob: f8bff32f7b432b1332d67bfdb827a731b9822bad [file] [log] [blame]
<html>
<head>
<meta HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=windows-1252">
<title>Simplifying Preference Pages with Field Editors</title>
<link rel="stylesheet" href="../../default_style.css">
<link rel="stylesheet" href="../version.css">
</head>
<body LINK="#0000ff" VLINK="#800080">
<div align="right"><font face="Times New Roman, Times, serif" size="2">Copyright
&copy; 2002 Object Technology International, Inc.</font>
<table border=0 cellspacing=0 cellpadding=2 width="100%">
<tr>
<td align=LEFT valign=TOP colspan="2" bgcolor="#0080C0"><b><font face="Arial,Helvetica"><font color="#FFFFFF">&nbsp;Eclipse
Corner Article</font></font></b></td>
</tr>
</table>
</div>
<div align="left">
<h1><img src="images/Idea.jpg" height=86 width=120 align=CENTER></h1>
</div>
<p>&nbsp;</p>
<h1 ALIGN="CENTER">Simplifying Preference Pages with Field Editors</h1>
<blockquote>
<b>Summary</b><br>
Even though preference pages can be simple to program, you can spend a
lot of time getting them "just right." Field editors make this task faster and easier by providing the behavior
for storing, loading, and validating preferences. Field editors also define some of the behavior for grouping
and laying out widgets on a preference page.
<p>
<b> By Ryan Cooper, OTI</b> <br>
August 21, 2002</p>
</blockquote>
<div class="version-info">
<p><em>This article applies to Eclipse 2.0.</em> The contents may not be valid for other versions of Eclipse.
<a href="https://bugs.eclipse.org/bugs/show_bug.cgi?id=138906">Bug 138906</a> has been opened to discuss
this article.</p>
</div>
<hr width="100%">
<h2>Introduction</h2>
<p>
A field editor is an object that presents the user with the value of a preference from a preference store.
It implements the loading, storing, and validating of its associated value. Instances of <tt>FieldEditor</tt> are most often
used in conjunction with a <tt>FieldEditorPreferencePage</tt>, which provides automatic validation handling and displays messages
received from its field editors. We will discuss when field
editors are most (and least) effective and briefly describe the available types of field editors. We will then explain how to use
them in both a <tt>FieldEditorPreferencePage</tt> and a regular <tt>PreferencePage</tt> using two example pages containing
some of the most commonly used field editor types. We will finish up with a discussion about writing your own field editors.
This article will assume some knowledge of preference pages and preference stores.
If you aren't familiar with the way they work, check out Tod Creasey's article
<a href="http://www.eclipse.org/articles/Article-Preferences/preferences.htm">Preferences in
the Eclipse Workbench UI</a> before continuing with this article.
</p>
<h2>When to use field editors (and when not to)</h2>
<p>
Most of the field editor types supplied by JFace were created to handle simple preference types. For example, there are no field editors
to handle tree selections. Therefore, the simpler your preference page, the more useful field editors will be to you. If
your page uses only simple widgets such as buttons, check boxes, and text fields, it's probably a good idea to use a
<tt>FieldEditorPreferencePage</tt>. Doing so will prevent you from having to write any code to store, retrieve, or validate values.
</p>
<p>
If your preference page contains one or more complex widgets such as a <tt>List</tt> or <tt>Tree</tt>, a regular preference page can be
a better solution. Even so, you can use field editors for any simple preferences on the page. In this case, you can
call the appropriate methods of <tt>FieldEditor</tt> to store, retrieve, and validate values, instead of writing SWT level code yourself.
Another option (discussed in the
<a href="#writing">Writing your own field editors</a> section) is to write any new field editors that you will need.
</p>
<p>
Although <tt>FieldEditorPreferencePage</tt> is very convenient for creating preference pages with
simple layouts, it does not allow you the control over the layout that a regular <tt>PreferencePage</tt> does.
If you want to fine-tune the layout of a preference page, you should use a regular <tt>PreferencePage</tt> instead of a
<tt>FieldEditorPreferencePage</tt>.
</p>
<p>
Sometimes field editors do not provide the access to widgets that you need. For example, you may need to set a selection listener on a
widget contained in a field editor. However, the field editor may not provide access to its widget. In cases
such as this one, it may be simpler not to use a field editor. As a rule of thumb, use field editors whenever possible, but only use a
<tt>FieldEditorPreferencePage</tt> if all of the preference types on a page are supported by field editors or you are willing to
write new field editors to support them. <tt>FieldEditorPreferencePage</tt> assumes that all preferences on the page are represented in
field editors.
Any preference not represented by a <tt>FieldEditor</tt> will be ignored by many of the methods of <tt>FieldEditorPreferencePage</tt>.
For example, the value of such a preference will not be saved in the preference store when <tt>performOk()</tt> is called.
</p>
<h2>Types of field editors</h2>
<p>
JFace provides nine concrete subclasses of <tt>FieldEditor</tt>. Each corresponds to a common widget grouping used to
present a preference. To illustrate some of these field editors, a screenshot of a preference page
for an imaginary HTML editor has been included. The available concrete field editors are:
</p>
<ul>
<li>
<b><tt>BooleanFieldEditor</tt></b> - This field editor takes the form of a checkbox with a label. It is used for presenting
boolean preferences.
</li>
<li>
<b><tt>IntegerFieldEditor</tt></b> - This field editor takes the form of a label and a text field. It is intended for presenting
integers, so any non-integer input is considered invalid. You can further refine the field validation by specifying a valid range for
the integer using <tt>setValidRange(int, int)</tt>.
</li>
<li>
<b><tt>StringFieldEditor</tt></b> - This field editor is very similar to <tt>IntegerFieldEditor</tt>. <tt>StringFieldEditor</tt>
is a more general-purpose field editor with simpler validation rules. Normally, any string is valid. However, you can
specify whether an empty string is valid or not by calling <tt>setEmptyStringAllowed(boolean)</tt>.
</li>
<li>
<b><tt>RadioGroupFieldEditor</tt></b> - This field editor takes the form of a group label and a set of radio buttons, each with a
label of its own. It is used to present a set of mutually exclusive choices. A <tt>RadioGroupFieldEditor</tt> may
be optionally contained by a <tt>Group</tt> control which adds an etched border around the field editor.
</li>
<li>
<b><tt>ColorFieldEditor</tt></b> - This field editor is used to present color preferences. It consists of a label and a button
which displays the currently selected color. Clicking on the button opens a <tt>ColorDialog</tt>, allowing the user to choose
another color.
</li>
<li>
<b><tt>FontFieldEditor</tt></b> - This field editor is used to select font preferences. It consists of a label for the preference,
a read-only text field containing the name, style, and size of the font, and a button with the label "Change...".
When the button is clicked, a <tt>FontDialog</tt> opens, allowing the user to choose a new font, change the font's style, and
change it's size.
</li>
<li>
<b><tt>DirectoryFieldEditor</tt></b> - This field editor is used to display a directory path in the file system. It
consists of a label, a text field containing the directory path, and a button with the label "Browse...". Clicking on this
button allows the user to browse the file system and choose a new directory.
</li>
<li>
<b><tt>FileFieldEditor</tt></b> - This field editor is similar to a <tt>DirectoryFieldEditor</tt>, but it allows the user
to select a file instead of a directory.
</li>
<li>
<b><tt>PathEditor</tt></b> - This field editor is the most complex concrete field editor supplied by JFace. It consists of a
title label, a list of directory paths, and a button bank to the left of the list with four buttons: "New...", "Remove",
"Up", and "Down". The "New..." button allows the user to browse the file system and add a new path to the list. The "Remove" button
removes the current selection from the list. The "Up" and "Down" buttons allow the user to change the order of the list items by
moving the selected item up or down. This field editor is not often used, but it is approprite when a preference keeps track of
several paths such as the class paths for a compiler.
</li>
</ul>
<p align="center"><img src="field_editor_types.jpg"></p>
<p align="center">Different types of field editors</p>
<h2>How to use field editors</h2>
<p>
This section will describe two example preference pages which make use of field editors. The two pages store the preferences for
a simple HTML editor. The editor has the ability to auto-format HTML (indenting nested tags a number of spaces
specified on the preference page), display text in different colors based on context, open a web browser for visually
checking HTML files, and perform some minimal error checking. The preference pages for the editor allow the user to configure
the preferences for these features.
</p>
<h3>Using field editors with a FieldEditorPreferencePage</h3>
<p>
The main preference page for the HTML editor is a subclass of <tt>FieldEditorPreferencePage</tt>, since all of the preferences on
the page are of types supported by field editors.
</p>
<pre>
class HTMLPreferencePage
extends FieldEditorPreferencePage
implements IWorkbenchPreferencePage {
// ...
}
</pre>
<p>A subclass of <tt>FieldEditorPreferencePage</tt> only requires a few methods to
be implemented. Subclasses must instantiate and set a preference store for the preference page, as shown below:
</p>
<pre>
public HTMLPreferencePage() {
<img src="images/tag_1.gif" height=13 width=24 align=CENTER> super(FieldEditorPreferencePage.GRID);
// Set the preference store for the preference page.
IPreferenceStore store =
HTMLEditorPlugin.getDefault().getPreferenceStore();
setPreferenceStore(store);
}
</pre>
<p>
The constructors for <tt>FieldEditorPreferencePage</tt> each take as an argument an <tt>int</tt> representing the layout style of the
page. In the above constructor, we use the constant
<tt>FieldEditorPreferencePage.GRID</tt> (see <img src="images/tag_1.gif" height=13 width=24 align=CENTER>). This constant indicates
that the controls of the field editors will be placed into a single grid
layout. The alternative constant is <tt>FieldEditorPreferencePage.FLAT</tt>, which indicates that each field editor is handled
separately. <tt>GRID</tt> generally results in more visually appealing preference pages, so it is
the layout style constant you should usually use.
</p>
<p align="center"><img src="HTML_pref_page.jpg"></p>
<p align="center">The HTML Editor Preference Page</p>
<p>
Subclasses must also implement <tt>FieldEditorPreferencePage.createFieldEditors()</tt>. In this method, we instantiate all of the field
editors on the page, and then add them to the page by calling
<tt>FieldEditorPreferencePage.addField(FieldEditor)</tt>. If field editors are instantiated but not added to the preference page, their
values will not be saved in the preference store, and will therefore have no effect. The following is the <tt>createFieldEditors()</tt>
method from our <tt>HTMLPreferencePage</tt>:
</p>
<pre>
protected void createFieldEditors() {
// Initialize all field editors.
BooleanFieldEditor formatOnSave = new BooleanFieldEditor(
IPreferenceConstants.FORMAT_PREFERENCE,
"&Format before saving",
<img src="images/tag_1.gif" height=13 width=24 align=CENTER> getFieldEditorParent());
addField(formatOnSave);
SpacerFieldEditor spacer1 = new SpacerFieldEditor(
<img src="images/tag_1.gif" height=13 width=24 align=CENTER> getFieldEditorParent());
addField(spacer1);
IntegerFieldEditor indentSpaces = new IntegerFieldEditor(
IPreferenceConstants.INDENT_PREFERENCE,
"&Number of spaces to indent:",
<img src="images/tag_1.gif" height=13 width=24 align=CENTER> getFieldEditorParent());
indentSpaces.setValidRange(0, 10);
addField(indentSpaces);
SpacerFieldEditor spacer2 = new SpacerFieldEditor(
<img src="images/tag_1.gif" height=13 width=24 align=CENTER> getFieldEditorParent());
addField(spacer2);
ColorFieldEditor commentColor = new ColorFieldEditor(
IPreferenceConstants.COMMENT_COLOR_PREFERENCE,
"C&omments:",
<img src="images/tag_1.gif" height=13 width=24 align=CENTER> getFieldEditorParent());
addField(commentColor);
ColorFieldEditor errorColor = new ColorFieldEditor(
IPreferenceConstants.ERROR_COLOR_PREFERENCE,
"&Errors:",
<img src="images/tag_1.gif" height=13 width=24 align=CENTER> getFieldEditorParent());
addField(errorColor);
ColorFieldEditor validColor = new ColorFieldEditor(
IPreferenceConstants.VALID_COLOR_PREFERENCE,
"&Valid text:",
<img src="images/tag_1.gif" height=13 width=24 align=CENTER> getFieldEditorParent());
addField(validColor);
FontFieldEditor font = new FontFieldEditor(
IPreferenceConstants.FONT_PREFERENCE,
"Edito&r font:",
<img src="images/tag_1.gif" height=13 width=24 align=CENTER> getFieldEditorParent());
addField(font);
SpacerFieldEditor spacer3 = new SpacerFieldEditor(
<img src="images/tag_1.gif" height=13 width=24 align=CENTER> getFieldEditorParent());
addField(spacer3);
FileFieldEditor webBrowser = new FileFieldEditor(
IPreferenceConstants.BROWSER_PREFERENCE,
"&Web browser:",
true,
<img src="images/tag_1.gif" height=13 width=24 align=CENTER> getFieldEditorParent());
addField(webBrowser);
}
</pre>
<p>
In the above code (see <img src="images/tag_1.gif" height=13 width=24 align=CENTER>), a separate call is made to
<tt>getFieldEditorParent()</tt> for each field editor. Your first instinct may be to cache this
method's return value in a local variable, and replace all the method calls with references to that variable. However, it is important
not to do so. According to the method's documentation, a new parent may be created each time the method is called. Caching the value
disobey's the method's contract.
</p>
<p> You may have noticed the use of <tt>SpacerFieldEditor</tt> in the above code.
This is a new <tt>FieldEditor</tt> subclass implemented in the example. It is
included in the zip file referred to in the <a href="#conclusion">Conclusion</a>
section. It is used to add space to a preference page to make it look less cluttered.
Unfortunately, JFace does not provide the ability to add space to a <tt>FieldEditorPreferencePage</tt>.
This is a known deficiency in the JFace framework. Different developers have
attempted several workarounds, but none have been entirely satisfactory. </p>
<p>
Since a
<tt>SpacerFieldEditor</tt> doesn't represent a preference, you might not think it's necessary to add it to the
page after instantiating it.
Since <tt>FieldEditorPreferencePage</tt> also controls the layout of the page, the page layout may become jumbled
if there are any field editors that
aren't added. Always call <tt>addField(FieldEditor)</tt> for each field editor on a
<tt>FieldEditorPreferencePage</tt>.
</p>
<h3>Using field editors with a regular PreferencePage</h3>
<p>
The HTML editor has a secondary preference page for controlling its error detecting facilities. There are currently two
options: "Don't show errors" and "Show error if a closing tag is missing." We would like this to be an easily
expandable preference, so we will use a radio button group instead of a checkbox. <tt>RadioGroupFieldEditor</tt> is
suited for this preference. However, the page also contains a list (a list of HTML tags for which a closing tag
is not required), for which there is no suitable field editor, so we will subclass <tt>PreferencePage</tt> rather than
<tt>FieldEditorPreferencePage</tt>.
</p>
<pre>
class ErrorPreferencePage
extends PreferencePage
implements IWorkbenchPreferencePage {
// ...
}
</pre>
<p align="center"><img src="errors_pref_page.jpg"></p>
<p align="center">The Errors Preference Page</p>
<p>
You must write a little more code to manage your field editors when not using a <tt>FieldEditorPreferencePage</tt>. In addition to
creating the preference store and instantiating field editors, you must write code to load preferences, store preferences,
and restore defaults. Without a <tt>FieldEditorPreferencePage</tt>, you can still take advantage of a field editor's
self-validation via the <tt>FieldEditor.isValid()</tt> method, but you must write the code that calls this method.
</p>
<p>
When using field editors with a regular preference page, you should instantiate them in the <tt>createContents(Composite)</tt> method,
just like
you would instantiate other items in the page layout. For example, here is
<tt>ErrorsPreferencePage.createContents(Composite)</tt>:
</p>
<pre>
protected Control createContents(Composite parent) {
Composite top = new Composite(parent, SWT.LEFT);
// Sets the layout data for the top composite's
// place in its parent's layout.
top.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
// Sets the layout for the top composite's
// children to populate.
top.setLayout(new GridLayout());
errors = new RadioGroupFieldEditor(
IPreferenceConstants.ERRORS_PREFERENCE,
"Error conditions",
1,
new String[][] {
{"Don't show errors",
IPreferenceConstants.NO_ERRORS
},
{"Show error if a closing tag is missing",
IPreferenceConstants.ERROR_FOR_MISSING_CLOSING_TAG
}
},
top,
true);
<img src="images/tag_1.gif" height=13 width=24 align=CENTER> errors.setPreferencePage(this);
<img src="images/tag_2.gif" height=13 width=24 align=CENTER> errors.setPreferenceStore(getPreferenceStore());
<img src="images/tag_3.gif" height=13 width=24 align=CENTER> errors.load();
Label listLabel = new Label(top, SWT.NONE);
listLabel.setText("Tags which do not require closing tags:");
exemptTagsList = new List(top, SWT.BORDER);
exemptTagsList.setItems(
HTMLEditorPlugin.getDefault().getExemptTagsPreference());
// ...
// The remainder of the code has been omitted for brevity.
// Download the zip file that accompanies this article
// for the complete code.
// ...
return top;
}
</pre>
<p>
There are a few extra lines of code that must be included when using a field editor with a regular preference page instead of a
<tt>FieldEditorPreferencePage</tt>. After instantiating the field editor, you must ensure that the field editor
knows its preference page and preference store (see <img src="images/tag_1.gif" height=13 width=24 align=CENTER>
and <img src="images/tag_2.gif" height=13 width=24 align=CENTER>). Otherwise, you can not take advantage of the field
editor's built-in methods for storing and loading the preference. You must also load the preference (see
<img src="images/tag_3.gif" height=13 width=24 align=CENTER>) so the current value will be displayed when the page is opened.</p>
<p>
Much of the <tt>createContents()</tt> method has been omitted from this example because it is quite long. The rest of the method
lays out the list,
buttons, and text field on the page. Using
<tt>FieldEditor</tt>s and <tt>FieldEditorPreferencePage</tt> whenever possible makes your preference page code cleaner and much
shorter. If you would like to compare the complete implementations of <tt>HTMLEditorPreferencePage</tt> and
<tt>ErrorsPreferencePage</tt>, please download the zip file referred to in the <a href="#conclusion">Conclusion</a> section.
</p>
<p>
Since we are using a regular preference page, we must also override <tt>performOk()</tt> and <tt>performDefaults()</tt>. Here we
take advantage of some of <tt>FieldEditor</tt>'s methods to make our code a little cleaner. Here are the methods from
<tt>ErrorsPreferencePage</tt>:
</p>
<pre>
protected void performDefaults() {
errors.loadDefault();
exemptTagsList.setItems(
HTMLEditorPlugin.getDefault().
getDefaultExemptTagsPreference());
// getDefaultExemptTagsPreference() is a convenience
// method which retrieves the default preference from
// the preference store.
super.performDefaults();
}
public boolean performOk() {
errors.store();
HTMLEditorPlugin.getDefault().
setExemptTagsPreference(exemptTagsList.getItems());
return super.performOk();
}
</pre>
<p>
Subclassing <tt>PreferencePage</tt> instead of <tt>FieldEditorPreferencePage</tt> requires more coding, but it allows you to mix
field editors with other controls necessary to display all the preferences.
If you would like to use a <tt>FieldEditorPreferencePage</tt> but there are no suitable field editors for the types of preferences
your page needs to show, you can always write your own field editors.
</p>
<h2 id="writing"><a name="writing"></a>Writing your own field editors</h2>
<p>
If none of the concrete <tt>FieldEditor</tt> subclasses provided by JFace are suitable for your needs, you can always write
your own type of field editor.
If you need a field editor just slightly different from an existing field editor subtype, you can subclass that type. In addition
to the concrete subclasses already discussed in this article, there are two abstract field editors which may be useful:
</p>
<ul>
<li>
<b><tt>StringButtonFieldEditor</tt></b> - This field editor can be subclassed to produce field editors that consist of a label,
a text field, and a button that performs some action. Both <tt>DirectoryFieldEditor</tt> and <tt>FileFieldEditor</tt> are
subclasses of this class.
</li>
<li>
<b><tt>ListEditor</tt></b> - This field editor can be subclassed to produce field editors that consist of a list of items and
a button bank. Three of the buttons ("Up", "Down", and "Remove") affect the selection of the list. The other button ("Add")
performs an action defined by subclasses to generate a new item for the list. <tt>PathEditor</tt> is a subclass of this class.
</li>
</ul>
<p>
If the field editor you need is not very similar to the subclasses provided by JFace, just subclass <tt>FieldEditor</tt>.
</p>
<p>
For example, we can implement a field editor to replace the list, buttons, and text field in <code>ErrorsPreferencePage</code>.
By doing so,
we can subclass
<tt>FieldEditorPreferencePage</tt> instead of <tt>PreferencePage</tt>. Let's write such a field editor and
re-implement <tt>ErrorsPreferencePage</tt> using it.
</p>
<p>
When subclassing <tt>FieldEditor</tt>, there are six abstract methods that must be implemented. You may override other
methods of <tt>FieldEditor</tt>, but this is usually not necessary.
First of all, you must implement <tt>doStore()</tt>, <tt>doLoad()</tt>, and <tt>doLoadDefault()</tt>. These are the methods
which translate a preference in a preference store to what the user sees, and back again. These methods are usually fairly easy to
implement. For example, here are the methods from <tt>AddRemoveListFieldEditor</tt>:
</p>
<pre>
protected void doLoad() {
String items = getPreferenceStore().getString(getPreferenceName());
setList(items);
}
protected void doLoadDefault() {
String items = getPreferenceStore().getDefaultString(getPreferenceName());
setList(items);
}
protected void doStore() {
String s = createListString(list.getItems());
if (s != null)
getPreferenceStore().setValue(getPreferenceName(), s);
}
</pre>
<p>
The above methods make use of two helper methods:
</p>
<ul>
<li> <tt>setList(String)</tt> parses a string representation of the list into
an array and sets the items in the list to be the elements of that array.
</li>
<li>
<tt>createListString(String[])</tt>
converts an array of list items into a single string
representation of the list.
</li>
</ul>
If you would like to see these helper methods or the full implementation
of the class, please download the zip file referred to in the <a href="#conclusion">Conclusion</a> section.
</p>
<p>
The other methods which need to be implemented involve the layout of the field editor. <tt>getNumberOfControls()</tt> is simple;
it just returns the highest number of controls that appear on one line in the field editor's parent composite.
For example, <tt>ListEditor</tt> contains two controls on one line (the list and the composite containing the buttons), so its
<tt>getNumberOfControls</tt> method returns 2. <tt>adjustForNumColumns(int)</tt> must ensure that the field editor is displayed
correctly if the number of columns in the parent <tt>Composite</tt> changes after this field editor has been laid out.
<tt>doFillIntoGrid(Composite, int)</tt> initially lays out the field editor in the given parent <tt>Composite</tt>. Here are
these methods from <tt>AddRemoveListFieldEditor</tt>:
</p>
<pre>
public int getNumberOfControls() {
// The button composite and the text field.
return 2;
}
protected void adjustForNumColumns(int numColumns) {
((GridData)top.getLayoutData()).horizontalSpan = numColumns;
}
protected void doFillIntoGrid(Composite parent, int numColumns) {
top = parent;
GridData gd = new GridData(GridData.FILL_HORIZONTAL);
gd.horizontalSpan = numColumns;
top.setLayoutData(gd);
Label label = getLabelControl(top);
GridData labelData = new GridData();
labelData.horizontalSpan = numColumns;
label.setLayoutData(labelData);
list = new List(top, SWT.BORDER);
// Create a grid data that takes up the extra
// space in the dialog and spans both columns.
GridData listData = new GridData(GridData.FILL_HORIZONTAL);
listData.heightHint =
convertVerticalDLUsToPixels(list, LIST_HEIGHT_IN_DLUS);
listData.horizontalSpan = numColumns;
list.setLayoutData(listData);
list.addSelectionListener(new SelectionAdapter() {
public void widgetSelected(SelectionEvent e) {
selectionChanged();
}
});
// Create a composite for the add and remove
// buttons and the input text field.
Composite addRemoveGroup = new Composite(top, SWT.NONE);
GridData addRemoveData = new GridData(GridData.FILL_HORIZONTAL);
addRemoveData.horizontalSpan = numColumns;
addRemoveGroup.setLayoutData(addRemoveData);
// ...
}
</pre>
<p>
The <tt>doFillIntoGrid(Composite, int)</tt> method is quite long, so most of it has been omitted above. To view the full method,
download the zip file referred to in the <a href="#conclusion">Conclusion</a> section.
</p>
<p>
Now that we have a field editor that stores a list of strings and allows the user to add items and remove them,
the implementation of our error preference page becomes trivial. Since we have already learned how to subclass
<tt>FieldEditorPreferencePage</tt>,
the new implementation isn't shown here. It can be found in the file
<tt>ErrorPreferencePage2.java</tt> in the zip file referred to in the <a href="#conclusion">Conclusion</a> section.
</p>
<h2 id="conclusion"><a name="conclusion"></a>Conclusion</h2>
<p>
This article has familiarized you with the different types of field editors and demonstrated their use. It has explained
how field editors work so you can safely write your own. We encourage the use of
field editors, and hope the knowledge provided by this article will help you easily implement cleaner preference
pages for your plugins.
</p>
<p>
To run the example or view the source code for this article unzip <a href="field_editors.zip">field_editors.zip</a>
into your <i>plugins/ </i>subdirectory.
</p>
</body>
</html>