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