Formatted text field API (RT-14000, RT-30881)

Martin Sladecek martin.sladecek at oracle.com
Wed Jun 11 16:38:04 UTC 2014


I already posted a comment in JIRA (RT-30881) that we should make the 
value writable.
Anyhow, we should move the discussion to JIRA to keep everything at one 
place.

Thanks,
-Martin
On 11.6.2014 18:14, Scott Palmer wrote:
> I already have a use for a bi-directional binding.  I have a UI that 
> shows properties of a component. The component's properties can be 
> updated via the UI, and also by other events from background processes 
> that are running, or even just as a reaction to some other property 
> changing.  So I need the binding from the model to the control to be 
> bi-directional. The UI view is both read/write.  The properties can be 
> numbers, rational numbers expressed as fractions ##/###, times 
> HH:MM:SS, enum values, etc.
>
> (My app has some complex requirements so in actual fact a simple 
> binding probably isn't enough.  But in principle it could be.)
>
> Or just think of a spreadsheet in Google docs being edited by two 
> people on a network... I want to be able to update a cell, and I want 
> to see when someone else updates it.  Two different read/write fields 
> connected to the same model.
>
> I just thought of something else. Can this be easily used as the field 
> for a editable ComboBox?
>
> Scott
>
>
> On Wed, Jun 11, 2014 at 10:08 AM, Martin Sladecek 
> <martin.sladecek at oracle.com <mailto:martin.sladecek at oracle.com>> wrote:
>
>     Although I'm not sure the second one can be considered a use-case,
>     I agree that somebody might want uneditable field with some format
>     (and it's up to the developer to make it uneditable), so I'll make
>     the value rw.
>
>     -Martin
>
>     On 11.6.2014 16:03, Scott Palmer wrote:
>
>         There are two cases for this:
>         - a regular binding could be used with a read-only
>         FormattedTextField
>         for cases where you want the text representation shown in the
>         UI and
>         have it selectable and readable for copy/paste.
>         - a bi-directional binding should "just work"
>
>         Scott
>
>         On Wed, Jun 11, 2014 at 9:59 AM, Martin Sladecek
>         <martin.sladecek at oracle.com
>         <mailto:martin.sladecek at oracle.com>> wrote:
>
>             Binding the property would mean the text field can be
>             edited (the field is
>             still editable), but it's content cannot be committed (by
>             either pressing
>             ENTER or focusing out of the field), so it's content is
>             practically
>             irrelevant.
>             If you think there's a reasonable use case for this, there
>             should be no
>             problem with having writable value.
>
>             -Martin
>
>
>             On 11.6.2014 15:53, Scott Palmer wrote:
>
>                 In FormattedTextField why is the value property
>                 returned as a
>                 read-only property?
>                 This control should allow bi-directional bindings to
>                 the value property.
>
>                 Scott
>
>                 On Wed, Jun 11, 2014 at 4:50 AM, Martin Sladecek
>                 <martin.sladecek at oracle.com
>                 <mailto:martin.sladecek at oracle.com>> wrote:
>
>                     Hello,
>                     I would like to start some discussion about
>                     formatted text field API. The
>                     related JIRA issues are RT-14000 (formatted text
>                     field) and RT-30881
>                     (content filter).
>
>                     The RT-30881 defines a content filter for all text
>                     input controls (in
>                     TextInputControl class), like TextField and
>                     TextArea. This was originally
>                     implemented by Richard Bair and briefly discussed
>                     here:
>
>                     http://mail.openjdk.java.net/pipermail/openjfx-dev/2012-December/004687.html.
>                     I've tried to build formatted text field on top of
>                     that (and made some
>                     changes to the API) since content filtering is
>                     something most formatted
>                     text
>                     fields will use.
>
>                     First, the TextInputControl additions:
>
>                     * contentFilter property of type
>                     ObjectProperty<ContentFilter>
>
>                     So let's look at the content filter and content
>                     change (both nested
>                     classes
>                     of TextInputControl):
>
>                         /**
>                            * Content Filter specifies the filter to be
>                     used with {@link
>                     TextInputControl#contentFilterProperty()}.
>                            * It allow user to intercept and modify any
>                     change done to the text
>                     content.
>                            * To avoid content that's not valid for the
>                     filter, it's possible
>                     to
>                     assign a default value supplier for the filter.
>                            * <p>
>                            * The filter itself is an {@code
>                     UnaryOperator} that accepts {@link
>                     javafx.scene.control.TextInputControl.ContentChange}
>                     object.
>                            * It should return a {@link
>                     javafx.scene.control.TextInputControl.ContentChange}
>                     object that contains
>                     the actual (filtered)
>                            * change. Returning null rejects the change.
>                            * <p>
>                            * If default value supplier is provided, it
>                     is used when the {@code
>                     ContentFilter} is assigned to a {@code
>                     TextInputControl}
>                            * and it's current text is invalid. It is
>                     expected that the
>                     provided
>                     default value is accepted by the filtering operator.
>                            */
>                           public static class ContentFilter {
>
>                               /**
>                                * Creates a new filter with the
>                     providing filtering operator.
>                                * @param filter the filtering operator
>                                *
>                                * @throws
>                     java.lang.NullPointerException if filter is null
>                                */
>                               public
>                     ContentFilter(UnaryOperator<ContentChange> filter) {}
>
>                               /**
>                                * Creates a new filter with the
>                     providing filtering operator
>                     and
>                     default value supplier.
>                                * @param filter the filtering operator
>                                * @param defaultValue the default value
>                     or null
>                                *
>                                * @throws
>                     java.lang.NullPointerException if filter is null
>                                */
>                               public
>                     ContentFilter(UnaryOperator<ContentChange> filter,
>                     Supplier<String> defaultValue) {}
>
>                               /**
>                                * The filtering operator of this filter.
>                                * @return the operator
>                                */
>                               public UnaryOperator<ContentChange>
>                     getFilter() {}
>
>                               /**
>                                * The default value provider of this filter
>                                * @return the default value provider or
>                     null
>                                */
>                               public Supplier<String> getDefaultValue() {}
>
>                               /**
>                                * Chains this filter with a filtering
>                     operator. The other
>                     filtering
>                     operator is used only if this
>                                * filter's operator rejects the
>                     operation. The default value of
>                     the
>                     new {@code ContentFilter} is the same
>                                * as of this filter.
>                                * @param other the filtering operator
>                     to chain
>                                * @return a new ContentFilter as
>                     described above
>                                */
>                               public ContentFilter
>                     orApply(UnaryOperator<ContentChange> other)
>                     {}
>
>                               /**
>                                * Chains this filter with a filtering
>                     operator of another
>                     {@code
>                     ContentFilter}. The other filtering operator is
>                     used only if this
>                                * filter's operator rejects the
>                     operation. The default value of
>                     the
>                     new {@code ContentFilter} is the same
>                                * as of this filter.
>                                * @param other the filter to chain
>                                * @return a new ContentFilter as
>                     described above
>                                */
>                               public ContentFilter
>                     orApply(ContentFilter other) {}
>
>                           }
>
>                     /**
>                            * Contains the state representing a change
>                     in the content for a
>                            * TextInputControl. This object is passed
>                     to any registered
>                            * {@code contentFilter} on the
>                     TextInputControl whenever the text
>                            * for the TextInputControl is modified.
>                            * <p>
>                            *     This class contains state and
>                     convenience methods for
>                     determining
>                     what
>                            *     change occurred on the control. It
>                     also has a reference to
>                     the
>                            *     TextInputControl itself so that the
>                     developer may query any
>                     other
>                            *     state on the control. Note that you
>                     should never modify the
>                     state
>                            *     of the control directly from within
>                     the contentFilter
>                     handler.
>                            * </p>
>                            * <p>
>                            *     The ContentChange is mutable, but not
>                     observable. It should
>                     be
>                     used
>                            *     only for the life of a single change.
>                     It is intended that the
>                            *     ContentChange will be modified from
>                     within the contentFilter.
>                            * </p>
>                            */
>                           public static final class ContentChange
>                     implements Cloneable{
>
>                               /**
>                                * Gets the control associated with this
>                     change.
>                                * @return The control associated with
>                     this change. This will
>                     never
>                     be null.
>                                */
>                               public final TextInputControl
>                     getControl() {}
>
>                               /**
>                                * Gets the start index into the {@link
>                     javafx.scene.control.TextInputControl#getText()}
>                                * for the modification. This will
>                     always be a value > 0 and
>                                * <= {@link
>                     javafx.scene.control.TextInputControl#getLength()}.
>                                *
>                                * @return The start index
>                                */
>                               public final int getStart() {}
>
>                               /**
>                                * Sets the start index for the range to
>                     be modified. This value
>                     must
>                                * always be a value > 0 and <= {@link
>                     javafx.scene.control.TextInputControl#getLength()}
>                                * <p>
>                                * <b>Note</b> that setting start before
>                     the current end will
>                     also
>                     change end to the same value.
>                                *
>                                * @param value the new start index
>                                */
>                               public final void setStart(int value) {}
>
>                               /**
>                                * Gets the end index into the {@link
>                     javafx.scene.control.TextInputControl#getText()}
>                                * for the modification. This will
>                     always be a value > {@link
>                     #getStart()} and
>                                * <= {@link
>                     javafx.scene.control.TextInputControl#getLength()}.
>                                *
>                                * @return The end index
>                                */
>                               public final int getEnd() {}
>
>                               /**
>                                * Sets the end index for the range to
>                     be modified. This value
>                     must
>                                * always be a value > {@link
>                     #getStart()} and <= {@link
>                     javafx.scene.control.TextInputControl#getLength()}.
>                                * Note that there is an order
>                     dependency between modifying the
>                     start
>                                * and end values. They must always be
>                     modified in order such
>                     that
>                     they
>                                * do not violate their constraints.
>                                *
>                                * @param value The new end index
>                                */
>                               public final void setEnd(int value) {}
>
>                               /**
>                                * A convenience method returning an
>                     IndexRange representing the
>                                * start and end values.
>                                *
>                                * @return a non-null IndexRange
>                     representing the range from
>                     start
>                     to end.
>                                */
>                               public final IndexRange getRange() {}
>
>                               /**
>                                * A convenience method assigning both
>                     the start and end values
>                                * together, in such a way as to ensure
>                     they are valid with
>                     respect
>                     to
>                                * each other. One way to use this
>                     method is to set the range
>                     like
>                     this:
>                                * {@code
>                                *    
>                     change.setRange(IndexRange.normalize(newStart,
>                     newEnd));
>                                * }
>                                *
>                                * @param value The new range. Cannot be
>                     null.
>                                */
>                               public final void setRange(IndexRange
>                     value) {}
>
>                               /**
>                                * A convenience method assigning both
>                     the start and end values
>                                * together, in such a way as to ensure
>                     they are valid with
>                     respect
>                     to
>                                * each other. The start must be less
>                     than or equal to the end.
>                                *
>                                * @param start The new start value.
>                     Must be a valid start value
>                                * @param end The new end value. Must be
>                     a valid end value
>                                */
>                               public final void setRange(int start,
>                     int end) {}
>
>                               /**
>                                * Gets the new anchor. This value will
>                     always be > 0 and
>                                * <= {@link
>                     #getProposedControlText()}{@code}.getLength()}
>                                *
>                                * @return The new anchor position
>                                */
>                               public final int getNewAnchor() {}
>
>                               /**
>                                * Sets the new anchor. This value must
>                     be > 0 and
>                                * <= {@link
>                     #getProposedControlText()}{@code}.getLength()}.
>                     Note
>                     that there
>                                * is an order dependence here, in that
>                     the anchor should be
>                                * specified after the new text has been
>                     specified.
>                                *
>                                * @param value The new anchor position
>                                */
>                               public final void setNewAnchor(int value) {}
>
>                               /**
>                                * Gets the new caret position. This
>                     value will always be > 0
>                     and
>                                * <= {@link
>                     #getProposedControlText()}{@code}.getLength()}
>                                *
>                                * @return The new caret position
>                                */
>                               public final int getNewCaretPosition() {}
>
>                               /**
>                                * Sets the new caret position. This
>                     value must be > 0 and
>                                * <= {@link
>                     #getProposedControlText()}{@code}.getLength()}.
>                     Note
>                     that there
>                                * is an order dependence here, in that
>                     the caret position
>                     should be
>                                * specified after the new text has been
>                     specified.
>                                *
>                                * @param value The new caret position
>                                */
>                               public final void
>                     setNewCaretPosition(int value) {}
>
>                               /**
>                                * Gets the text used in this change.
>                     For example, this may be
>                     new
>                                * text being added, or text which is
>                     replacing all the
>                     control's
>                     text
>                                * within the range of start and end.
>                     Typically it is an empty
>                     string
>                                * only for cases where the range is
>                     being deleted.
>                                *
>                                * @return The text involved in this
>                     change. This will never be
>                     null.
>                                */
>                               public final String getText() {}
>
>                               /**
>                                * Sets the text to use in this change.
>                     This is used to replace
>                     the
>                                * range from start to end, if such a
>                     range exists, or to insert
>                     text
>                                * at the position represented by start
>                     == end.
>                                *
>                                * @param value The text. This cannot be
>                     null.
>                                */
>                               public final void setText(String value) {}
>
>                               /**
>                                * Gets the complete new text which will
>                     be used on the control
>                     after
>                                * this change. Note that some controls
>                     (such as TextField) may
>                     do
>                     further
>                                * filtering after the change is made
>                     (such as stripping out
>                     newlines)
>                                * such that you cannot assume that the
>                     newText will be exactly
>                     the
>                     same
>                                * as what is finally set as the content
>                     on the control, however
>                     it
>                     is
>                                * correct to assume that this is the
>                     case for the purpose of
>                     computing
>                                * the new caret position and new anchor
>                     position (as those
>                     values
>                     supplied
>                                * will be modified as necessary after
>                     the control has stripped
>                     any
>                                * additional characters that the
>                     control might strip).
>                                *
>                                * @return The controls proposed new
>                     text at the time of this
>                     call,
>                     according
>                                *         to the state set for start,
>                     end, and text properties
>                     on
>                     this ContentChange object.
>                                */
>                               public final String
>                     getProposedControlText() {}
>
>                               /**
>                                * Gets whether this change was in
>                     response to text being added.
>                     Note that
>                                * after the ContentChange object is
>                     modified by the
>                     contentFilter
>                     (by one
>                                * of the setters) the return value of
>                     this method is not
>                     altered.
>                     It answers
>                                * as to whether this change was fired
>                     as a result of text being
>                     added,
>                                * not whether text will end up being
>                     added in the end.
>                                *
>                                * <p>
>                                * Text may have been added either cause
>                     it was {@link
>                     #isInserted()},
>                                * {@link #isAppended()}, or {@link
>                     #isPrepended()}.
>                                * </p>
>                                *
>                                * @return true if text was being added
>                                */
>                               public final boolean isAdded() {}
>
>                               /**
>                                * Gets whether this change was in
>                     response to text being
>                     deleted.
>                     Note that
>                                * after the ContentChange object is
>                     modified by the
>                     contentFilter
>                     (by one
>                                * of the setters) the return value of
>                     this method is not
>                     altered.
>                     It answers
>                                * as to whether this change was fired
>                     as a result of text being
>                     deleted,
>                                * not whether text will end up being
>                     deleted in the end.
>                                *
>                                * @return true if text was being deleted
>                                */
>                               public final boolean isDeleted() {}
>
>                               /**
>                                * Gets whether this change was in
>                     response to text being added,
>                     and
>                     in
>                                * particular, inserted into the midst
>                     of the control text. If
>                     this
>                     is
>                                * true, then {@link #isAdded()} will
>                     return true.
>                                *
>                                * @return true if text was being inserted
>                                */
>                               public final boolean isInserted() {}
>
>                               /**
>                                * Gets whether this change was in
>                     response to text being added,
>                     and
>                     in
>                                * particular, appended to the end of
>                     the control text. If this
>                     is
>                                * true, then {@link #isAdded()} will
>                     return true.
>                                *
>                                * @return true if text was being appended
>                                */
>                               public final boolean isAppended() {}
>
>                               /**
>                                * Gets whether this change was in
>                     response to text being added,
>                     and
>                     in
>                                * particular, prepended at the start of
>                     the control text. If
>                     this
>                     is
>                                * true, then {@link #isAdded()} will
>                     return true.
>                                *
>                                * @return true if text was being prepended
>                                */
>                               public final boolean isPrepended() {}
>
>                               /**
>                                * Gets whether this change was in
>                     response to text being
>                     replaced.
>                     Note that
>                                * after the ContentChange object is
>                     modified by the
>                     contentFilter
>                     (by one
>                                * of the setters) the return value of
>                     this method is not
>                     altered.
>                     It answers
>                                * as to whether this change was fired
>                     as a result of text being
>                     replaced,
>                                * not whether text will end up being
>                     replaced in the end.
>                                *
>                                * @return true if text was being replaced
>                                */
>                               public final boolean isReplaced() {}
>
>                               @Override
>                               public ContentChange clone() {}
>                     }
>
>
>
>                     The new FormattedTextField class relies on the
>                     content filtering and adds
>                     a
>                     concept of values convertible to/from a text:
>
>                     **
>                        * FormattedTextField is a special kind of
>                     TextField that handles
>                     conversion
>                     between a value of type {@code T} and
>                        * a text of this TextField.
>                        * <p>
>                        * There are two types of converters:
>                        * <ul>
>                        *     <li>{@link #valueConverterProperty()}
>                     represents the default
>                     converter between the text and values</li>
>                        *     <li>{@link #editConverterProperty()} is a
>                     special converter that
>                     takes precedence when the textfield is being edited.
>                        *     This allows to have a edit-friendly
>                     format and a nice display
>                     format
>                     for the value</li>
>                        * </ul>
>                        *
>                        * <p>
>                        * When editing the text field, the value is not
>                     updated until either
>                     {@code
>                     ENTER} key is pressed or focus is lost.
>                        * If the conversion fail, the last known valid
>                     value is used instead.
>                        *
>                        * @param <T> the value type
>                        */
>                     public final class FormattedTextField<T> extends
>                     TextField{
>
>                           /**
>                            * Creates a formatted text field with the
>                     specified converter and a
>                     null value.
>                            * @param valueConverter the value converter
>                            */
>                           public FormattedTextField(StringConverter<T>
>                     valueConverter) {}
>
>                           /**
>                            * Creates a formatted text field with the
>                     specified converter and a
>                     value
>                            * @param valueConverter the value converter
>                            * @param value the initial value
>                            */
>                           public FormattedTextField(StringConverter<T>
>                     valueConverter, T
>                     value) {}
>
>                           /**
>                            * This represents the current value of the
>                     formatted text field. If
>                     a
>                     {@link #valueConverterProperty()} is provided,
>                            * and the text field is not being edited,
>                     the value is a
>                     representation
>                     of the text in the text field.
>                            */
>                           public final ReadOnlyObjectProperty<T>
>                     valueProperty() {}
>                           public final void setValue(T value) {}
>                           public final T getValue() {}
>
>                           /**
>                            * The default converter between the values
>                     and text.
>                            * Note that changing the converter might
>                     lead to a change of value,
>                     but
>                     only if the text can be converted by the new
>                            * converter. Otherwise, the current value
>                     is converted to a text,
>                     which
>                     is set to the field.
>                            * @see #editConverterProperty()
>                            */
>                           public final ObjectProperty<StringConverter<T>>
>                     valueConverterProperty()
>                     {}
>                           public final void
>                     setValueConverter(StringConverter<T> converter) {}
>                           public final StringConverter<T>
>                     getValueConverter() {}
>
>                           /**
>                            * Converter between values and text when
>                     the field is being edited.
>                            * @see #valueConverterProperty()
>                            */
>                           public final ObjectProperty<StringConverter<T>>
>                     editConverterProperty()
>                     {}
>                           public final void
>                     setEditConverter(StringConverter<T> converter) {}
>                           public final StringConverter<T>
>                     getEditConverter() {}
>
>                     }
>
>                     You can find the whole patch here:
>
>                     https://javafx-jira.kenai.com/secure/attachment/44678/rt-30881_14000_proposal.patch
>                     There are some examples for content filtering in
>                     RT-30881. I'll attach
>                     some
>                     formatted text field samples soon.
>
>                     Thanks,
>                     -Martin
>
>
>



More information about the openjfx-dev mailing list