Custom JSpinner Model not working - java

I tried to implement my own JSpinner model to accept an enumeration (including I18N), so I did like that:
searchSpinner.setModel(new AbstractSpinnerModel() {
int index = 0;
int minIndex = 0;
int maxIndex = MY_ENUM.values().length - 1;
Object selected = MY_ENUM.values()[index];
#Override
public Object getValue() {
return selected;
}
#Override
public void setValue(Object value) {
selected = value;
fireStateChanged();
}
#Override
public Object getNextValue() {
if (index < maxIndex) {
index++;
}
fireStateChanged();
return MY_ENUM.values()[index];
}
#Override
public Object getPreviousValue() {
if (index > minIndex) {
index--;
}
fireStateChanged();
return MY_ENUM.values()[index];
}
#Override
public void addChangeListener(ChangeListener l) {
}
#Override
public void removeChangeListener(ChangeListener l) {
}
});
The problem is that did not work, and even the spinner list looks like disabled. What am I doing wrong?
UPDATE: Based on first answer

You should extend from AbstractSpinnerModel (note to folks new to his question -- note that his original question had the class implementing the SpinnerModel interface. He later changed his code to reflect my recommendation) and be sure to call the fireStateChanged() method when appropriately. Also you've not taken into account edge cases and beyond edge cases.
e.g.,
import javax.swing.*;
import javax.swing.JSpinner.DefaultEditor;
public class MySpinnerPanel extends JPanel {
public static void main(String[] args) {
JSpinner spinner = new JSpinner(new MyEnumSpinnerModel());
JSpinner.DefaultEditor editor = (DefaultEditor) spinner.getEditor();
editor.getTextField().setColumns(5);
JPanel panel = new JPanel();
panel.add(spinner);
JOptionPane.showMessageDialog(null, panel);
}
}
enum MyEnum {
FE, FI, FO, FUM, FOO, FUBAR, SPAM
}
class MyEnumSpinnerModel extends AbstractSpinnerModel {
private int index = 0;
#Override
public Object getValue() {
return MyEnum.values()[index];
}
#Override
public void setValue(Object value) {
if (value instanceof MyEnum) {
index = ((MyEnum) value).ordinal();
fireStateChanged();
} else {
String text = value.toString() + " is not a valid enum item";
throw new IllegalArgumentException(text);
}
}
#Override
public Object getNextValue() {
if (index >= MyEnum.values().length - 1) {
return null;
} else {
return MyEnum.values()[index + 1];
}
}
#Override
public Object getPreviousValue() {
if (index <= 0) {
return null;
} else {
return MyEnum.values()[index - 1 ];
}
}
}
Edit
Note that the model itself should not require a listener to notify the view (as per the other answer to this question) as that's what the AbstractSpinnerModel does internally. It's fireStateChange() method is what the model itself should call to trigger this notification, same as most all other similar model structures in Swing such as any TableModel object that you create that derives from the AbstractTableModel. For details, please see the source code for the SpinnerListModel. Your code should emulate this class.

You should use ChangeListener to notify the view of changes in the model.
spinner = new JSpinner(new SpinnerModel() {
private ChangeListener l;
#Override
public void setValue(Object value) {
...
if(l != null) {
l.stateChanged(new ChangeEvent(this));
}
}
...
#Override
public void addChangeListener(ChangeListener l) {
this.l = l;
}
#Override
public void removeChangeListener(ChangeListener l) {
if(this.l == l) {
this.l = null;
}
}
});
Edit: You can use List to register many listeners.

Related

Expanding one field while others are collapsed in blackberry java

I am developing an app in which i am adding and deleting fields based on setting flag to true or false.but what i am trying to do is if i click on field that particular field should expand while others are collapsed(even if it is expanded)
i googled it but i didnt get solution please help me i am new to blackberry java
i have used below code
public final class MyScreen extends MainScreen implements
FieldChangeListener
{
/**
* Creates a new MyScreen object
*/
private VerticalFieldManager main_manager;
private HorizontalFieldManager parentNodes;
private LabelField parent_lables[];
private Bitmap bitmap,upbitmap;
private BitmapField bitmap_field[];
private VerticalFieldManager submanager[];
private int sizeOfParentNodes=3;
private int sizeOfChildNodes=5;
private static boolean flag[];
public MyScreen()
{
// Set the displayed title of the screen
bitmap=Bitmap.getBitmapResource("arrow.png");
upbitmap=Bitmap.getBitmapResource("uparrow.png");
main_manager=new
VerticalFieldManager(Manager.VERTICAL_SCROLL|VERTICAL_SCROLLBAR){
protected void sublayout(int maxWidth, int maxHeight) {
super.sublayout(Display.getWidth(), Display.getHeight());
setExtent(Display.getWidth(), Display.getHeight());
};
};
parent_lables=new LabelField[sizeOfParentNodes];
flag=new boolean[sizeOfParentNodes];
submanager=new VerticalFieldManager[sizeOfParentNodes];
bitmap_field=new BitmapField[sizeOfParentNodes];
for(int i=0;i<sizeOfParentNodes;i++)
{
submanager[i]=new VerticalFieldManager();
updateGUI(i);
main_manager.add(submanager[i]);
}
add(main_manager);
}
public void fieldChanged(Field field, int context) {
// TODO Auto-generated method stub
synchronized (UiApplication.getEventLock()) {
for(int i=0;i<sizeOfParentNodes;i++)
{ if(field==parent_lables[i])
{
if(flag[i]==true){
flag[i]=false;
submanager[i].deleteAll();
updateGUI(i);
parent_lables[i].setFocus();
}else{
flag[i]=true;
bitmap_field[i].setBitmap(upbitmap);
submanager[i].invalidate();
}
}
}
}
}
public void updateGUI(int index)
{
parentNodes=new HorizontalFieldManager(USE_ALL_WIDTH);
bitmap_field[index]=new BitmapField(bitmap);
parentNodes.add(bitmap_field[index]);
parent_lables[index]=new LabelField("Day"+index,Field.FOCUSABLE){
protected boolean navigationClick(int status, int time) {
fieldChangeNotify(1);
return true;
};
};
parentNodes.add(parent_lables[index]);
parent_lables[index].setChangeListener(this);
submanager[index].add(parentNodes);
}
}
I knocked this together, and will likely need some tweaks by you to get it exactly how you want it, but it should be something you can work from. Assuming I've understood your question correctly.
You'll need to create a base field, a helper able to manage a list of them, and a callback. The fact that you need a base field is the biggest downfall, because it immediately excludes all other widgets you have, as they need to be made from scratch with the paint method. You can use a VerticalFieldManager instead of field by extending VerticalFieldManager instead of Field.
On to the java classes.
My CollapsableField.java looks as follows:
public abstract class CollapsableField extends Field
{
// We make use of a different listener than the FieldChangeListener, since you can only attach one and we will most likely want to do so, we can't "occupy" the listener.
private CollapseListener listener;
private boolean collapsed;
protected int collapsedWidth;
protected int collapsedHeight;
public CollapsableField()
{
collapsed = true;
// Field is completely collapsed by default
collapsedWidth = 0;
collapsedHeight = 0;
}
public void setCollapseListener(CollapseListener listener)
{
this.listener = listener;
}
public final boolean isCollapsed()
{
return collapsed;
}
public final void collapse()
{
this.collapsed = true;
if (listener != null)
{
listener.onCollapse(this);
}
fieldChangeNotify(0); // Notify that the field has changed, so that attached field change listeners will fire
updateLayout(); // re-call layout
}
public final void expand()
{
this.collapsed = false;
if (listener != null)
{
listener.onExpand(this);
}
fieldChangeNotify(0); // Notify that the field has changed, so that attached field change listeners will fire
updateLayout(); // re-call layout
}
protected void layout(int width, int height)
{
if (collapsed)
{
// Set dimensions to collapsed
setExtent(collapsedWidth, collapsedHeight);
}
else
{
// Set dimensions to what the extending class specified
setExtent(width, height);
}
}
protected final void paint(Graphics graphics)
{
if (collapsed)
{
paintCollapsed(graphics);
}
else
{
paintExpanded(graphics);
}
}
protected abstract void paintCollapsed(Graphics graphics);
protected abstract void paintExpanded(Graphics graphics);
}
The Group then takes a list of these, applies a listener to each field as it's added. When a field signals that it has expanded, the group will tell all other fields to collapse themselves.
CollapsableGroup.java:
public class CollapsableGroup
{
private Vector fields;
private CollapseListener listener;
public CollapsableGroup()
{
fields = new Vector();
listener = new CollapseListener()
{
public void onExpand(CollapsableField source)
{
for (int i = 0; i < fields.size(); i++)
{
CollapsableField field = (CollapsableField) fields.elementAt(i);
if ((!field.isCollapsed()) && (field != source))
{
field.collapse();
}
}
}
public void onCollapse(CollapsableField source)
{
// Don't need to handle this. Method is here just for completeness
}
};
}
public void add(CollapsableField field)
{
field.setCollapseListener(listener);
fields.addElement(field);
}
public void remove(CollapsableField field)
{
field.setCollapseListener(null);
fields.removeElement(field);
}
/**
* Returns the currently expanded field. Returns <b>null</b> if all fields are collapsed.
*
* #return
*/
public CollapsableField getExpandedField()
{
for (int i = 0; i < fields.size(); i++)
{
CollapsableField field = (CollapsableField) fields.elementAt(i);
if (!field.isCollapsed())
{
return field;
}
}
return null;
}
public void expand(CollapsableField field)
{
field.expand(); // Listeners should take care of the rest
}
public void collapseAll()
{
for (int i = 0; i < fields.size(); i++)
{
CollapsableField field = (CollapsableField) fields.elementAt(i);
if (!field.isCollapsed())
{
field.collapse();
}
}
}
}
And finally the listener interface.
CollapseListener.java:
interface CollapseListener
{
public void onExpand(CollapsableField source);
public void onCollapse(CollapsableField source);
}
Those three classes should be all that you need. The classes that follow are my example/test classes.
TestLabel.java is an example of an extended class:
public class TestLabel extends CollapsableField
{
private String text;
private String collapsedText;
public TestLabel(String text, String collapsedText)
{
this.text = text;
this.collapsedText = collapsedText;
// Tells the layout method to collapse to the size of this text
collapsedWidth = getFont().getAdvance(collapsedText);
collapsedHeight = getFont().getHeight();
}
public int getPreferredWidth()
{
return getFont().getAdvance(text);
}
public int getPreferredHeight()
{
return getFont().getHeight();
}
protected void layout(int width, int height)
{
super.layout(getPreferredWidth(), getPreferredHeight());
}
protected void paintCollapsed(Graphics graphics)
{
// Paints only the collapsedText
graphics.drawText(collapsedText, 0, 0);
}
protected void paintExpanded(Graphics graphics)
{
// Paints the full Text
graphics.drawText(text, 0, 0);
}
protected boolean touchEvent(TouchEvent message)
{
// Toggle on mouse press
if (message.getEvent() == TouchEvent.CLICK)
{
if (isCollapsed())
{
expand();
}
else
{
collapse();
}
return true;
}
return super.touchEvent(message);
}
}
The following screen contains some of the fields to show that both the widgets themselves and the group can manipulate the fields.
MyScreen.java:
public final class MyScreen extends MainScreen
{
public MyScreen()
{
// Set the displayed title of the screen
setTitle("MyTitle");
final CollapsableGroup group = new CollapsableGroup();
final TestLabel label1 = new TestLabel("Label1", "L1");
label1.setBackground(BackgroundFactory.createSolidBackground(0x999999));
group.add(label1);
final TestLabel label2 = new TestLabel("Label2", "L2");
label2.setBackground(BackgroundFactory.createSolidBackground(0xBBBBBB));
group.add(label2);
final TestLabel label3 = new TestLabel("Label3", "L3");
label3.setBackground(BackgroundFactory.createSolidBackground(0xDDDDDD));
group.add(label3);
ButtonField collapseAll = new ButtonField("Collapse All")
{
protected boolean navigationClick(int status, int time)
{
group.collapseAll();
return true;
}
};
add(collapseAll);
ButtonField expand1 = new ButtonField("Expand1")
{
protected boolean navigationClick(int status, int time)
{
group.expand(label1);
return true;
}
};
add(expand1);
ButtonField expand2 = new ButtonField("Expand2")
{
protected boolean navigationClick(int status, int time)
{
group.expand(label2);
return true;
}
};
add(expand2);
ButtonField expand3 = new ButtonField("Expand3")
{
protected boolean navigationClick(int status, int time)
{
group.expand(label3);
return true;
}
};
add(expand3);
add(label1);
add(label2);
add(label3);
}
}

Filtering JComboBox

At beginning I will say that I don't have in mind auto-complete combobox, but rather having a "setFilter(Set)" method in my combobox, so it displays what is in the set.
I was unable to achieve that effect, trying different approaches and I think it's the view responsibility to filter what it displays, so I should not extend ComboBoxModel.
This is what I have so far (main includes the case which doesn't work):
import java.awt.*;
import java.util.Set;
import javax.swing.*;
public class FilteredComboBox extends JComboBox {
private ComboBoxModel entireModel;
private final DefaultComboBoxModel filteredModel = new DefaultComboBoxModel();
private Set objectsToShow;
public FilteredComboBox(ComboBoxModel model) {
super(model);
this.entireModel = model;
}
public void setFilter(Set objectsToShow) {
if (objectsToShow != null) {
this.objectsToShow = objectsToShow;
filterModel();
} else {
removeFilter();
}
}
public void removeFilter() {
objectsToShow = null;
filteredModel.removeAllElements();
super.setModel(entireModel);
}
private void filterModel() {
filteredModel.removeAllElements();
for (int i = 0; i < entireModel.getSize(); ++i) {
Object element = entireModel.getElementAt(i);
addToFilteredModelIfShouldBeDisplayed(element);
}
super.setModel(filteredModel);
}
private void addToFilteredModelIfShouldBeDisplayed(Object element) {
if (objectsToShow.contains(element)) {
filteredModel.addElement(element);
}
}
#Override
public void setModel(ComboBoxModel model) {
entireModel = model;
super.setModel(entireModel);
if (objectsToShow != null) {
filterModel();
}
}
public static void main(String[] args) {
JFrame f = new JFrame();
f.setLayout(new BoxLayout(f.getContentPane(), BoxLayout.X_AXIS));
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
DefaultComboBoxModel model = new DefaultComboBoxModel();
FilteredComboBox cb = new FilteredComboBox(model);
cb.setPrototypeDisplayValue("XXXXXXXXXXXX");
f.add(cb);
f.pack();
Set objectsToShow = new HashSet();
objectsToShow.add("1");
objectsToShow.add("3");
objectsToShow.add("4");
cb.setFilter(objectsToShow); // if you set that filter after addElements it will work
model.addElement("1");
model.addElement("2");
model.addElement("3");
model.addElement("4");
model.addElement("5");
f.setVisible(true);
}
}
"I think it's the view responsibility to filter what it displays" - I'd argue that, the view displays what it's told, the model drives what it can show, but that's me...
This is an idea I wrote way back in Java 1.3 (with generic updates) which basically wraps a proxy ComboBoxModel around another ComboBoxModel. The proxy (or FilterableComboBoxModel) then makes decisions about which elements from the original model match a filter and updates it's indices.
Basically, all it does is generates an index map between itself and the original model, so it's not copying anything or generating new references to the original data.
The filtering is controlled via a "filterable" interface which simply passes the element to be checked and expects a boolean result in response. This makes the API highly flexible as filtering can be done any way you want without the need to change the FilterableComboBoxModel in any way. It also means you can change the filter been used by simply applying a new one...
If, like I usually do, you want to pass some value to the filter, you will need to inform the model that the filter has changed, via the updateFilter method...yeah, I know, a ChangeListener would probably be a better idea, but I was trying to keep it simple ;)
For flexibility (and to maintain the current inheritance model), the core API is based on a ListModel, which means, you can also use the same concept with a JList
FilterableListModel
import java.util.ArrayList;
import java.util.List;
import javax.swing.AbstractListModel;
import javax.swing.ListModel;
import javax.swing.event.ListDataEvent;
import javax.swing.event.ListDataListener;
public class FilterableListModel<E> extends AbstractListModel<E> implements ListDataListener {
private ListModel<E> peer;
private List<Integer> lstFilteredIndicies;
private IFilterable filter;
public FilterableListModel() {
lstFilteredIndicies = new ArrayList<Integer>(25);
}
public FilterableListModel(ListModel<E> model) {
this();
setModel(model);
}
public FilterableListModel(ListModel<E> model, IFilterable filter) {
this();
setModel(model);
setFilter(filter);
}
public void setModel(ListModel<E> parent) {
if (peer == null || !peer.equals(parent)) {
if (peer != null) {
fireIntervalRemoved(this, 0, peer.getSize() - 1);
peer.removeListDataListener(this);
}
peer = parent;
lstFilteredIndicies.clear();
if (peer != null) {
peer.addListDataListener(this);
}
filterModel(true);
}
}
public ListModel<E> getModel() {
return peer;
}
#Override
public int getSize() {
IFilterable filter = getFilter();
return filter == null ? getModel() == null ? 0 : getModel().getSize() : lstFilteredIndicies.size();
}
#Override
public E getElementAt(int index) {
IFilterable filter = getFilter();
ListModel<E> model = getModel();
E value = null;
if (filter == null) {
if (model != null) {
value = model.getElementAt(index);
}
} else {
int filterIndex = lstFilteredIndicies.get(index);
value = model.getElementAt(filterIndex);
}
return value;
}
public int indexOf(Object value) {
int index = -1;
for (int loop = 0; loop < getSize(); loop++) {
Object at = getElementAt(loop);
if (at == value) {
index = loop;
break;
}
}
return index;
}
#Override
public void intervalAdded(ListDataEvent e) {
IFilterable filter = getFilter();
ListModel model = getModel();
if (model != null) {
if (filter != null) {
int startIndex = Math.min(e.getIndex0(), e.getIndex1());
int endIndex = Math.max(e.getIndex0(), e.getIndex1());
for (int index = startIndex; index <= endIndex; index++) {
Object value = model.getElementAt(index);
if (filter.include(value)) {
lstFilteredIndicies.add(index);
int modelIndex = lstFilteredIndicies.indexOf(index);
fireIntervalAdded(this, modelIndex, modelIndex);
}
}
} else {
fireIntervalAdded(this, e.getIndex0(), e.getIndex1());
}
}
}
#Override
public void intervalRemoved(ListDataEvent e) {
IFilterable filter = getFilter();
ListModel model = getModel();
if (model != null) {
if (filter != null) {
int oldRange = lstFilteredIndicies.size();
filterModel(false);
fireIntervalRemoved(this, 0, oldRange);
if (lstFilteredIndicies.size() > 0) {
fireIntervalAdded(this, 0, lstFilteredIndicies.size());
}
} else {
fireIntervalRemoved(this, e.getIndex0(), e.getIndex1());
}
}
}
#Override
public void contentsChanged(ListDataEvent e) {
filterModel(true);
}
public void setFilter(IFilterable<E> value) {
if (filter == null || !filter.equals(value)) {
filter = value;
if (getModel() != null) {
if (getModel().getSize() > 0) {
fireIntervalRemoved(this, 0, getModel().getSize() - 1);
}
}
filterModel(true);
}
}
public IFilterable<E> getFilter() {
return filter;
}
protected void filterModel(boolean fireEvent) {
if (getSize() > 0 && fireEvent) {
fireIntervalRemoved(this, 0, getSize() - 1);
}
lstFilteredIndicies.clear();
IFilterable<E> filter = getFilter();
ListModel<E> model = getModel();
if (filter != null && model != null) {
for (int index = 0; index < model.getSize(); index++) {
E value = model.getElementAt(index);
if (filter.include(value)) {
lstFilteredIndicies.add(index);
if (fireEvent) {
fireIntervalAdded(this, getSize() - 1, getSize() - 1);
}
}
}
}
}
public void updateFilter() {
IFilterable filter = getFilter();
ListModel model = getModel();
if (filter != null && model != null) {
for (int index = 0; index < model.getSize(); index++) {
Object value = model.getElementAt(index);
if (filter.include(value)) {
if (!lstFilteredIndicies.contains(index)) {
lstFilteredIndicies.add(index);
fireIntervalAdded(this, getSize() - 1, getSize() - 1);
}
} else if (lstFilteredIndicies.contains(index)) {
int oldIndex = lstFilteredIndicies.indexOf(index);
lstFilteredIndicies.remove(oldIndex);
fireIntervalRemoved(this, oldIndex, oldIndex);
}
}
}
}
}
Filterable
public interface IFilterable<O> {
public boolean include(O value);
}
FilterableComboBoxModel
import javax.swing.ComboBoxModel;
public class FilterableComboBoxModel<E> extends FilterableListModel<E> implements ComboBoxModel<E> {
private FilterableComboBoxModel(ComboBoxModel<E> model) {
super(model);
}
public ComboBoxModel<E> getComboBoxModel() {
return (ComboBoxModel) getModel();
}
#Override
public void setSelectedItem(Object anItem) {
getComboBoxModel().setSelectedItem(anItem);
}
#Override
public Object getSelectedItem() {
return getComboBoxModel().getSelectedItem();
}
}
It should be noted that it might actually be possible to use a RowFilter instead, but I've never really had the time to look at it (since I already had a working API)

populate multiple combobox with same model but select diff

Having problems with the ComboBox, I have populated multiple ComboBoxes with the same model, but when I run my program and select a value from one ComboBox it selects the same value for the rest.
ComboHBoy.setModel(defaultComboBoxModel);
ComboHGirl.setModel(defaultComboBoxModel);
ComboDHBoy.setModel(defaultComboBoxModel);
ComboDHGirl.setModel(defaultComboBoxModel);
That's because they all are referenced to the same model, any change of the model will affect the all the other combos.
There is no way to solve this except that every combobox have it's own DefaultComboBoxModel.
private DefaultComboBoxModel hBoyModel= new DefaultComboBoxModel();
private DefaultComboBoxModel hGirlModel= new DefaultComboBoxModel();
//....
ComboHBoy.setModel(hBoyModel);
ComboHGirl.setModel(hGrilModel);
//....
Use just a ListModel to manage your data and create a ComboboxModel adapter that is based on the ListModel. This ComboboxModel will only add the selection capability. Remember that a ComboboxModel extends ListModel. So it is easy to adapt the interfaces.
The only tricky part is to handle the update events.
For example:
public class ListAdapterComboboxModel implements ComboBoxModel {
private ListModel dataModel;
private Object selectedObject;
private DataModelListDataListenerAdapter listDataListenerAdapter;
public ListAdapterComboboxModel(ListModel ListModel) {
dataModel = ListModel;
this.listDataListenerAdapter = new DataModelListDataListenerAdapter();
dataModel.addListDataListener(listDataListenerAdapter);
}
public int getSize() {
return dataModel.getSize();
}
public Object getElementAt(int index) {
return dataModel.getElementAt(index);
}
public void addListDataListener(ListDataListener l) {
listDataListenerAdapter.addListDataListener(l);
}
public void removeListDataListener(ListDataListener l) {
listDataListenerAdapter.removeListDataListener(l);
}
public void setSelectedItem(Object anObject) {
if ((selectedObject != null && !selectedObject.equals(anObject))
|| selectedObject == null && anObject != null) {
selectedObject = anObject;
ListDataEvent e = new ListDataEvent(this,
ListDataEvent.CONTENTS_CHANGED, -1, -1);
listDataListenerAdapter.delegateListDataEvent(e);
}
}
public Object getSelectedItem() {
return selectedObject;
}
private class DataModelListDataListenerAdapter implements ListDataListener {
protected EventListenerList listenerList = new EventListenerList();
public void removeListDataListener(ListDataListener l) {
listenerList.remove(ListDataListener.class, l);
}
public void addListDataListener(ListDataListener l) {
listenerList.add(ListDataListener.class, l);
}
public void intervalAdded(ListDataEvent e) {
delegateListDataEvent(e);
}
public void intervalRemoved(ListDataEvent e) {
checkSelection(e);
delegateListDataEvent(e);
}
public void contentsChanged(ListDataEvent e) {
checkSelection(e);
delegateListDataEvent(e);
}
private void checkSelection(ListDataEvent e) {
Object selectedItem = getSelectedItem();
ListModel listModel = (ListModel) e.getSource();
int size = listModel.getSize();
boolean selectedItemNoLongerExists = true;
for (int i = 0; i < size; i++) {
Object elementAt = listModel.getElementAt(i);
if (elementAt != null && elementAt.equals(selectedItem)) {
selectedItemNoLongerExists = false;
break;
}
}
if (selectedItemNoLongerExists) {
ListAdapterComboboxModel.this.selectedObject = null;
}
}
protected void delegateListDataEvent(ListDataEvent lde) {
ListDataListener[] listeners = listenerList
.getListeners(ListDataListener.class);
for (ListDataListener listDataListener : listeners) {
listDataListener.contentsChanged(lde);
}
}
}
}
And then just use it like this
public class ComboboxModelTest extends JFrame{
public static void main(String[] args) {
ComboboxModelTest comboboxModelTest = new ComboboxModelTest();
comboboxModelTest.pack();
comboboxModelTest.setVisible(true);
}
public ComboboxModelTest() {
Container contentPane = getContentPane();
contentPane.setLayout(new FlowLayout());
DefaultListModel defaultListModel = new DefaultListModel();
defaultListModel.addElement("Element 1");
defaultListModel.addElement("Element 2");
ComboBoxModel firstComboboxModel = new ListAdapterComboboxModel(defaultListModel);
ComboBoxModel secondComboboxModel = new ListAdapterComboboxModel(defaultListModel);
JComboBox jComboBox1 = new JComboBox(firstComboboxModel);
JComboBox jComboBox2 = new JComboBox(secondComboboxModel);
contentPane.add(jComboBox1);
contentPane.add(jComboBox2);
}
}
Then you only have to manage the data in one ListModel and you have distinct selection models.
Also take a look at The MVC pattern and SWING.

Is possible to set an unknown value on a JSlider?

Short:
Is possible to set an unknown value on a JSlider having no knob?
Long:
I'm working on a project that has a desktop client developed in Java Swing where the user is required to measure some parameters using a slider. That's a requirement, it has to be a slider.
Until the user interacts with the slider the value has to be unknown, and that means showing no knob. That's a requirement too.
When using a JSlider the knob shows always and is no way to set any value out of its bounds or set it to null as it uses the primitive type int and not the object Integer.
Is there a way to set it a null fail-value or at least some value that shows no knob? Is there some extension that would allow to do so?
Make your own implementation of BasicSliderUI and override paintThumb(Graphics g) to do what you require.
Also take a look here: How to hide the knob of jSlider?
With the great help of Harry Joy about hiding the knob, finally I've been able to solve the problem.
If you check Harry's answer you can read about overriding the method BasicSliderUI.paintThumb(Graphics) for hiding the knob. It works fine on most L&F's not based on Synth (and that means Nimbus), where the approach would be different, but doable via customizations on the L&F.
Installing it is a bit tricky: having a PropertyChangeListener on the UIManager that checks any change on the L&F and installs a proper UI delegate on the component does the magic (in this solution I'm just showing the one based in BasicSliderUI the others are copy-pastes). Also I've tweaked it a bit to make it set the value to the position where is first clicked.
For the delegate to know if it must paint the knob or not I decided having a client property on the JSlider called "ready", which is to be a boolean. This way, the delegate can be installed on any JSlider, not only the one extended.
Now, how to manage unknown values? Adding another new property "selectedValue", this one is bound to both "value" and "ready" and of Integer type. "ready" is false or true depending on if it's null or not, and viceversa, and both "value" and "selectedValue" hold the same value (one being int and the other Integer) unless not ready, which would set the "selectedValue" to null. It might sound complicated, but it is not.
Also there's a little tweak for clearing the value with [Esc] and propagating properties in the component implemented.
Here's the code:
BasicSliderUIExt
import java.awt.Graphics;
import java.awt.event.MouseEvent;
import javax.swing.JSlider;
import javax.swing.SwingConstants;
import javax.swing.plaf.basic.BasicSliderUI;
public class BasicSliderUIExt extends BasicSliderUI {
public BasicSliderUIExt(JSlider slider) {
super(slider);
}
#Override
public void paintThumb(Graphics g) {
if (isReady(super.slider)) {
super.paintThumb(g);
}
}
#Override
protected TrackListener createTrackListener(final JSlider slider) {
return new TrackListener() {
#Override
public void mousePressed(MouseEvent event) {
if (isReady(slider)) {
super.mousePressed(event);
} else {
JSlider slider = (JSlider) event.getSource();
switch (slider.getOrientation()) {
case SwingConstants.VERTICAL:
slider.setValue(valueForYPosition(event.getY()));
break;
case SwingConstants.HORIZONTAL:
slider.setValue(valueForXPosition(event.getX()));
break;
}
super.mousePressed(event);
super.mouseDragged(event);
}
}
#Override
public boolean shouldScroll(int direction) {
if (isReady(slider)) {
return super.shouldScroll(direction);
}
return false;
}};
}
public static boolean isReady(JSlider slider) {
Object ready = slider.getClientProperty(JSliderExt.READY_PROPERTY);
return (ready == null) || (!(ready instanceof Boolean)) || (((Boolean) ready).booleanValue());
}
}
JSliderExt
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.lang.reflect.Constructor;
import javax.swing.BoundedRangeModel;
import javax.swing.JSlider;
import javax.swing.LookAndFeel;
import javax.swing.UIManager;
import javax.swing.plaf.SliderUI;
public class JSliderExt extends JSlider {
private static final long serialVersionUID = 1L;
public static final String EXTENT_PROPERTY = "extent";
public static final String MAXIMUM_PROPERTY = "maximum";
public static final String MINIMUM_PROPERTY = "minimum";
public static final String READY_PROPERTY = "ready";
public static final String SELECTED_VALUE_PROPERTY = "selectedValue";
public static final String VALUE_PROPERTY = "value";
public static final boolean READY_DEFAULT_VALUE = false;
protected SliderUI uix = new BasicSliderUIExt(this);
public JSliderExt(BoundedRangeModel model, boolean ready) {
super(model);
init(ready);
}
public JSliderExt(BoundedRangeModel model) {
super(model);
init(READY_DEFAULT_VALUE);
}
public JSliderExt(int orientation, int minimmum, int maximum, int value, boolean ready) {
super(orientation, minimmum, maximum, value);
init(ready);
}
public JSliderExt(int orientation, int minimmum, int maximum, int value) {
super(orientation, minimmum, maximum, value);
init(READY_DEFAULT_VALUE);
}
public JSliderExt(int minimmum, int maximum, int value, boolean ready) {
super(minimmum, maximum, value);
init(ready);
}
public JSliderExt(int minimmum, int maximum, int value) {
super(minimmum, maximum, value);
init(READY_DEFAULT_VALUE);
}
public JSliderExt(int minimmum, int maximum, boolean ready) {
super(minimmum, maximum);
init(ready);
}
public JSliderExt(int minimmum, int maximum) {
super(minimmum, maximum);
init(READY_DEFAULT_VALUE);
}
public JSliderExt(int orientation, boolean ready) {
super(orientation);
init(ready);
}
public JSliderExt(int orientation) {
super(orientation);
init(READY_DEFAULT_VALUE);
}
public JSliderExt(boolean ready) {
super();
init(ready);
}
public JSliderExt() {
super();
init(READY_DEFAULT_VALUE);
}
private void init(boolean ready) {
UIManager.addPropertyChangeListener(new PropertyChangeListener() { // Changes the UI delegate in L&F change
#Override
public void propertyChange(PropertyChangeEvent event) {
if ("lookAndFeel".equals(event.getPropertyName())) {
Object newValue = event.getNewValue();
if ((newValue != null) && (newValue instanceof LookAndFeel)) {
LookAndFeel lookAndFeel = (LookAndFeel) newValue;
try {
if (lookAndFeel instanceof MetalLookAndFeel) {
JSliderExt.this.uix = new MetalSliderUIExt();
} else if (lookAndFeel instanceof com.sun.java.swing.plaf.motif.MotifLookAndFeel) {
JSliderExt.this.uix = new MotifSliderUIExt(JSliderExt.this);
} else if (lookAndFeel instanceof com.sun.java.swing.plaf.windows.WindowsLookAndFeel) {
JSliderExt.this.uix = new WindowsSliderUIExt(JSliderExt.this);
} else {
throw new NullPointerException("Default Look & Feel not matched");
}
} catch (Exception e) {
try {
Package sliderPackage = JSliderExt.this.getClass().getPackage();
String lookAndFeelName = lookAndFeel.getName();
if (lookAndFeelName.equals("CDE/Motif")) {
lookAndFeelName = "Motif";
} else if (lookAndFeelName.equals("Windows Classic")) {
lookAndFeelName = "Windows";
} else if (lookAndFeelName.startsWith("JGoodies")) {
lookAndFeelName = "Basic";
}
Class<?> sliderUiType = Class.forName(sliderPackage.getName() + "." + lookAndFeelName
+ "SliderUIExt");
Constructor<?> constructor1 = null;
try {
constructor1 = sliderUiType.getConstructor(JSlider.class);
} catch (Exception e3) { // Nothing to do here
}
Constructor<?> constructor0 = null;
try {
constructor0 = sliderUiType.getConstructor();
} catch (Exception e3) { // Nothing to do here
}
Object sliderUi = null;
if (constructor1 != null) {
sliderUi = constructor1.newInstance(JSliderExt.this);
} else if (constructor0 != null) {
sliderUi = constructor0.newInstance();
}
if ((sliderUi != null) && (sliderUi instanceof SliderUI)) {
JSliderExt.this.setUI((SliderUI) sliderUi);
}
} catch (Exception e2) {
JSliderExt.this.uix = new BasicSliderUIExt(JSliderExt.this);
}
}
} else {
JSliderExt.this.uix = new BasicSliderUIExt(JSliderExt.this);
}
updateUI();
}
}});
addPropertyChangeListener(new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent event) {
String propertyName = event.getPropertyName();
if (READY_PROPERTY.equals(propertyName)) {
Object newValue = event.getNewValue();
if ((newValue == null) || (!(newValue instanceof Boolean)) || (((Boolean) newValue).booleanValue())) {
setSelectedValue(Integer.valueOf(getValue()));
} else {
setSelectedValue(null);
}
} else if (SELECTED_VALUE_PROPERTY.equals(propertyName)) {
Object newValue = event.getNewValue();
System.out.println(newValue);
if ((newValue != null) && (newValue instanceof Integer)) {
int value = getValue();
int newSelectedValue = ((Integer) newValue).intValue();
if (value != newSelectedValue) {
setValue(newSelectedValue);
}
setReady(true);
} else {
setReady(false);
}
repaint();
} else if (VALUE_PROPERTY.equals(propertyName)) {
setReady(true);
Object newValue = event.getNewValue();
if ((newValue != null) && (newValue instanceof Integer)) {
setSelectedValue((Integer) newValue);
}
}
}});
addKeyListener(new KeyAdapter() { // Enables escape key for clearing value
#Override
public void keyPressed(KeyEvent event) {
if (event.getKeyCode() == KeyEvent.VK_ESCAPE) {
JSliderExt.this.setReady(false);
}
}});
setReady(ready);
}
#Override
public void setValue(int value) {
int oldValue = getValue();
super.setValue(value);
firePropertyChange(VALUE_PROPERTY, Integer.valueOf(oldValue), Integer.valueOf(value));
}
#Override
public void setExtent(int extent) {
int oldExtent = getExtent();
super.setExtent(extent);
firePropertyChange(EXTENT_PROPERTY, Integer.valueOf(oldExtent), Integer.valueOf(extent));
}
#Override
public void setMinimum(int minimum) {
int oldMinimum = getMinimum();
super.setMinimum(minimum);
firePropertyChange(MINIMUM_PROPERTY, Integer.valueOf(oldMinimum), Integer.valueOf(minimum));
}
#Override
public void setMaximum(int maximum) {
int oldMaximum = getMaximum();
super.setMaximum(maximum);
firePropertyChange(MAXIMUM_PROPERTY, Integer.valueOf(oldMaximum), Integer.valueOf(maximum));
}
public Integer getSelectedValue() {
return (Integer) getClientProperty(SELECTED_VALUE_PROPERTY);
}
public void setSelectedValue(Integer selectedValue) {
putClientProperty(SELECTED_VALUE_PROPERTY, selectedValue);
}
public boolean isReady() {
Object ready = getClientProperty(READY_PROPERTY);
return ((ready != null) && (ready instanceof Boolean)) ? ((Boolean) ready).booleanValue()
: READY_DEFAULT_VALUE;
}
public void setReady(boolean waiting) {
putClientProperty(READY_PROPERTY, Boolean.valueOf(waiting));
}
#Override
public void updateUI() {
setUI(this.uix);
updateLabelUIs();
}
}
Please note that using this code, might require some changes on selecting the delegates depending on your application, since this is intended for a Windows system. As said, Synth/Nimbus has to be worked in a different manner, but also any custom L&F or for OSX, needs the proper delegate to be extended and added on the listener.

GWT editors and get/set value

I have following editor class, and I'm curious what's wrong with it. When running, it does correctly set the right radio button as selected. However, when flushing the top level editor, getValue is never called, and my object's property never get updated. Here's the code (hint - modified ValueListBox):
public class ValueRadioList<T> extends FlowPanel implements
HasConstrainedValue<T>, LeafValueEditor<T>, ValueChangeHandler<Boolean> {
private final List<T> values = new ArrayList<T>();
private final Map<Object, Integer> valueKeyToIndex =
new HashMap<Object, Integer>();
private final String name;
private final Renderer<T> renderer;
private final ProvidesKey<T> keyProvider;
private T value;
public ValueRadioList(Renderer<T> renderer) {
this(renderer, new SimpleKeyProvider<T>());
}
public ValueRadioList(Renderer<T> renderer, ProvidesKey<T> keyProvider) {
super();
this.name = DOM.createUniqueId();
this.keyProvider = keyProvider;
this.renderer = renderer;
}
private void addValue(T value) {
Object key = keyProvider.getKey(value);
if (valueKeyToIndex.containsKey(key)) {
throw new IllegalArgumentException("Duplicate value: " + value);
}
valueKeyToIndex.put(key, values.size());
values.add(value);
RadioButton radio = new RadioButton(name, renderer.render(value));
radio.addValueChangeHandler(this);
add(radio);
assert values.size() == getWidgetCount();
}
#Override public HandlerRegistration addValueChangeHandler(
ValueChangeHandler<T> handler) {
return addHandler(handler, ValueChangeEvent.getType());
}
#Override public T getValue() {
return value;
}
#Override public void onValueChange(ValueChangeEvent<Boolean> event) {
int selectedIndex = -1;
for (int i = 0, l = getWidgetCount(); i < l; i++) {
if (((RadioButton) getWidget(i)).getValue()) {
selectedIndex = i;
break;
}
}
if (selectedIndex < 0) {
return; // Not sure why this happens during addValue
}
T newValue = values.get(selectedIndex);
setValue(newValue, true);
}
#Override public void setAcceptableValues(Collection<T> newValues) {
values.clear();
valueKeyToIndex.clear();
clear();
for (T nextNewValue : newValues) {
addValue(nextNewValue);
}
updateRadioList();
}
#Override public void setValue(T value) {
setValue(value, false);
}
#Override public void setValue(T value, boolean fireEvents) {
if (value == this.value
|| (this.value != null && this.value.equals(value))) {
return;
}
T before = this.value;
this.value = value;
updateRadioList();
if (fireEvents) {
ValueChangeEvent.fireIfNotEqual(this, before, value);
}
}
private void updateRadioList() {
Object key = keyProvider.getKey(value);
Integer index = valueKeyToIndex.get(key);
if (index == null) {
addValue(value);
}
index = valueKeyToIndex.get(key);
((RadioButton) getWidget(index)).setValue(true);
}
}
Solved it, my POJO missed a setter for that field.

Categories