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.
Related
In my project, I have a TableViewer which needs to show around 3000 items and also filter them. Without SWT.VIRTUAL the table takes multiple seconds to display.
So, I implemented ILazyContentProvider and not using "ArrayContentProvider" but now I am facing issues in filtering the elements of TableViewer.
I am writing the below code for filtering but the select method is not running at all.
private static class DefaultFilterextends ViewerFilter {
private String searchText;
public void setSearchText(final String searchText) {
this.searchText = searchText;
}
#Override
public boolean select(Viewer viewer, Object parentElement, Object element) {
if (this.searchText == null) {
return true;
}
return (element.toString().contains(searchText) || element.toString().equals(searchText));
}
}
The below ContentProvider, I am writting
private class LazyContentProvider implements ILazyContentProvider {
private TableViewer viewer;
private List<String> elements;
public LazyContentProvider(TableViewer viewer) {
this.viewer = viewer;
}
#Override
public void dispose() {
}
#Override
public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
this.elements = (List<String>) newInput;
}
#Override
public void updateElement(int index) {
viewer.replace(elements.get(index), index);
}
}
Can someone please guide me how can I do the filteration of elements in TableViewer with SWT.VIRTUAL?
TableViewer does not support filtering when SWT.VIRTUAL is used. Instead you must do the filtering in the content provider.
The content provider will need to maintain a filtered list of elements. For example:
public class LazyContentProvider implements ILazyContentProvider
{
private TableViewer viewer;
private List<String> allElements;
private List<String> filteredElements;
public LazyContentProvider()
{
//
}
#Override
public void dispose()
{
//
}
#SuppressWarnings("unchecked")
#Override
public void inputChanged(final Viewer viewer, final Object oldInput, final Object newInput)
{
this.viewer = (TableViewer)viewer;
allElements = (List<String>)newInput;
filteredElements = allElements;
}
#Override
public void updateElement(final int index)
{
viewer.replace(filteredElements.get(index), index);
}
public void filter(final String searchText)
{
filteredElements = allElements.stream().filter(element -> element.contains(searchText)).toList();
viewer.setItemCount(filteredElements.size());
viewer.refresh();
}
}
Here I have added a filteredElements list that contains just the current filtered elements, this is used in updateElement.
I added a filter method which updates the filtered elements list. It also tells the table viewer about the new item count and refreshes the table.
The .toList() method in the filter stream requires Java 16, for older versions of Java use .collect(Collectors.toList()).
I want to filter GXT ComboBox Store. for example if I type 'st' in combobox I want combobox to show only values that contain 'st'
Here is my implementation
combo = new ComboBox<MerchantDTO>(store, label);
StoreFilter<MerchantDTO> filter = new StoreFilter<MerchantDTO>() {
#Override
public boolean select(Store<MerchantDTO> store, MerchantDTO parent, MerchantDTO item) {
boolean canView = (item.getName() != null && item.getName().toLowerCase().contains(combo.getText().toLowerCase()));
return canView;
}
};
store.setEnableFilters(true);
store.addFilter(filter);
This filter works and shows correct values, But combobox's dropdown list does not open automatically. I have to click on combobox manually to open dropdown list and see filtered results. I am using GXT 3.1.0 and GWT 2.7.0
I tried using combo.expand(); function but it didnt open dropdown list.
Any help would be appreciated.
I found solution. Here is sample how to add custom filter to GXT (version 3.1.0) ComboBox
1) Create class which extends ListStore and add String variable for user input text
public abstract class XListStore<M> extends ListStore<M> {
private String userText;
public XListStore(ModelKeyProvider<? super M> keyProvider) {
super(keyProvider);
}
#Override
protected boolean isFilteredOut(M item) {
return filter(item);
}
public abstract boolean filter(M item);
public String getUserText() {
return userText;
}
public void setUserText(String userText) {
this.userText = userText;
}
}
2) Initialize custom list store and implement filter method
XListStore<SampleDTO> store = new XListStore<SampleDTO>(new ModelKeyProvider<SampleDTO>() {
#Override
public String getKey(SampleDTO item) {
return item.getId();
}
}) {
public boolean filter(SampleDTO item) {
boolean result = false;
//Write you filter logic here
return result;
}
};
store.setEnableFilters(true);
3) Initialize ComboBox and add Key up handler
ComboBox<SampleDTO> comboBox = new ComboBox<SampleDTO>(store, label);
comboBox.addKeyUpHandler(new KeyUpHandler() {
#Override
public void onKeyUp(KeyUpEvent event) {
store.setUserText(comboBox.getText());
}
});
Done. Now ComboBox will filter store according to user input and will open dropdown window automatically
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 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.