So i need to update the jList dynamically. I read that i have to use a fireData.... or something similar depending on the api.
I'm using AbstractListModel for the jList in the following code.
/*private javax.swing.JList<String> anycast_users;*/
void set_user_model(){
anycast_users.setModel(new javax.swing.AbstractListModel<String>() {
#Override
public int getSize() {
return anycast_users_num;
}
#Override
public String getElementAt(int i) {
return anycast_users_list[i];
}
});
/*listeners and other init..*/
The AbstractListNodel has this function
protected void fireContentsChanged(Object source,
int index0,
int index1)
So, to update the list i initially tried
anycast_users.getModel(). //in a different function (updateList())
But as fireContentsChanged is protected i can't call it from here.
So, how to call the function correctly, and what additional changes is required?
I would create a method in that class where you have access to the model and make something like this:
public ListModel<String> getAnycastUsersModel() {
return this.anycast_users.getModel();
}
This way you can access it from wherever you want to.
And I would suggest you using DefaultListModel if you're able to :)
I'm going with a solution suggested by MadProgrammer.
class anycastModel extends javax.swing.AbstractListModel<String>{
#Override
public int getSize() {
return anycast_users_num;
}
#Override
public String getElementAt(int i) {
return anycast_users_list[i];
}
public void updateList(){
fireContentsChanged(this, 0, anycast_users_num);
}
}
void set_usermodel(){
anycast_model=new anycastModel();
anycast_users.setModel(anycast_model);
}
For updating
anycast_model.updateList();
Related
I tried to use DiffUtil approach to update my list which always consist of 30 items, now each item data updates every minute but no way of telling if all item's data will have updates so to avoid abusing notifyDataSetChanged() I create a class extending DiffUtil.
public class DifUtil extends DiffUtil.Callback {
private final List<Asset> oldList, newList;
public DifUtil(List<Asset> newList, List<Asset> oldList) {
this.oldList = oldList;
this.newList = newList;
}
#Override
public int getOldListSize() {
return oldList.size();
}
#Override
public int getNewListSize() {
return newList.size();
}
#Override
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
return oldList.get(oldItemPosition).getId().equals(newList.get(newItemPosition).getId());
}
#Override
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
return oldList.get(oldItemPosition).equals(newList.get(newItemPosition));
}
#Nullable
#Override
public Object getChangePayload(int oldItemPosition, int newItemPosition) {
//you can return particular field for changed item.
return super.getChangePayload(oldItemPosition, newItemPosition);
}
}
Add new public function to notify the adapter with this
public void updateList(List<Asset> newList) {
DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new DifUtil(newList, this.assetList));
this.assetList.clear();
this.assetList.addAll(newList);
diffResult.dispatchUpdatesTo(this);
}
Overriding another onBindViewHolder (not sure if needed when not using payload)
onBindViewHolder(#NonNull AssetsAdapter.ViewHolder holder, int position, #NonNull List<Object> payloads)
Then updating the list by just calling
adapter.updateList(newAssetList);
The updating of list works but I can only see those new values by scrolling the list, I need to view the updates even without recycling the view (when scrolling) just like notifyItemChanged().
To my understanding calling dispatchUpdatesTo should handle and update the views and its data or am I missing something here please enlighten me.
I managed to make it work by following this blog and its way of implementing DiffUtil.
https://www.journaldev.com/20873/android-recyclerview-diffutil
However I did not perform the cloning part since it is irrelevant on my case.
BONUS
There is a known bug where updates makes the list to scroll at the bottom. I get rid of this by saving scroll state with LayoutManager like in this SO answer.
https://stackoverflow.com/a/44053550/11338467
So my adapter DiffUtil update part will be this
public void updateList(List<Asset> newList) {
Parcelable recyclerViewState = layoutManager.onSaveInstanceState();
DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new DifUtil(newList, this.assetList));
diffResult.dispatchUpdatesTo(this);
this.assetList.clear();
this.assetList.addAll(newList);
layoutManager.onRestoreInstanceState(recyclerViewState);
}
In my program, I have a jList and I can add, delete, modify items in this Jlist.
My problem is, if I click on my add button before selecting an item in my jList, the items inside the jList disapear. (only in apeareance because they are actually still in the jList)
If, before that, I select an item in my list, then everything is working fine. So my guess would be that the "valueChanged()" method from my listener is doing something that I don't do myself.
Here is my list initialisation, which I call at the start of the program:
public final void initList() {
jListPaiement.setModel(new MyListModel(ls.getDb().getListePaiements()));
final DecimalFormat df = new DecimalFormat("###.##");
jListPaiement.addListSelectionListener(new ListSelectionListener() {
#Override
public void valueChanged(ListSelectionEvent lse) {
MoyenPaiement mp = (MoyenPaiement) ((MyListModel) jListPaiement.getModel()).getElementAt(jListPaiement.getSelectedIndex());
jTextFieldFF.setText(df.format(mp.getFraisf()));
jTextFieldFV.setText(df.format(mp.getFraisv() * 100));
jTextFieldNomP.setText(mp.getNom());
jTextFieldFF.setVisible(true);
jTextFieldFV.setVisible(true);
jTextFieldNomP.setVisible(true);
jLabel1.setVisible(true);
jLabel6.setVisible(true);
jLabel7.setVisible(true);
jLabel8.setVisible(true);
jLabel11.setVisible(true);
jButtonSaveP.setVisible(true);
}
});
Here is the code from the add button:
private void jButtonAddPActionPerformed(java.awt.event.ActionEvent evt) {
MoyenPaiement mp = new MoyenPaiement("Nouveau", 0, 0);
((MyListModel) jListPaiement.getModel()).addElement(mp);
jListPaiement.setSelectedValue(mp, true);
jListPaiement.repaint();
}
MyListModel code:
public class MyListModel extends AbstractListModel {
ArrayList list;
public MyListModel(ArrayList list) {
this.list = list;
}
#Override
public int getSize() {
return list.size();
}
#Override
public Object getElementAt(int i) {
return list.get(i);
}
public void addElement(Object o){
list.add(o);
}
public void deleteElement(Object o){
list.remove(o);
}
public void setElement(int i,Object o){
list.set(i, o);
}
public ArrayList getList() {
return list;
}
public void setList(ArrayList list) {
this.list = list;
}
}
Any help will be greatly appreciated.
Thanks
Edit: After further research, the problem is when I add item to my model.
It comes exactly on the line:
((MyListModel) jListPaiement.getModel()).addElement(mp);
Even if I add a simple string such as:
((MyListModel) jListPaiement.getModel()).addElement("String");
The problem still occurs.
Look in detail what happens on this line and if you initialize jListPaiement correctly with the right data.
jListPaiement.setModel(new MyListModel(ls.getDb().getListePaiements()));
Seems like on this line setSelectedValue() can't find the element mp
jListPaiement.setSelectedValue(mp, true);
I finally found a solution.
Rather than using my own List Model, I used DefaultListModel and everything works fine. It's been long time since i worked on this project and I don't remember why i chose to make my own list model class.
Even tough it works now, I still don't understand what was missing in my own class (MyListModel) that made it not working..
I have researched and attempted to fill a jcombobox dynamically from an arraylist containing Publisher Objects. I have tried to implement a renderer in order to show the Publishers name using the getName() method. The combobox shows the names when the program is run, however, if a new Publisher is then added to the ArrayList, the combobox becomes blank.
Creating Model:
public class PublisherComboBoxModel implements ComboBoxModel{
protected List<Publisher> publishers;
public PublisherComboBoxModel(List<Publisher> list) {
this.listeners = new ArrayList();
this.publishers = list;
if(list.size() > 0) {
selected = list.get(0);
}
}
protected Object selected;
#Override
public void setSelectedItem(Object item) {
this.selected = item;
}
#Override
public Object getSelectedItem() {
return this.selected;
}
#Override
public Object getElementAt(int index) {
return publishers.get(index);
}
#Override
public int getSize() {
return publishers.size();
}
protected List listeners;
#Override
public void addListDataListener(ListDataListener l) {
listeners.add(l);
}
#Override
public void removeListDataListener(ListDataListener l) {
this.listeners.remove(l);
}
}
Creating renderer:
jComboBoxPublisher.setModel(publisherComboModel);
jComboBoxPublisher.setRenderer(new DefaultListCellRenderer() {
#Override
public Component getListCellRendererComponent(JList list,
Object value,
int index,
boolean isSelected,
boolean cellHasFocus) {
Publisher publisher = (Publisher)value;
if(value!=null)
{
value = publisher.getName();
}
return super.getListCellRendererComponent(list, value,
index, isSelected, cellHasFocus);
}
This is not necessarily an answer, but highlights a potential problem
While skimming over your code, I noticed that you combo box model is simply maintaining a reference to the original list. This isn't necessarily a problem, but may result in some unexpected and potentially, unwanted behaviour...
The main problem, is that the combo box model has no idea when the list is changed, therefore it can't tell combo box that it should updated.
Generally, what I would normally do is make a new List of the original list. This means that if the original is updated, it won't cause issues for the model and and combo box.
I would then add mutation functionality to the combo box model so it could be updated, for example...
public class PublisherComboBoxModel extends AbstractListModel implements ComboBoxModel {
private List<Publisher> publishers;
private Publisher selectedItem;
public PublisherComboBoxModel(List<Publisher> publishers) {
this.publishers = new ArrayList<>(publishers);
}
public void addPublisher(Publisher pub) {
publishers.add(pub);
fireIntervalAdded(this, publishers.size() - 1, publishers.size() - 1);
}
#Override
public int getSize() {
return publishers.size();
}
#Override
public Object getElementAt(int index) {
return publishers.get(index);
}
#Override
public void setSelectedItem(Object anItem) {
selectedItem = (Publisher) anItem;
}
#Override
public Object getSelectedItem() {
return selectedItem;
}
}
There are several alternatives to this idea. You could create a "general" model, which listed the publishers, but provided event notification to interested parties, so when you added or removed publishers from this model, interested parties, like the combo box model, would be notified and have an opportunity to update themselves and forward appropriate notifications to their interested parties.
Personally, in larger scaled applications, this is my preferred approach.
Another approach would be to provide the combo box model with direct notification...
Thats, you would maintain a reference to the existing list as you are, but the combo box model would have methods that you could call which it could then forward on.
I have a class called ErrorHighlighter which gets notified anytime a property called errorString is changed. Based on this propertychangeevent I update the HighLighterPredicate to highlight a particular row with a red background.
ErrorHighlighter receives the propertychangeevent, it also changes the HighlighterPredicate, but the table row does not get updated with red background.
I also update the tooltip of the row. That does not get reflected either.
Please see the code below. Could someone please help?
public class ErrorRowHighlighter extends ColorHighlighter implements PropertyChangeListener {
private Map<Integer, String> rowsInError;
private SwingObjTreeTable<ShareholderHistoryTable> treeTable;
public ErrorRowHighlighter(SwingObjTreeTable<ShareholderHistoryTable> treeTable) {
super(CommonConstants.errorColor, null);
this.treeTable = treeTable;
rowsInError=new HashMap<Integer, String>();
setHighlightPredicate(new HighlightPredicate() {
#Override
public boolean isHighlighted(Component renderer, ComponentAdapter adapter) {
if(rowsInError.containsKey(adapter.row)){
return true;
}
return false;
}
});
this.treeTable.addMouseMotionListener(new MouseMotionAdapter() {
#Override
public void mouseMoved(MouseEvent e) {
int row=ErrorRowHighlighter.this.treeTable.rowAtPoint(e.getPoint());
if(rowsInError.containsKey(row)){
ErrorRowHighlighter.this.treeTable.setToolTipText(rowsInError.get(row));
}else{
ErrorRowHighlighter.this.treeTable.setToolTipText(null);
}
}
});
}
public void highlightRowWithModelDataAsError(ShareholderHistoryTable modelData){
int indexForNodeData = treeTable.getIndexForNodeData(modelData);
if(indexForNodeData>-1){
rowsInError.put(indexForNodeData, modelData.getErrorString());
updateHighlighter();
}
}
public void unhighlightRowWithModelDataAsError(ShareholderHistoryTable modelData){
int indexForNodeData = treeTable.getIndexForNodeData(modelData);
if(indexForNodeData>-1){
rowsInError.remove(indexForNodeData);
updateHighlighter();
}
}
public void updateHighlighter(){
treeTable.removeHighlighter(this);
treeTable.addHighlighter(this);
}
#Override
public void propertyChange(PropertyChangeEvent evt) {
ShareholderHistoryTable sourceObject= (ShareholderHistoryTable) evt.getSource();
if(StringUtils.isNotEmpty(sourceObject.getErrorString())){
highlightRowWithModelDataAsError(sourceObject);
}else{
unhighlightRowWithModelDataAsError(sourceObject);
}
}
}
This looks like a mistake on my part. The method treeTable.getIndexForNodeData() actually returns back the index of the row by doing a pre-order traversal of the underlying tree data structure. This includes a root node that is not being displayed on the jxtreetable. Hence I needed to minus 1 from the index
int indexForNodeData = treeTable.getIndexForNodeData(modelData)-1;
This fixed the problem for me. I am leaving the post rather than deleting it if anyone wants to look at an example of a ColorHighlighter and a property change listener.
I follow the Presentation Model pattern for coding some screens.
I store some Beans in an ArrayList
I will display the content of this List in a JTable, thanks to an AbstractTableModel
I also want to display some records from this list in a Combo Box (in a form) and some others in a JList, at the same time
These three screens (and their model) are independent from each other
How to manage add {one or more}/remove {one or more} on my List and view changes in "real-time" everywhere?
I'm about to write my own ObservableList or implement that around an EventDispatcher... What do you think?
PS:
I know that in C# the BindingList helps for that purpose, what about Java?
I'm already able to display updates of each bean, thanks to PropertyChangeSupport.
Let your AbstractTableModel implement ListModel, which is usable with both JComboBox andJList. You can forward methods to the default model implementations as required.
Addendum: SharedModelDemo, mentioned in How to Use Tables, is an example that may get you started. It extends DefaultListModel implements TableModel, while you should do extends AbstractTableModel implements ListModel
Addendum: For reference, here's an outline of the minimal implementation and three test instantiations. I've used the default combo and list implementations, but you can use the corresponding abstract implementations if necessary.
public class SharedModel extends AbstractTableModel
implements ComboBoxModel, ListModel {
private ComboBoxModel comboModel = new DefaultComboBoxModel();
private ListModel listModel = new DefaultListModel();
//ComboBoxModel
#Override
public void setSelectedItem(Object anItem) {
comboModel.setSelectedItem(anItem);
}
#Override
public Object getSelectedItem() {
return comboModel.getSelectedItem();
}
// ListModel
#Override
public int getSize() {
return listModel.getSize();
}
#Override
public Object getElementAt(int index) {
return listModel.getElementAt(index);
}
#Override
public void addListDataListener(ListDataListener l) {
listModel.addListDataListener(l);
}
#Override
public void removeListDataListener(ListDataListener l) {
listModel.removeListDataListener(l);
}
// TableModel
#Override
public int getRowCount() {
return 0;
}
#Override
public int getColumnCount() {
return 0;
}
#Override
public Object getValueAt(int rowIndex, int columnIndex) {
return null;
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
SharedModel sm = new SharedModel();
JTable table = new JTable(sm);
JList list = new JList(sm);
JComboBox check = new JComboBox(sm);
}
});
}
}
For the JComboBox and the JList you could simply reference sections of the ArrayList using the subList() method. This will work if you can easily identify the starting and ending locations within the ArrayList and the elements you need are sequential.
If the situation is more dynamic than that you could implement custom List classes that took the ArrayList in the constructor and then apply whatever logic you need to return the appropriate records.