New to the forum and to Java. I am trying to have my JList respond when double-clicked, which I have accomplished. The JList is being populated by a SQL query which is ran when a button in the GUI is pressed. Based on the SQL query, the JList is populated, this is also working.
The issue comes about if I try to update the JList by clicking the button to query SQL again. When I click that, the change initially shows up in the JList, however when I click on that option in the JList it immediately switches back to what it was initially. When I double-click on what appears to be the incorrect name, the value that I have printing in the console reports correctly. So it has the value correct in the console but the rendering in the JList is not correct.
I appreciate any responses, I have combed the forums without any luck. I am new to Java so I'm sure there is quite a bit that isn't perfect with my code. Code is below please let me know if you need more. Thank you.
public JPanel results(StringBuilder message)
{
StringBuilder[] options = {message};
showOption = new JList(options);
showOption.setLocation(300, 50);
showOption.setSize(140,100);
showOption.setVisibleRowCount(10);
textPanel.add(showOption);
showOption.revalidate();
showOption.repaint();
MouseListener mouseListener = new MouseAdapter() {
public void mouseClicked(MouseEvent e) {
//JList showOption = (JList) mouseEvent.getSource();
if (e.getClickCount() == 2) {
int index = showOption.locationToIndex(e.getPoint());
Object o = showOption.getModel().getElementAt(index);
System.out.println("Double-clicked on: " + o.toString());
}
}
};
showOption.addMouseListener(mouseListener);
return totalGUI;
}
public static void main ( String args[] )
{
//JFrame.setDefaultLookAndFeelDecorated(true);
JFrame frame = new JFrame("[=] JTextField of Dreams [=]");
GUI_TextField demo = new GUI_TextField();
frame.setContentPane(demo.createContentPane());
//frame.setContentPane(demo.results(message));
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(510, 400);
frame.setVisible(true);
}
Three things jump out at me immediately.
You're creating a new JList each time
You're manually setting the size and position of the JList
You're not removing the previous JList
For example...
public JPanel results(StringBuilder message)
{
StringBuilder[] options = {message};
// Create new JList
showOption = new JList(options);
// This is ill advised
showOption.setLocation(300, 50);
showOption.setSize(140,100);
showOption.setVisibleRowCount(10);
// What about the last JList?
textPanel.add(showOption);
This raises a number of possibilities, the likely one is that you are covering over the previous list, which is being brought to the front when textPanel is validated and painted.
Swing follows (loosly) the MVC paradigm (and for more details)
So instead of re-creating the view each time, you should simply re-create the model, for example...
public JPanel results(StringBuilder message)
{
DefaultListModel model = new DefaultListModel();
model.addElement(message);
showOption.setModel(model);
If showOption isn't created initially before this method is called, you should consider putting in a if statement to detect when showOption is null and initialise it appropriately.
You should also avoid using setLocation and setSize. Swing has being designed to operate with the use of layout managers, these make it possible to define workflow and general layout that can be used across multiple platforms.
Take a look at How to use lists and Laying Out Components Within a Container
Related
I wanted the information to be shown when a radio button is clicked. The program look like this program. When I click one of the radio button and press ENTER, I should be getting the result on the right textfields but It seems like there was nothing displayed on the right textfields when i choose a radio button. There was no error in my code though. Other than that, I was able to choose both of the buttons, how can I set it as only one of the radio button will be true?
public class Q2 extends JFrame implements ItemListener{...}
public Q2()
{
Font ft1 = new Font ("Dialog", Font.BOLD+Font.ITALIC,30);
Font ft2 = new Font ("Arial", Font.BOLD,15);
lb2 = new JLabel("Calculate fee by:");
lb2.setFont(ft1);
rb1 = new JRadioButton("Kilometer (RM0.95 per/km)");
lb1 = new JLabel("Welcome to MyTeksiSapu");
lb1.setForeground(Color.WHITE);
lb1.setFont(ft1);
lb3 = new JLabel("Distance to destination(km): ");
tf1 = new JTextField(10);
rb2 = new JRadioButton("Time (RM0.35 per/min)");
lb4 = new JLabel("Distance to destination(mins): ");
tf2 = new JTextField(10);
lb5 = new JLabel("Calculated by: ");
tf3 = new JTextField(10);
tf3.setFont(ft2);
tf3.setForeground(Color.red);
tf3.setOpaque(false);
lb6 = new JLabel("Total: RM ");
tf4 = new JTextField(10);
tf4.setFont(ft2);
tf4.setForeground(Color.red);
tf4.setOpaque(false);
JPanel p1 = new JPanel();
p1.add(lb1);
p1.setBackground(Color.GREEN);
JPanel subp2 = new JPanel();
subp2.setLayout(new GridLayout(2,3));
subp2.add(rb1);subp2.add(lb3);subp2.add(tf1);
subp2.add(rb2);subp2.add(lb4);subp2.add(tf2);
JPanel p2 = new JPanel();
p2.setLayout(new GridLayout(2,1));
p2.add(lb2);
p2.setBackground(Color.lightGray);
p2.add(subp2);
JPanel subp3 = new JPanel();
subp3.setLayout(new GridLayout(2,2));
subp3.add(lb5);subp3.add(tf3);
subp3.add(lb6);subp3.add(tf4);
JPanel p3 = new JPanel();
p3.setLayout(new GridLayout(2,2));
ImageIcon icon = new ImageIcon(getClass().getResource("taxicar.jpg"));
JLabel taxicar = new JLabel(icon);
p3.add(taxicar);
p3.setBackground(Color.white);
p3.add(subp3);
add(p1,BorderLayout.NORTH);
add(p2,BorderLayout.CENTER);
add(p3,BorderLayout.EAST);
}
public void itemStateChanged(ItemEvent e)
{
DecimalFormat df = new DecimalFormat("0.00");
double km = Double.parseDouble(tf1.getText());
double mins = Double.parseDouble(tf2.getText());
double total = 0;
if(rb1.isSelected())
{
tf3.setText("Meter");
total = 0.95*km;
tf3.setText(df.format(total));
}
if(rb2.isSelected())
{
tf3.setText("Time");
total = 0.95*mins;
tf3.setText(df.format(total));
}
}
Beyond the simple functionality of an application (works vs. doesn't work), there are general guidelines and principles that should be observed to ensure the best possible performance is achieved when creating an application. I have a few observations on what's wrong with this application beyond the OP's question
Responsibility of components
The first mistake I found with the OP's implementation was with the assigned responsibilities of the JFrame. In a swing application, the responsibility of the JFrame should be limited to provide main container for the application itself. With these, there are some derived responsibilities that I won't get into. However, I would say that the JFrame is not responsible to act as a middle man between Events and Components. Therefore, in this context, JFrame implements ItemListener is clearly a mistake. When you look at what the itemStateChanged method does, it obvious that it doesn't belong there. Moreover, if the JFrame ever needs to execute some logic based on changes in its own state, the implementation of this method will be forced to add a new responsibility to it; violating SOLID's Single Responsibility Principle. On the other hand, if the implementation of the listener is limited to the radio buttons, it his very unlikely this will change in the future. For now, let's assume that the radio buttons will need to implement ItemListener (although I will argue against this shortly).
To fix this code, you could simply add an anonymous listener to the
buttons directly. Search the web for details on how to do this.
You could also opt to create your own class that extends
JRadioButton where your specialized class can address all of the
dependencies your components will need for your application or
project. For homework (like this one appears to be) this is not
necessary.
A better option to anonymous listeners, is to create a private class
that implements the desired listener.
public class MyClass {
...
private class MyListener implements .... {
// implemented methods here
}
}
Then, your component can simply create an instance of MyListener and use it in as many components that might need it OR create as many instances of MyListener and assigned to specific components, but this looks like anonymous listeners would be better.
Expected behavior of radio buttons
Radio buttons are buttons (remember this point). However, they are a specialized form of buttons. When people see radio buttons, they expect that the selection of a radio button automatically deselects the previously selected buttons. This application doesn't do that. In this example, both radio buttons can be in the selected state at the same time. This behavior is expected from JCheckBox, but not from JRadioButton. In order for radio buttons to activate the behavior I just described, they must be added to a ButtonGroup.
ButtonGroup group = new ButtonGroup();
group.add(rb1);
group.add(rb2);
Once this is done, controlling the deselection of previously selected button in the group is handled automatically. Besides the point of this being how most people expect radio buttons to behave like, this application requires this behavior. Why, because text fields set the text they are supposed to display based on the EXCLUSIVE selection of one of the radio buttons. For this application, one text field is responsible to display whether the taxi fare was calculated based on distance OR time; not both.
Lastly, typically one of the radio buttons will be selected by default. HOWEVER, in this context, I think it is OK for the application to launch with both deselected. Doing this will eliminate the confusion of seeing one selected and no text in the fields that get populated based on radio button's selection.
Event Listeners
Event listeners are at the heart of Event-Driver Programming, which is basically how modern applications are built. Java Swing framework has a very robust set of listeners for almost all situations. In my many years working Swing applications, I don't recall a time where I designed my own listener.
Remember I stated earlier that "radio buttons are buttons" and that I also wrote about "responsibility of components"? I stated that to frame why I oppose the use of ItemListener for triggering the logic of what happens when a radio button is selected. From the ItemSelectable interface (which AbstractButton extends), this interface was created or designed to address the following responsibility:
The interface for objects which contain a set of items for which zero or more can be selected
Based on that, you can see this sounds very close to what ButtonGroup does. If you examine this class, you can see why AbstractButton extends this interface. ButtonGroup needs it in order to "create a multiple-exclusion scope for a set of buttons." This is how radio buttons assigned to a button group get automatically deselected when another one is selected. Could the same listener be used to fire off the logic to update the text fields based on the radio button that is selected? Yes, it can be done this way. But based on the listener's documentation, this wasn't the reason why it was created.
My suggestion is to implement ActionListener instead. This class is not only a general-purpose listener, it is the one recommended by the designers of the Swing framework to handle such events. In this case, my solution is very simple:
private class RadioSelectionListener implements ActionListener {
#Override
public void actionPerformed (ActionEvent e) {
JRadioButton rb = (JRadioButton)e.getSource(); // As the creator, I know this listener is added to radio buttons only. So, the assumption is safe
DecimalFormat df = new DecimalFormat("0.00");
double km = Double.parseDouble(tf1.getText());
double mins = Double.parseDouble(tf2.getText());
double total = 0;
if (rb.getActionCommand().equals("km")) {
tf3.setText("Meter");
total = 0.95 * km;
} else {
tf3.setText("Time");
total = 0.95 * mins;
}
tf4.setText(df.format(total)); // This was set on tf3 in the OP's code which is wrong
}
}
In order for this solution to work, the same instance of ActionListener should be added and an "action command" should be assigned to each button. Although this is not necessary, it makes it much easier to know which of the button is associated with the event's source (as you can see in my code snippet above). Therefore, somewhere in the application, the following lines must be added:
rb1.setActionCommand("km");
rb2.setActionCommand("time");
ActionListener listener = new RadioSelectionListener();
rb1.addActionListener(listener);
rb2.addActionListener(listener);
"Never" extend JFrame
I typed the word never inside quotes because this is not a rule set in stone. This is more of a personal preference, but one I find to be shared by a whole lot of people. Instead, you should create an "app" class that contains a JFrame along with the rest of the components. This "app" should be the class containing the main method to launch your app. The recommended approach to do this is the following:
public class MyApp {
public static void main (String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run () {
createAndShowGUI();
}
private void createAndShowGUI () {
// construct and add your frame and other items here.
}
});
}
}
Summary
Remember Single Responsibility Principle and assign responsibility where it belongs. Here, the components that need to be registered to receive notification when they are clicked are the buttons themselves, not the frame.
Encapsulate behavior and attributes in classes to improve usability. It also improves readability. In this case, the frame should not be a "god class" with hundreds lines of code.
Lastly, implement behaviors that closely match the expectations or mental models of the users. For radio buttons, only one in a group is expected to be selected at any given time. In fact, they got their name from old radios with preset buttons that, when pressed, they would change the station selection. Imagine how bad it would've been if radios allowed multiple stations to be selected at the same time?
There are a few more (minor) issues I would like to address, but this gone long enough... I included screenshots of the application working after my changes.
I am trying to refresh my Jtable shown in the UI whenever I query the mysql database. The idea was to show whatever new data updated in the UI JTable.
The UI class is below.
public class DBView {
private JFrame frame = new JFrame();
private JScrollPane tableScrollPane = new JScrollPane();
private DefaultTableModel dbTable = new DefaultTableModel();
public void setDbTable(DefaultTableModel dbTable) {
this.dbTable = dbTable;
//this.dbTable.repaint();
paintDBTable();
}
public DefaultTableModel getDbTable() {
return dbTable;
}
public DBView() {
initializeFrame();
paintDBTable();
}
private void paintDBTable() {
tableScrollPane.setBounds(20, 350, 400, 80);
frame.getContentPane().add(tableScrollPane);
JTable DBTable = new JTable(dbTable);
tableScrollPane.add(DBTable);
DBTable.setFillsViewportHeight(true);
tableScrollPane.setViewportView(DBTable);
}
/**
* Initialize the contents of the frame.
*/
private void initializeFrame() {
frame.setVisible(true);
frame.setBounds(100, 100, 451, 525);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().setLayout(null);
frame.setTitle("MySQL Database");
}
From another Model class I am calling the setDbTable() method. I input a new Jtable object to the setDbTable() method with data read from the database input into the new Jtable object.
The issue is inside the setDbTable() method, I am using paintDBTable() method again.
I tried using dbTable.fireTableDataChanged() method to refresh the view, didnt work.
The way it is now, it is working. But using the setDbTable() method to refresh seems like a very inefficient way to do it.
Question is Do you see anyway I could use another method defined for use of refreshing Jtables?
P.S. I am very new to java and programming in general. Sorry if the code is messy and the question is unclear. I can give all the code if its helpful. I removed most of the methods and other classes in the original code to make the question clearer.
tableScrollPane.add(DBTable);
JScrollPane isn't designated as container, you have to add child to JViewport
there are two options
a) tableScrollPane = new JScrollPane(myTable);
b) tableScrollPane.setViewportView(myTable);
DefaultTableModel dbTable = new DefaultTableModel();
DefaultTableModel is model that hold value for presentations layer for the JTable
rename this local variable (that make the sence) to dbTableModel instead of dbTable
you have to create a JTables view, f.e. two basics options
a) JTable myTable = new JTable(dbTableModel)
b) myTable.setModel(dbTableModel)
dbTable.fireTableDataChanged() is implemented in DefaultTableModel and correctly, not reason to call this method, nor outside of models definition (class, void, interface that returns XxxTableModel)
more informations in linked Oracle tutorials, ... for working code examples in SSCCE / MCVE form too
refresh data for JTable by
removing all rows in dbTableModel.setRowsCount(0);, then add a new row(s) to dbTableModel.addXxx
re_creating dbTableModel, note then must be added back to JTable e.g. myTable.setModel(dbTableModel)
It is not so confusing to refresh the JTable data and refreshing the UI after that, because:
Swing components implemented MVC and Observer in a very fantastic way. That means whenever you change the data in TableModel, the UI will be notified and repainted as you wanted.
So you should change you code in a way that you keep the JTable variable not the TableModel variable in your class. After that in setDbTable call the setModel method of the JTable, it means:
public class DBView {
private JTable jtable = new JTable();
public void setDbTable(DefaultTableModel dbTable) {
this.jtable.setModel(dbTable);
//this.dbTable.repaint();
//paintDBTable();
}
.
.
.
}
Hope this would be helpful,
Good Luck.
Im using a JPanel with propertyChangeListener and want it to rerender itself based on whenever a particular variable model changes. My code for the same is as follows --
public class LabelMacroEditor extends JPanel implements PropertyChangeListener {
private static final long serialVersionUID = 1L;
private LabelMacroModel model;
public LabelMacroEditor(LabelMacroModel bean) {
this.model = bean;
model.addPropertyChangeListener(this);
setupComponents();
validate();
setVisible(true);
}
public void setupComponents()
{
Box allButtons = Box.createVerticalBox();
JScrollPane macroModelScroller = new JScrollPane(allButtons);
macroModelScroller.setPreferredSize(new Dimension(300, 200));
for(MacroModel macroModel : model.getMacroModelList())
{
LabelMacroEditorEditableEntity macroEditorEntity = new LabelMacroEditorEditableEntity(macroModel);
Box entityBox = Box.createHorizontalBox();
entityBox.add(macroEditorEntity.getUpButton());
entityBox.add(Box.createHorizontalStrut(15));
entityBox.add(macroEditorEntity.getMacroDetailsButton());
entityBox.add(Box.createHorizontalStrut(15));
entityBox.add(macroEditorEntity.getDownButton());
allButtons.add(entityBox);
}
add(macroModelScroller);
}
#Override
public void propertyChange(PropertyChangeEvent arg0) {
revalidate();
repaint();
}
}
When i use the debug mode in eclipse i can see that whenever there is a change to model it triggers off the call propertyChange and it also runs over revalidate and repaint but only the JPanel display remains the same. It does not seem to be rerendering itself.
Anything fundamental that I'm missing here ?
EDIT :
An example snippet of a property im changing is as follows --
labelMacroModel.addMacroModel(addedMacroModel);
where labelMacroModel is of the type LabelMacroModel and addedMacroModel is of the type Macro
Now the relevant part of LabelMacroModel class that fires off the property change is as follows --
private List<MacroModel> macroModelList;// this is the list of all MacroModels
public void addMacroModel(MacroModel macroModel) {
macroModelList.add(macroModel);
pcs.fireIndexedPropertyChange("LabelMacroModel", macroModelList.size(), null, macroModel);
}
Its not clear how you are changing the components in the panel. If panel is not updated then repaint/revalidate will have no effect. I think you should not need revalidate/repaint to be called explicitly if you are not modifying the way components are laid out. JButton.setText should for example change the label of the button without need of calling repaint.
To expand on the answer by AKJ above, I think you should be reconstructing your components on property change. So doing a remove all then readding is one way to do this. Once you get this working you could be more selective about pushing the model update into the GUI eg if a new entry has been added then just add a new component to reflect this. The remove all / readd is fine for a lot of cases though. HTH.
I have the JList which is using ListModel and not the DefaultListModel. I don't want to change the type now because I am using this in many places. I want to remove a selected item from the same list. How do i do this? I am using the following code but its not working for me.
made_list.removeSelectionInterval(
made_list.getSelectedIndex(), made_list.getSelectedIndex());
--EDIT--
I am using the following code when I create my list:
made_list = new javax.swing.JList();
made_list.setModel(new DefaultListModel());
And then in the JButton mouseclick event, I am using the following code to remove the selected item from the list when the button is pressed
private void removeActionPerformed(java.awt.event.ActionEvent evt) {
//made_list.removeSelectionInterval(made_list.getSelectedIndex(),
//made_list.getSelectedIndex());
System.out.println(made_list.getModel());
DefaultListModel model = (DefaultListModel)made_list.getModel();
model.remove(1);
}
removeSelectionInterval removes nothing from the model or the list except the selection interval. The list items remain unscathed. I'm afraid that you're either going to have to extend your ListModel and give it a removeItem(...) method as well as listeners and the ability to fire notifiers, etc... a la AbstractListModel -- quite a lot of work! If it were my money, though, I'd go the easy route and simply use a DefaultListModel for my model as it is a lot safer to do it this way, a lot easier, and will take a lot less time. I know you state that you don't want to use these, but I think you'll find it a lot easier than your potential alternatives.
An example of an SSCCE is something like this:
import java.awt.event.*;
import javax.swing.*;
public class Foo1 {
private String[] elements = {"Monday", "Tueday", "Wednesday"};
private javax.swing.JList made_list = new javax.swing.JList();
public Foo1() {
made_list.setModel(new DefaultListModel());
for (String element : elements) {
((DefaultListModel) made_list.getModel()).addElement(element);
}
JButton removeItemBtn = new JButton("Remove Item");
removeItemBtn.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
removeActionPerformed(e);
}
});
JPanel panel = new JPanel();
panel.add(new JScrollPane(made_list));
panel.add(removeItemBtn);
JOptionPane.showMessageDialog(null, panel);
}
private void removeActionPerformed(ActionEvent e) {
System.out.println("made_list's model: " + made_list.getModel());
System.out.println("Model from a fresh JList: " + new JList().getModel());
DefaultListModel model = (DefaultListModel) made_list.getModel();
if (model.size() > 0) {
model.remove(0);
}
}
public static void main(String[] args) {
new Foo1();
}
}
You've been given a link to different sections of the Swing tutorial in the past to help solve problems. This was done for a reason. It helps solve your current problem. It gives you a reference for future problems.
All you need to do is look at the Table of Contents for the Swing tutorial and you will find a section on "How to Use Lists" which has a working example that adds/removes items from a list. Please read the tutorial first.
Or if you can't remember how to find the Swing tutorial then read the JList API where you will find a link to the same tutorial.
//First added item into the list
DefaultListModel dlm1=new DefaultListModel();
listLeft.setModel(dlm1);
dlm1.addElement("A");
dlm1.addElement("B");
dlm1.addElement("C");
// Removeing element from list
Object[] temp=listRight.getSelectedValues();
if(temp.length>0)
{
for(int i=0;i<temp.length;i++)
{
dlm1.removeElement(temp[i]);
}
}
I have a JFrame that contains a "display" JPanel with JTextField and a "control" JPanel with buttons that should access the contents of the display JPanel. I think my problem is related on how to use the observer pattern, which in principle I understand. You need to place listeners and update messages, but I don't have a clue where to put these, how to get access from one panel to the other and maybe if necessary to introduce a "datamodel" class. For example, I want to access the contents of the JTextField from the control panel and I use an anonymous action listener as follows:
JButton openfile = new JButton("Convert file");
openfile.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
openButtonPressed();
}
});
You need to reduce the coupling between these objects.
You can have a master object, that owns all the text fields and the button ( the panels are irrelevant )
Then a separete actionlistener within that master object ( I call it mediator see mediator pattern )
That action listener performs a method on the mediator which in turn take the values from the textfields and create perhaps a transfer object.
This way you reduce the coupling between the panels, textfields etc. and let the control in one place ( the mediator ) that is, you don't let them know each other.
You can take a look at the code in this question:
https://stackoverflow.com/questions/324554/#324559
It shows these concepts in running code.
BTW the observer pattern is already implemented in the JTextField, JButton, ActionListener etc. You just need to add the hooks.
I hope this helps.
EDIT Joined two answers into one.
This is the code.
class App { // this is the mediator
// GUI components.
private JFrame frame;
private JTextField name;
private JTextField count;
private JTextField date;
// Result is displayed here.
private JTextArea textArea;
// Fired by this button.
private JButton go;
private ActionListener actionListener;
public App(){
actionListener = new ActionListener(){
public void actionPerformed( ActionEvent e ){
okButtonPressed();
}
};
}
private void okButtonPressed(){
// template is an object irrelevant to this code.
template.setData( getData() );
textArea.setText( template.getTransformedData() );
}
public void initialize(){
frame = new JFrame("Code challenge v0.1");
frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
name = new JTextField();
count = new JTextField();
date = new JTextField();
textArea = new JTextArea();
go = new JButton("Go");
go.addActionListener( actionListener ); // prepare the button.
layoutComponents(); // a lot of panels are created here. Irrelevant.
}
}
Complete and running code can be retrieved here:
It is important to favor composition over inheritance when possible.
It does make the code cleaner if you create the models in one layer and add a layer or two above to create the components and layout. Certainly do not extend the likes of JFrame and JPanel.
Do not feel the need to make the composition hierarchy in the model layer exactly match the display. Then it's just a matter of taking the text from the Document and performing the relevant operation.
Okay, perhpas not that simple. Swing models are a little bit messy. In particular ButtonModel is brain damaged, and the controller area of code might not be entirely pure.
We have so called builders, which will build the parent panel out of the children. In this builder you will have access to all the subcomponents you need to listen to and can thus can implement any logic there.
Finally the builder will then return the parent panel with the complete logic.
Once you've got the parent panel it's really a mess getting to the child components and have them do anything.
thanks. I added a datamodel layer which handles somehow the communication between the panels.
I also found this link on Listeners on JTextField usefull:
link text