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

Scott Palmer swpalmer at gmail.com
Wed Jun 11 16:14:19 UTC 2014


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> 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> 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> 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