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.slade...@oracle.com <mailto:martin.slade...@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.slade...@oracle.com
<mailto:martin.slade...@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.slade...@oracle.com
<mailto:martin.slade...@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