I have a simple list field class which shows the default menu when I select a particular listitem. I want to customize the listfield item, so that when an item is selected a new screen is pushed onto the stack. I am overridding trackwheeel() method, but am not able to make it work.
import net.rim.device.api.system.*;
import net.rim.device.api.ui.*;
import net.rim.device.api.ui.component.*;
import net.rim.device.api.ui.container.*;
import java.util.Vector;
public class TeamListScreen extends UiApplication
{
public static void main(String[] args)
{
TeamListScreen theApp = new TeamListScreen();
theApp.enterEventDispatcher();
}
public TeamListScreen()
{
pushScreen(new ListFieldScreen());
}
}
class ListFieldScreen extends MainScreen
{
private ListField _listField;
private Vector _listElements;
int listFieldIndex = -1;
public ListFieldScreen()
{
setTitle("List Field Sample");
_listElements = new Vector();
_listField = new ListField();
ListCallback _callback = new ListCallback();
_listField.setCallback(_callback);
_listField.setRowHeight(45);
//_listField.setChangeListener(this);
add(_listField);
initializeList();
_listField = new ListField(_listElements.size()) {
protected void drawFocus(Graphics graphics, boolean on) {
}
protected boolean trackwheelClick(int status, int time) {
listFieldIndex = _listField.getSelectedIndex();
if (listFieldIndex < 0) {
listFieldIndex = 0;
}
try {
UiApplication.getUiApplication().invokeLater(new Runnable() {
public void run() {
UiApplication.getUiApplication().pushScreen(new LiveScreen());
// UiApplication.getUiApplication().popScreen(getActiveScreen());
}
});
} catch (Exception e) {
}
return true;
}
};
}
private void initializeList()
{
String itemOne = "List item one";
String itemTwo = "List item two";
_listElements.addElement(itemOne);
_listElements.addElement(itemTwo);
reloadList();
}
private void reloadList()
{
_listField.setSize(_listElements.size());
}
private class ListCallback implements ListFieldCallback
{
public void drawListRow(ListField list, Graphics g, int index, int y, int w)
{
String text = (String)_listElements.elementAt(index);
g.drawText(text, 0, y, 0, w);
}
public Object get(ListField list, int index)
{
return _listElements.elementAt(index);
}
public int indexOfList(ListField list, String prefix, int string)
{
return _listElements.indexOf(prefix, string);
}
public int getPreferredWidth(ListField list)
{
return Display.getWidth();
}
}
}
trackwheelClick() function is deprecated, you should use navigationClick() instead.
BTW, you don't need to use UiApplication.getUiApplication().invokeLater, because it is already running in the event queue.
Related
While developing a small task manager, I have noticed that columns aren't sorted correctly. To discard problems with my program, I have created a minimal version but it still fails to order the unique column right.
import java.awt.BorderLayout;
import java.util.List;
import java.util.Random;
import javax.swing.*;
import javax.swing.table.AbstractTableModel;
public class TableSortTest extends JFrame
{
private final JTable table;
private final ATableModel model;
public TableSortTest ()
{
setDefaultCloseOperation (EXIT_ON_CLOSE);
setSize (1366, 768);
setLocationRelativeTo (null);
model = new ATableModel ();
table = new JTable ();
table.setFillsViewportHeight (true);
table.setAutoCreateRowSorter (true);
table.setModel (model);
add (new JScrollPane (table), BorderLayout.CENTER);
setVisible (true);
Worker worker = new Worker ();
worker.execute ();
}
private class Pair
{
int index;
int value;
}
private class Worker extends SwingWorker <Void, Pair>
{
#Override
protected Void doInBackground ()
{
while (!isCancelled ())
{
Random r = new Random ();
for (int i = 0; i < 100; i++)
{
int indice = getIndexInRange (0, 99);
Pair p = new Pair ();
p.index = indice;
p.value = Math.abs (r.nextInt ());
publish (p);
}
try
{
Thread.sleep (1000);
}
catch (InterruptedException ie)
{
ie.printStackTrace ();
}
}
return null;
}
#Override
public void process (List <Pair> items)
{
for (Pair p : items)
{
model.setValueAt (p.value, p.index, 0);
}
}
}
public static int getIndexInRange (int min, int max)
{
return (min + (int) (Math.random () * ((max - min) + 1)));
}
private class ATableModel extends AbstractTableModel
{
private final Integer [] data;
public ATableModel ()
{
data = new Integer [100];
Random r = new Random ();
for (int i = 0; i < 100; i++)
{
data [i] = Math.abs (r.nextInt ());
}
}
#Override
public int getColumnCount ()
{
return 1;
}
#Override
public int getRowCount ()
{
return data.length;
}
#Override
public Object getValueAt (int rowIndex, int columnIndex)
{
return data [rowIndex];
}
#Override
public void setValueAt (Object value, int rowIndex, int columnIndex)
{
data [rowIndex] = (Integer) value;
fireTableRowUpdated (rowIndex, columnIndex);
}
#Override
public Class getColumnClass (int columnIndex)
{
return Integer.class;
}
#Override
public String getColumnName (int col)
{
return "Column";
}
}
public static final void main (String [] args)
{
SwingUtilities.invokeLater (() ->
{
try
{
new TableSortTest ();
}
catch (Exception e)
{
e.printStackTrace ();
}
});
}
}
I have tried with a ScheduledExecutorService + Runnable and a Timer + TimerTask just to test if it was a threading problem, but the behavior is the same. I have also read the Java Tutorial page about the subject. Given that my table only uses standard types I think that a simple table.setAutoCreateRowSorter (true); should do the job, shouldn't it?
Shouldn't the table be sorted after every modification/addition/removal even is fired?
Thanks for your quick answer trashgod. You're right, I meant fireTableRowsUpdated () but I made a mistake when I wrote the code, sorry. The point is that fireTableRowsUpdated (rowIndex, rowIndex) and fireTableCellUpdated (rowIndex, columnIndex) both fail to sort the column correctly. In the real program most of the table rows do change from one iteration to the next so calling fireTableDataChanged () makes perfect sense. But I didn't want to use it because if I select one or more rows to send a signal to the processes or whatever the selection is lost on every update. I have explored this way and found two forms of preserving the selection but it's a bit annoying and one of them breaks the selection with the keyboard. I show the necessary additions to the original code next.
The first form saves the selection before modifying the model and restores it after every update:
...
private class Worker extends SwingWorker <Void, Pair>
{
private int [] selectedRows;
#Override
protected Void doInBackground ()
{
while (!isCancelled ())
{
// Save the selection before modifying the model
int x = table.getSelectedRowCount ();
if (x > 0)
{
selectedRows = new int [x];
int [] tableSelection = table.getSelectedRows ();
for (int i = 0; i < x; i++)
{
selectedRows [i] = table.convertRowIndexToModel (tableSelection [i]);
}
}
Random r = new Random ();
for (int i = 0; i < table.getRowCount (); i++)
{
int indice = getIndexInRange (0, table.getRowCount () - 1);
Pair p = new Pair ();
p.index = indice;
p.value = Math.abs (r.nextInt ());
publish (p);
}
// If I put the code to restore the selection here, it doesn't work...
try
{
Thread.sleep (1000);
}
catch (InterruptedException ie)
{
ie.printStackTrace ();
}
}
return null;
}
#Override
public void process (List <Pair> items)
{
for (Pair p : items)
{
model.setValueAt (p.value, p.index, 1);
}
// Restore the selection on every update
if (selectedRows != null && selectedRows.length > 0)
{
for (int i = 0; i < selectedRows.length; i++)
{
table.addRowSelectionInterval (table.convertRowIndexToView (selectedRows [i]), table.convertRowIndexToView (selectedRows [i]));
}
}
}
}
...
The second form uses a ListSelectionListener, a KeyListener, and a flag. Selection with the keyboard doesn't work. To be honest, I don't know how did I come to get this solution. It probably was by chance:
public class TableSortTestSolucionConSelectionListener extends JFrame implements KeyListener
{
...
private boolean ctrlOrShiftDown = false;
private int [] selectedRows;
#Override
public void keyPressed (KeyEvent e)
{
ctrlOrShiftDown = e.isControlDown () || e.isShiftDown ();
}
#Override
public void keyReleased (KeyEvent e)
{
ctrlOrShiftDown = e.isControlDown () || e.isShiftDown ();
}
#Override
public void keyTyped (KeyEvent e)
{
ctrlOrShiftDown = e.isControlDown () || e.isShiftDown ();
}
public TableSortTestSolucionConSelectionListener ()
{
...
ListSelectionListener lsl = new ListSelectionListener ()
{
#Override
public void valueChanged (ListSelectionEvent e)
{
if (!e.getValueIsAdjusting ())
{
if (!ctrlOrShiftDown)
{
int x = table.getSelectedRowCount ();
if (x > 0)
{
selectedRows = new int [x];
int [] tableSelection = table.getSelectedRows ();
for (int i = 0; i < x; i++)
{
selectedRows [i] = table.convertRowIndexToModel (tableSelection [i]);
}
}
}
// Disable the listener to avoid infinite recursion
table.getSelectionModel ().removeListSelectionListener (this);
if (selectedRows != null && selectedRows.length > 0)
{
for (int i = 0; i < selectedRows.length; i++)
{
table.addRowSelectionInterval (table.convertRowIndexToView (selectedRows [i]), table.convertRowIndexToView (selectedRows [i]));
}
}
table.getSelectionModel ().addListSelectionListener (this);
}
}
};
table.getSelectionModel ().addListSelectionListener (lsl);
...
}
Fortunately today I have found a simple way to get the column sorted correctly and keep the current selection. You only have to add the following to your code:
TableRowSorter trs = (TableRowSorter) table.getRowSorter ();
trs.setSortsOnUpdates (true);
With this both fireTableCellUpdated () and fireTableRowsUpdated () work as I expected. To my understanding, setAutoCreateRowSorter () is only used to sort the rows when you click on the table header.
Greetings.
Using setSortsOnUpdates(), suggested here by #trcs, is the best general solution, but you may be able to optimize updates by the choice of TableModelEvent available to subclasses of AbstractTableModel.
The critical issue is the implementation of setValueAt(). If you meant fireTableRowsUpdated(), instead of fireTableRowUpdated(), note that the parameters represent a range of rows, not a row & column. In this case, because "all cell values in the table's rows may have changed," the revised example below invokes fireTableDataChanged(). I've also changed the model to manage a List<Integer> and normalized the size, N.
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import javax.swing.*;
import javax.swing.table.AbstractTableModel;
/** #see https://stackoverflow.com/a/36522182/230513 */
public class TableSortTest extends JFrame {
private final JTable table;
private final ATableModel model;
public TableSortTest() {
setDefaultCloseOperation(EXIT_ON_CLOSE);
model = new ATableModel();
table = new JTable(model){
#Override
public Dimension getPreferredScrollableViewportSize() {
return new Dimension(200, 500);
}
};
table.setFillsViewportHeight(true);
table.setAutoCreateRowSorter(true);
add(new JScrollPane(table), BorderLayout.CENTER);
pack();
setLocationRelativeTo(null);
setVisible(true);
Worker worker = new Worker();
worker.execute();
}
private class Pair {
int index;
int value;
}
private class Worker extends SwingWorker<Void, Pair> {
private static final int N = 100;
private final Random r = new Random();
#Override
protected Void doInBackground() {
while (!isCancelled()) {
for (int i = 0; i < N; i++) {
int index = r.nextInt(N);
Pair p = new Pair();
p.index = index;
p.value = Math.abs(r.nextInt());
publish(p);
}
try {
Thread.sleep(1000);
} catch (InterruptedException ie) {
ie.printStackTrace();
}
}
return null;
}
#Override
public void process(List<Pair> items) {
for (Pair p : items) {
model.setValueAt(p.value, p.index, 0);
}
}
}
private class ATableModel extends AbstractTableModel {
private static final int N = 100;
private final List<Integer> data = new ArrayList<>(N);
public ATableModel() {
final Random r = new Random();
for (int i = 0; i < N; i++) {
data.add(Math.abs(r.nextInt()));
}
}
#Override
public int getColumnCount() {
return 1;
}
#Override
public int getRowCount() {
return data.size();
}
#Override
public Object getValueAt(int rowIndex, int columnIndex) {
return data.get(rowIndex);
}
#Override
public void setValueAt(Object value, int rowIndex, int columnIndex) {
data.set(rowIndex, (Integer) value);
fireTableDataChanged();
}
#Override
public Class getColumnClass(int columnIndex) {
return Integer.class;
}
#Override
public String getColumnName(int col) {
return "Column";
}
}
public static final void main(String[] args) {
SwingUtilities.invokeLater(() -> {
new TableSortTest();
});
}
}
Recognizing that this is just an example, the variation below optimizes updates by publishing a List<Integer>, which is passed en bloc to the TableModel via process().
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import javax.swing.*;
import javax.swing.table.AbstractTableModel;
/**
* # see https://stackoverflow.com/a/36522182/230513
*/
public class TableSortTest extends JFrame {
private final JTable table;
private final ATableModel model;
public TableSortTest() {
setDefaultCloseOperation(EXIT_ON_CLOSE);
model = new ATableModel();
table = new JTable(model) {
#Override
public Dimension getPreferredScrollableViewportSize() {
return new Dimension(200, 500);
}
};
table.setFillsViewportHeight(true);
table.setAutoCreateRowSorter(true);
add(new JScrollPane(table), BorderLayout.CENTER);
pack();
setLocationRelativeTo(null);
setVisible(true);
Worker worker = new Worker();
worker.execute();
}
private class Worker extends SwingWorker<List<Integer>, List<Integer>> {
private static final int N = 100;
private final Random r = new Random();
private final List<Integer> data = new ArrayList<>(N);
#Override
protected List<Integer> doInBackground() throws Exception {
while (!isCancelled()) {
data.clear();
for (int i = 0; i < N; i++) {
data.add(Math.abs(r.nextInt()));
}
publish(data);
try {
Thread.sleep(1000);
} catch (InterruptedException ie) {
ie.printStackTrace(System.err);
}
}
return data;
}
#Override
protected void process(List<List<Integer>> chunks) {
for (List<Integer> chunk : chunks) {
model.update(chunk);
}
}
}
private class ATableModel extends AbstractTableModel {
private List<Integer> data = new ArrayList<>();
public void update(List<Integer> data) {
this.data = data;
fireTableDataChanged();
}
#Override
public int getColumnCount() {
return 1;
}
#Override
public int getRowCount() {
return data.size();
}
#Override
public Object getValueAt(int rowIndex, int columnIndex) {
return data.get(rowIndex);
}
#Override
public Class getColumnClass(int columnIndex) {
return Integer.class;
}
#Override
public String getColumnName(int col) {
return "Column";
}
}
public static final void main(String[] args) {
SwingUtilities.invokeLater(() -> {
new TableSortTest();
});
}
}
I have edited the question as your suggestions but null pointer exception is caught.I used connectionRequest instead of multipartRequest since i dont need to upload(just need to read the value frm json). All my codes below, please have a look.
Edited: exception
java.lang.NullPointerException
at userclasses.StateMachine$16.readResponse(StateMachine.java:1834)
at com.codename1.io.ConnectionRequest.performOperation(ConnectionRequest.java:438)
at com.codename1.io.NetworkManager$NetworkThread.run(NetworkManager.java:263)
at com.codename1.impl.CodenameOneThread.run(CodenameOneThread.java:176)
Code:
#Override
protected void beforeImgGallery(Form f) {
int iter = 0;
GridLayout gr = new GridLayout(1, 1);
Container grid = new Container(gr);
gr.setAutoFit(true);
grid.setScrollableY(true);
grid.addComponent(new InfiniteProgress());
f.addComponent(BorderLayout.CENTER, grid);
f.removeAllCommands();
f.setBackCommand(null);
createPictureCommand(grid);
}
private static boolean animating;
private Vector<Map<String, Object>> responsesgallery;
String galleryPhotoUrl;
private void createPictureCommand(final Container grid) {
ConnectionRequest mp = new ConnectionRequest(){
#Override
protected void readResponse(InputStream input) throws IOException {
JSONParser p = new JSONParser();
results = p.parse(new InputStreamReader(input));
responsesgallery = (Vector<Map<String, Object>>) results.get("data");
//i've kept this for loop in postResponse but same error
for (int i = 0; i < responsesgallery.size(); i++) {
//null pointer exception in this line
final Button btn = createImageButton(i, grid, imageList.getSize());
//if i simply create a btn like below, it works
// final Button btn = new Button((URLImage.createToStorage(placeholder, token, galleryPhotoUrl, URLImage.RESIZE_SCALE_TO_FILL)));
imageList.addImageId(i);
grid.addComponent(i, btn);
Hashtable hm = (Hashtable) responsesgallery.get(i);
String galleryImgId = (String) hm.get("news_id");
galleryPhotoUrl = (String) hm.get("photo");
}
}
};
mp.setUrl("http://capitaleyedevelopment.com/~admin/traffic/api/news/getLatestNews");
NetworkManager.getInstance().addToQueueAndWait(mp);
}
ImageList imageList;
Button createImageButton(final int imageId, final Container grid, final int offset) {
final Button btn = new Button(URLImage.createToStorage(placeholder, token, galleryPhotoUrl, URLImage.RESIZE_SCALE_TO_FILL));
btn.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) {
imageList.setSelectedIndex(offset);
final Container viewerParent = new Container(new LayeredLayout());
ImageViewer viewer = new ImageViewer(imageList.getItemAt(offset));
viewerParent.addComponent(viewer);
Container parent = new Container(new BorderLayout());
viewerParent.addComponent(parent);
viewer.setImageList(imageList);
grid.getParent().replace(grid, viewerParent, CommonTransitions.createSlide(CommonTransitions.SLIDE_HORIZONTAL, false, 300));
Display.getInstance().getCurrent().setBackCommand(createBackCommand(viewerParent, grid));
}
});
return btn;
}
public static final String SERVER_URL = "http://capitaleyedevelopment.com/~admin/traffic/api/news/getLatestNews";
class ImageList implements ListModel<Image> {
private int selection;
private long[] imageIds;
private EncodedImage[] images;
private EventDispatcher listeners = new EventDispatcher();
public void addImageId(int id) {
long[] n = new long[imageIds.length + 1];
EncodedImage[] nImages = new EncodedImage[n.length];
System.arraycopy(imageIds, 0, n, 0, imageIds.length);
System.arraycopy(images, 0, nImages, 0, images.length);
n[imageIds.length] = id;
imageIds = n;
images = nImages;
listeners.fireDataChangeEvent(-1, DataChangedListener.ADDED);
}
public long getSelectedImageId() {
return imageIds[selection];
}
public ImageList(long[] images) {
this.imageIds = images;
this.images = new EncodedImage[images.length];
}
public Image getItemAt(final int index) {
if (images[index] == null) {
images[index] = placeholder;
Util.downloadUrlToStorageInBackground(IMAGE_URL_PREFIX + imageIds[index], "FullImage_" + imageIds[index], new ActionListener() {
public void actionPerformed(ActionEvent evt) {
try {
images[index] = EncodedImage.create(Storage.getInstance().createInputStream("FullImage_" + imageIds[index]));
listeners.fireDataChangeEvent(index, DataChangedListener.CHANGED);
} catch (IOException err) {
err.printStackTrace();
}
}
});
}
return images[index];
}
public int getSize() {
return imageIds.length;
}
public int getSelectedIndex() {
return selection;
}
public void setSelectedIndex(int index) {
WebServiceProxy.getPhotoLikesAsync(imageIds[selection], new Callback<Integer>() {
public void onSucess(Integer value) {
}
public void onError(Object sender, Throwable err, int errorCode, String errorMessage) {
}
});
selection = index;
}
public void addDataChangedListener(DataChangedListener l) {
listeners.addListener(l);
}
public void removeDataChangedListener(DataChangedListener l) {
listeners.removeListener(l);
}
public void addSelectionListener(SelectionListener l) {
}
public void removeSelectionListener(SelectionListener l) {
}
public void addItem(Image item) {
}
public void removeItem(int index) {
}
}
In the Photo Share demo (that's on github) I demonstrate something pretty similar. I used a custom list model that fetches the images to the ImageViewer dynamically.
The interesting bit is this list model where the images are downloaded dynamically as needed:
class ImageList implements ListModel<Image> {
private int selection;
private long[] imageIds;
private EncodedImage[] images;
private EventDispatcher listeners = new EventDispatcher();
public void addImageId(long id) {
long[] n = new long[imageIds.length + 1];
EncodedImage[] nImages = new EncodedImage[n.length];
System.arraycopy(imageIds, 0, n, 0, imageIds.length);
System.arraycopy(images, 0, nImages, 0, images.length);
n[imageIds.length] = id;
imageIds = n;
images = nImages;
listeners.fireDataChangeEvent(-1, DataChangedListener.ADDED);
}
public long getSelectedImageId() {
return imageIds[selection];
}
public ImageList(long[] images) {
this.imageIds = images;
this.images = new EncodedImage[images.length];
}
public Image getItemAt(final int index) {
if(images[index] == null) {
images[index] = placeholder;
Util.downloadUrlToStorageInBackground(IMAGE_URL_PREFIX + imageIds[index], "FullImage_" + imageIds[index], new ActionListener() {
public void actionPerformed(ActionEvent evt) {
try {
images[index] = EncodedImage.create(Storage.getInstance().createInputStream("FullImage_" + imageIds[index]));
listeners.fireDataChangeEvent(index, DataChangedListener.CHANGED);
} catch(IOException err) {
err.printStackTrace();
}
}
});
}
return images[index];
}
public int getSize() {
return imageIds.length;
}
public int getSelectedIndex() {
return selection;
}
public void setSelectedIndex(int index) {
WebServiceProxy.getPhotoLikesAsync(imageIds[selection], new Callback<Integer>() {
public void onSucess(Integer value) {
if(likeCount != null) {
likeCount.setText("" + value);
likeCount.getParent().revalidate();
}
}
public void onError(Object sender, Throwable err, int errorCode, String errorMessage) {
}
});
selection = index;
}
public void addDataChangedListener(DataChangedListener l) {
listeners.addListener(l);
}
public void removeDataChangedListener(DataChangedListener l) {
listeners.removeListener(l);
}
public void addSelectionListener(SelectionListener l) {
}
public void removeSelectionListener(SelectionListener l) {
}
public void addItem(Image item) {
}
public void removeItem(int index) {
}
}
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);
}
}
First, let me discuss the data model :
I've compiled an HL7 definition in an tree-like object model, with 3 levels : Segment, Element and Subelement. The segment contains a List, and the Element contains a List. The relationship between those is kinda like "Continent-Country-City"
Second, i've created a GUI component composed of 3 combobox, each having a different ComboBoxModel accessing the object model described earlier.
I've applied the Observer pattern on the ComboBoxModel, so that each model is aware of which List<> it has to access :
- the HL7DefinitionElementModel observes the HL7DefinitionSegmentModel
- the HL7DefinitionSubelementModel observes the HL7DefinitionElementModel
When you pick a value in the first combobox (the Segment), the HL7DefinitionElementModel updates and store which Segment have been picked. The data is correctly stored. The first value is correctly displayed in the "closed" JComboBox.
But when you try to open that JComboBox (the Element), if the number of elements is too big (around 12), the items in the invoked Popup Menu are blank.
I'll gladly post any code.
I could send the entire project if needed, but haven't been able to reduce it to a SSCEE.
EDIT : here is an illustration of the problem
(http://imgur.com/AiJ8mu0)
EDIT : here is an SSCCE :
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Observable;
import java.util.Observer;
import java.util.Vector;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.event.ListDataListener;
public class Application extends JFrame {
/**
*
*/
private static final long serialVersionUID = -2517616123689799182L;
public Application() {
add(new HL7FieldSelector());
}
public static void main(String[] args) {
Application a = new Application();
a.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
a.setSize(new Dimension(400, 100));
a.setVisible(true);
}
}
class HL7FieldSelector extends JPanel implements ActionListener {
/**
*
*/
private static final long serialVersionUID = 6135074279269049773L;
private JComboBox<HL7DefinitionSegment> segment;
private JComboBox<HL7DefinitionElement> element;
private HL7DefinitionSegmentModel segmentModel;
private HL7DefinitionElementModel elementModel;
public HL7FieldSelector() {
segment = new JComboBox<HL7DefinitionSegment>();
element = new JComboBox<HL7DefinitionElement>();
segmentModel = new HL7DefinitionSegmentModel();
elementModel = new HL7DefinitionElementModel();
segmentModel.addObserver(elementModel);
segment.setModel(segmentModel);
element.setModel(elementModel);
segment.addActionListener(this);
element.addActionListener(this);
initUI();
}
private void initUI() {
this.setLayout(new GridLayout(1, 3));
this.add(segment);
this.add(element);
this.setMinimumSize(new Dimension(500, 30));
this.setPreferredSize(new Dimension(500, 30));
this.setMaximumSize(new Dimension(500, 30));
segment.setMaximumRowCount(10);
element.setMaximumRowCount(10);
}
#Override
public void actionPerformed(ActionEvent arg0) {
setVisible(!isVisible());
setVisible(!isVisible());
}
}
class HL7DefinitionSegment implements Comparable<HL7DefinitionSegment>, Serializable {
/**
*
*/
private static final long serialVersionUID = 7922798285885405647L;
private List<HL7DefinitionElement> elements;
private String name;
public HL7DefinitionSegment(String name, ArrayList<HL7DefinitionElement> elements) {
this.name = name;
this.elements = elements;
}
public HL7DefinitionSegment() {
elements = new Vector<HL7DefinitionElement>();
}
public boolean addElement(HL7DefinitionElement element) {
return elements.add(element);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<HL7DefinitionElement> getElements() {
return elements;
}
#Override
public String toString() {
return this.name;
}
#Override
public int compareTo(HL7DefinitionSegment segment) {
return name.compareTo(segment.getName());
}
}
class HL7DefinitionElement implements Comparable<HL7DefinitionElement>, Serializable {
/**
*
*/
private static final long serialVersionUID = -5344721929162039227L;
private String name;
private String number;
public HL7DefinitionElement(String name, String number) {
this.name = name;
this.number = number;
}
public HL7DefinitionElement() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getNumber() {
return number;
}
public void setNumber(String number) {
this.number = number;
}
#Override
public String toString() {
return number + " " + name;
}
#Override
public int compareTo(HL7DefinitionElement element) {
return number.compareTo(element.getNumber());
}
}
class HL7DefinitionModel implements Serializable {
/**
*
*/
private static final long serialVersionUID = -8180582371776223436L;
private List<HL7DefinitionSegment> segments;
private static HL7DefinitionModel _instance;
public HL7DefinitionModel(Vector<HL7DefinitionSegment> segments) {
this.segments = segments;
}
public HL7DefinitionModel() {
segments = new Vector<HL7DefinitionSegment>();
}
public boolean addSegment(HL7DefinitionSegment segment) {
return segments.add(segment);
}
public List<HL7DefinitionSegment> getSegments() {
return segments;
}
public void setSegments(List<HL7DefinitionSegment> segments) {
this.segments = segments;
}
public static HL7DefinitionModel getInstance() {
if (_instance == null) {
_instance = new HL7DefinitionModel();
for (int i = 0; i < 2; i++) {
HL7DefinitionSegment s = new HL7DefinitionSegment();
s.setName("SEG" + i);
for (int j = 0; j < 20; j++) {
s.addElement(new HL7DefinitionElement("" + j + j + j, "" + j));
}
_instance.addSegment(s);
}
}
return _instance;
}
}
class HL7DefinitionSegmentModel extends Observable implements javax.swing.ComboBoxModel<HL7DefinitionSegment> {
private HL7DefinitionModel model;
private HL7DefinitionSegment selectedItem;
public HL7DefinitionSegmentModel() {
this.model = HL7DefinitionModel.getInstance();
this.selectedItem = getElementAt(0);
}
public void addListDataListener(ListDataListener l) {}
public void removeListDataListener(ListDataListener l) {}
#Override
public HL7DefinitionSegment getElementAt(int index) {
return model.getSegments().get(index);
}
#Override
public int getSize() {
return model.getSegments().size();
}
#Override
public Object getSelectedItem() {
return selectedItem;
}
#Override
public void setSelectedItem(Object anItem) {
this.selectedItem = (HL7DefinitionSegment) anItem;
setChanged();
notifyObservers();
}
}
class HL7DefinitionElementModel extends Observable implements javax.swing.ComboBoxModel<HL7DefinitionElement>, Observer {
private HL7DefinitionSegment segment;
private HL7DefinitionElement selectedItem;
public HL7DefinitionElementModel() {
this.segment = null;
}
#Override
public void update(Observable o, Object arg1) {
if (o instanceof HL7DefinitionSegmentModel) {
segment = (HL7DefinitionSegment) ((HL7DefinitionSegmentModel) o).getSelectedItem();
setSelectedItem(getElementAt(0));
}
}
public void addListDataListener(ListDataListener l) {}
public void removeListDataListener(ListDataListener l) {}
#Override
public HL7DefinitionElement getElementAt(final int index) {
return segment.getElements().get(index);
}
#Override
public int getSize() {
return (segment != null ? segment.getElements().size() : 0);
}
#Override
public Object getSelectedItem() {
return selectedItem;
}
#Override
public void setSelectedItem(Object anItem) {
this.selectedItem = (HL7DefinitionElement) anItem;
setChanged();
notifyObservers();
}
}
To reproduce the error :
- Launch the application
- Click on second combobox
- Click on the first, select option 2
- Click on second combobox
- Notice how the list is empty
if you do :
- Launch the application
- Click on the first combobox, select option 2
- Click on second combobox
- Notice how the list is populated
EDIT : Here is something else that I've found : The popup menu is only blank if there is more elements in it than in the initial "fill". That is, if the combobox is filled with 20 elements in the first pick, on the next picks the popup menu will be blank if the number of elements is 21 or more, displayed if the number of elements is 20 or less.
I am struggling a long time now with the following problem:
I do not understand why the method setStatus () does not work. The method setButton () check whether the individual elements in a field "status" to "true" if all this have to activate a button. Unfortunately staus field values do not change.
I'd like to add, that textBox.setTitle() and textBox.addStyleName work well.
Here is my code:
package com.mg;
import java.util.ArrayList;
import com.comarch.spr.client.UserServlet;
import com.comarch.spr.client.UserServletAsync;
import com.gargoylesoftware.htmlunit.javascript.host.Window;
import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.BlurEvent;
import com.google.gwt.event.dom.client.BlurHandler;
import com.google.gwt.event.dom.client.KeyPressEvent;
import com.google.gwt.event.dom.client.KeyPressHandler;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.TextBox;
public class Validator {
private ArrayList<ValidationElement> listOfElements = new ArrayList<Validator.ValidationElement>();
private Button button;
public int i = 0;
public Validator(ArrayList<ValidationElement> listOfElements, Button button) {
this.listOfElements = listOfElements;
this.button = button;
}
public void createElement0(TextBox textbox, String regex, int type, UserServletAsync userServlet ) {
ValidationElement element = new ValidationElement(textbox, regex, type, userServlet);
listOfElements.add(element);
}
public void createElement1(TextBox textbox, String regex, int type ) {
ValidationElement element = new ValidationElement(textbox, regex, type);
listOfElements.add(element);
}
public void addElement(ValidationElement element) {
listOfElements.add(element);
}
public void setButton() {
int sizeOfList = listOfElements.size();
int check = 0;
for(ValidationElement element : listOfElements) {
element.validate();
if (element.status == true)
check = check + 1;
}
if (check != 0)
button.setEnabled(true);
else
button.setEnabled(false);
}
//========================== VALIDATION ELEMENT CLASS===========================
public class ValidationElement {
public TextBox textBox = new TextBox();
public boolean status;
public String regex;
public int type; // 0 - validateFromDatabase() , || 1 - checkRegEx()
public UserServletAsync userServlet;
public ValidationElement(TextBox textbox, String regex, int type ) {
this.textBox = textbox;
this.regex = regex;
this.type = type;
}
public ValidationElement(TextBox textbox, String regex, int type, UserServletAsync userServlet) {
this.textBox = textbox;
this.regex = regex;
this.type = type;
this.userServlet = userServlet;
}
public boolean isStatus() {
return status;
}
public void setStatus(boolean status) {
this.status = status;
}
//========================== VALIDATION FUNCTIONS =========================================
public void validate() {
if (type == 0) {
validateFromDatabase();
}
else if(type == 1)
checkRegEx();
}
public void validateFromDatabase() {
textBox.addBlurHandler(new BlurHandler() {
#Override
public void onBlur(BlurEvent event) {
final String text = textBox.getText();
userServlet.validateUser(textBox.getText(), new AsyncCallback<Boolean>() {
public void onFailure(Throwable caught) {
}
public void onSuccess(Boolean result) {
boolean resultOfCallback = result;
if(resultOfCallback){
textBox.addStyleName("invalid");
textBox.setTitle("error");
}
else if (text.matches(regex) && !resultOfCallback) {
textBox.setStyleName("gwt-TextBox");
setStatus(true);
}
else if (text.matches(regex) && resultOfCallback) {
}
else {
textBox.addStyleName("invalid");
textBox.setTitle("error");
}
}
});
}
});
}
public void checkRegEx () {
textBox.addKeyPressHandler(new KeyPressHandler() {
#Override
public void onKeyPress(KeyPressEvent event) {
final String text = textBox.getText();
if(!text.matches(regex))
{
setStatus(false);
}
else {
setStatus(true);
}
isValid();
}
});
textBox.addBlurHandler(new BlurHandler() {
public void onBlur(BlurEvent event) {
final String text = textBox.getText();
if(!text.matches(regex))
{
textBox.addStyleName("invalid");
textBox.setTitle("error");
setStatus(false);
}
else
{
setStatus(true);
textBox.setStyleName("gwt-TextBox");
textBox.setTitle("error");
}
}
});
}
public void isValid() {
if(this.status = true)
i=i+1;
}
}
}
public void isValid() {
if(this.status = true)
i=i+1;
}
Shouldn't this be
public void isValid() {
if(this.status == true)
i=i+1;
}
The first method sets the status to true, then always increments i.
Or you can go one better:
public void isValid() {
if(this.status) {
i++;
}
}
Also if you're using Eclipse, you can set the compiler options to highlight accidental boolean assignments in if statements as compilation errors. It's a very useful option.
In class Validator, there is:
public int i = 0;
In class ValidationElement, which is inside Validator, in for example checkRegEx() method, I put:
i++;
public void checkRegEx () {
textBox.addKeyPressHandler(new KeyPressHandler() {
#Override
public void onKeyPress(KeyPressEvent event) {
final String text = textBox.getText();
if(!text.matches(regex))
{
setStatus(false);
}
else {
setStatus(true);
}
isValid();
}
});
textBox.addBlurHandler(new BlurHandler() {
public void onBlur(BlurEvent event) {
final String text = textBox.getText();
if(!text.matches(regex))
{
textBox.addStyleName("invalid");
textBox.setTitle("error");
setStatus(false);
}
else
{
textBox.setStyleName("gwt-TextBox");
textBox.setTitle("ok");
i++;
}
}
});
}
When I want to see if everything is okay. It turns out that the value of "i" in setButton() method in class Validator is still zero, while the other lines of code in this block work well:
textBox.setStyleName("gwt-TextBox");
textBox.setTitle("ok");
Most likely it's just too early to check status at the next line after validate() is invoked.
As far as I can tell status changes only after BlurEvent happens. TextBox updates you mention work fine because these seem to be triggered at the right moment.
public void setButton() {
int sizeOfList = listOfElements.size();
int check = 0;
button.setEnabled(false); // reset button
for(ValidationElement element : listOfElements) {
element.validate();
// drop attempt to check status here
}
// drop attempt to enable button here
}
//...
{
button.setEnabled(true); // was: setStatus(true);
textBox.setStyleName("gwt-TextBox");
textBox.setTitle("error");
}
Also I would prefer to define ValidationElement
static:
public static class ValidationElement
// have quite a painful experience chasing subtle bugs
// related to named non-static inner classes