Project Notes
Project: dbSwing: JTextPane Sample
Author: JBuilder Team
Company: borland.com
Description:
This application uses Swing JInternalFrames to display product descriptions from
the IntlDemo sample application. The descriptions are stored in a data set
and displayed be a JdbTextPane. In addition to standard editing, font sizes
and styles can be set on blocks of text.
What this sample demonstrates
Creating a multiple document interface with JDesktopPane and JInternalFrame
Implementing a floating toolbar that detects the active data set automatically
Displaying images from a data set in a JdbStatusLabel
Loading text from a resource file into a data set
Editing text in a JdbTextPane
Creating a toolbar of text-editing actions
Other design notes
Setup
This sample requires that you have JDataStore installed and read/write access to the sample
IntlDemo.jds JDataStore database in the samples/JDataStore/datastores directory. The sample
tries to dynamically determine the proper URL to IntlDemo.jds, but since the location is
dependent upon the location of your JBuilder installation, it might not always succeed. It
writes a message indicating the URL which it is trying to use. If you are unable to run
or design the sample, you should explicitly specify the URL to IntlDemo.jds correct for
your installation in TextPaneDesktop.java.
If you have not installed JDataStore, please install it before
running this sample. Follow the instructions on
Installing JDataStore in Developing Database Applications.
Creating a multiple document interface with JDesktopPane and
JInternalFrame
Swing's JDesktopPane and JInternalFrame classes make it easy to create an
application in which you can work with several "windows" of data on a "desktop".
JDesktopPane provides the "desktop". When instances of JInternalFrame are added to a
JDesktopPane, each one can be moved, minimized, resized, and maximized independently.
This is an MDI (multiple document interface) application.
To create the skeleton of an MDI application in JBuilder, use the Desktop Pane and
Internal Frame snippets on the first tab of the File | New gallery. Here,
TextPaneDesktop.java, created with the Desktop Pane snippet, is our runnable file. It
opens as an empty "desktop". Open a "window" (really an internal frame) that displays
product descriptions by selecting a language from the menu. The code for these internal
frames is in TextPaneInternalFrame.java, which began as an Internal Frame snippet.
Because this is an MDI application, you can open several frames for different languages
and move or resize each one as you like. Each frame has its own data set of product
information, with text in the selected language.
Internal frames can be layered, but we put all our product information frames in the same
layer, so the one that has focus is always on top. We do take advantage of layering
when we create our toolbar, however. This is described in the next section.
Implementing a floating toolbar that detects the active data
set automatically
When working with product information in this application, it's convenient to have
a navigation toolbar. To save space, we'd like to have just one toolbar even when
two or more product information frames are open. We accomplish this by defining
another JInternalFrame class, TextPaneToolBar.java, that contains only a JdbNavToolBar.
We create just one instance of this class when the first product information frame
is opened, and we don't allow it to be resized or closed. We put the toolbar's frame
in a higher layer than the product information frames, so it stays on top of them.
By default, a toolbar's floatable property is true, allowing it to be dragged to a new
position in its layout (if the layout can accept it) or floated in its own window.
Our toolbar is floatable, but there's nowhere in our application to dock it, so there
are only two choices: leave it in its internal frame, or float it in a separate window.
When not floated, the toolbar must stay within the desktop, like any internal frame;
when floated, it can be placed anywhere but is not guaranteed to stay on top of the
desktop or any other window. To float the toolbar, place the mouse over a spot on the
toolbar that's outside of any button and drag. To return a floating toolbar to its
frame, close it.
When the toolbar is floated, its internal frame becomes empty. We watch its ancestorRemoved
event to know when it is floated/docked so we can make the internal frame invisible
when it's empty.
By default, a toolbar automatically detects the data set of the active data-aware component.
This is what we want: each product information frame has its own data set, and we want
the toolbar to navigate the data set of the frame that has focus. We just help it
out in one small way: when a frame itself - not a data-aware component on the frame -
gets focus, we pass the focus on to the JdbTextPane on the frame. This causes the
toolbar to bind to the frame's data set. We treat JdbStatusLabels differently, however.
We want each frame's status label to report only on that frame's data set, so we set the
status label's dataSet property explicitly.
Displaying images from a data set in a JdbLabel
Like Swing's JLabel, JdbLabel can display both text and images. Being data-aware, JdbLabel
can get its data from a data set. To support images, it has two extra properties,
columnNameIcon and iconEditable. These properties have the same meaning for the
JdbLabel's icon as the standard columnName and editable properties do for its text
value. For an image, "editable" means that you can double-click to open a file chooser
dialog and use it to load a different file into the JdbLabel. In this application, we
use a JdbLabel called itemImageJdbLbl in TextPaneInternalFrame.java to display images
of product. It's not editable by default, but you can set its iconEditable property
to true if you want to experiment.
Loading text from a resource file into a data set
IntlDemo stores text in resource files - one resource file for each supported
national language. Each product's Item Number and Description form a key/value
pair in the resource file. In this application, we create a small data set containing
Item Number, Image, and Description information. We query the PRODUCTS table in intldemo.gdb
for item numbers (from the "SKU" field) and images, then fill in the corresponding
descriptions from the resource file for the desired language. The descriptions are
plain text in the resource file, but we want to turn them into styled text, so we
have to do a little work as we load them into the data set. This is explained in the next
section.
Because this is a sample application and we want to show off JdbTextPane, we allow the
descriptions to be edited. But we've disabled insertion and deletion of rows and removed
the Save and Refresh buttons, among others, from the toolbar because we don't have any code
to write modified product descriptions back to the resource file.
This sample uses resource files from the IntlDemo application - not copies of them,
but the same files, from the samples\com\samples\borland\intl\applicaton\resources directory.
We include them all in the project so they'll be compiled (for .java files) or
copied to the myclasses directory (for .properties files) when we make the project.
If the product descriptions are blank, IntlDemo's TextRes.java resource file probably
was not found. If the descriptions are in English regardless what language is chosen,
TextRes.java was found, but not the language-specific properties files such as
TextRes_fr.properties.
Editing text in a JdbTextPane
Once we have product descriptions loaded from the resource files, we can look at
JdbTextPane's capabilities. Please keep in mind here that this is only
a sample, not a real application. The scenario is that we start with plain text and
store it in a data set where we have the ability to set fonts, sizes, colors, and attributes
like bold and italic on blocks of text. We can do all that in this sample, but we
have no provision to save the styled text when we close the application. In a more
realistic case, we'd save the data set - in a server table or a JDataStore, for instance -
when the application shuts down. (JDataStore is not available in all versions of JBuilder.)
First there is the matter of loading the plain text into the data set. Because we want
to be able to style the text, we have to use a column whose data type is Object. The
objects we put in this column will be handled by a Swing-based text component, so they
should be Swing document models. Specifically, for styled text, they should be
DefaultStyledDocuments. In TextPaneDesktop's internalFrameSetup() method we instantiate
a DefaultStyledDocument for each product description, put the description in it, and
load it into our data set.
Right-click in the text pane for a menu of text operations. In addition to
standard edit operations such as cut, copy, and paste, you can set font and color properties
on the selected text and undo and redo changes. dbSwing's menus for text components
vary according to the component, its editability, its data-awareness, and the class
of its document model.
It's the font and color properties we're mostly interested in. They apply to the selected
block of text, not to the component as a whole, so you can use as many fonts and colors
as you like within a product description. Background color is saved in the document
but not displayed in the component. Our text pane is bound to a data set, so style
properties are saved in the data set - if you navigate off a row and then back to it,
they're still there.
By default, JdbTextPane's popup menu allows you to load data from a text file or
a Java serialized file and to save to the same formats. If you save styled text
to a text file, only plain text is written out and the styling is lost. Unlike
JdbEditorPane, JdbTextPane cannot open and save RTF and HTML files. JdbTextPane is
a good choice if you plan to keep styled text in a data set and display and edit it
through Java, but not a good choice if you want to output to other file formats.
For this reason, we have disabled the Open and Save menu commands in this application.
Creating a toolbar of text-editing actions
Operations on JdbTextPane's menu are implemented by actions, defined
either in DBTextDataBinder or in JTextComponent. In Swing, actions can easily be
added to a menu or toolbar. Those in DBTextDataBinder are especially easy to use
because they're defined as public constants and have icons. We've put some
of the most useful menu actions on our toolbar. The undo, redo, font dialog, and foreground
color dialog buttons on our toolbar are from DBTextDataBinder. The others are
JTextComponent actions.
Actions can't be added to TextPaneToolBar.java through JBuilder's UI Designer.
However, the code is easy to write. We add an action to the toolbar, get back a button,
set properties on that button, then reuse the button object for the next action. The
file can still be opened in the UI Designer, but the added buttons aren't visible
until we run the application.
System and local fonts are displayed in the font dialog. The toolbar buttons that set
bold and italic attributes will work for system fonts only, but you can get the same
effect by choosing the bold or italic version of the current font, if there is one,
from the font dialog.
Other design notes
Menus: The simple menus in this application were constructed using
the JBuilder Menu Designer. If you have more complex menus and will be translating
your application, you might want to use dbSwing menu components such as IntlMenu and
IntlMenuItem. These components offer the textWithMnemonic property, which allows a
translator to see the context for a mnemonic while translating it. There are two
ways to work with dbSwing menu components in the Menu Designer:
- Build the menu with Swing menu components, then edit the the code and change the
components to dbSwing components.
- Build the menu by selecting dbSwing menu components with the Bean Chooser and
dropping them into the Component Tree.
In either case, you will be able to view your menu in the Menu Designer and set properties
in the Inspector. You can even do a few operatons (for example, adding separators)
in the Menu Designer. However, any operation that changes a menu object's class, such as
turning a regular menu item into a checkable one, will create a Swing object.
Designability: Using JDesktopPane and JInternalFrame to build an MDI
application reduces the amount of development we can do through JBuilder's UI Designer
a little. All of our classes are designable; however, some significant code in each
was written manually:
- TextPaneDesktop: The code to build a data set for each product information
frame and to open both the toolbar frame and the product information frames was
written by hand.
- TextPaneInternalFrame gets its data set as a parameter, so the Designer doesn't
know what columns it contains. As a result, the components' dataSet and column
properties are set through code in the constructor, after jbInit is called.
- In TextPaneToolBar, the text edit buttons are added to the toolbar manually.
dbSwing tools: Three useful and easy-to-use dbSwing components are used
throughout the dbSwing samples:
-
DBExceptionHandler displays the message and stack trace for any exception
that occurs throughout this application. We dropped an instance of it from the palette
into TextPaneDesktop.java, but this is really only necessary if we want to set its properties.
DBExceptionHandler's handleException() method is static, so we can call it from
TextPaneInteralFrame.java and TextPaneToolBar.java without having instances of it
defined in those files.
-
DBEventMonitor writes information about data set evens to System.out.
This is helpful in understanding data set events and when debugging. To use DBEventMonitor,
set its dataAwareComponentContainer property to "this" or to another container of
components to be monitored. If there's too much output, set properties to monitor
fewer event types.
-
DBDisposeMonitor sets dbSwing components' dataSet property to null
when their windows are closed or disposed of so that they can be garbage collected.
If a window contains a data store, DBDisposeMonitor also closes it. This may be where
DBDisposeMonitor's is most valuable, because a data store requires additional checks
when re-opened if it was not closed cleanly. To use DBDisposeMonitor, set its
dataAwareComponentContainer property to "this" or to another container.