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
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 have a quick question. I don't get it...
I've got a JFrame where I add a JComboBox:
JComboBox<String> Team_ComboBox = new JComboBox<>();
Team_ComboBox_Handler ComboBox_Listener = new Team_ComboBox_Handler();
Team_ComboBox.addActionListener(ComboBox_Listener);
Team_ComboBox.addItem("Test 1");
Team_ComboBox.addItem("Test 2");
On this Frame I have a button which opens another JFrame.
Play = new JButton();
Play.setText("Play");
Play.setPreferredSize(dimension);
Play.addActionListener(menuhandler);
private class main_menuhandler implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
if(e.getSource()==Play){
teams Team = new teams();
Team.teams();
disposeMainMenue();
}
if(e.getSource()==Close) {
System.exit(DO_NOTHING_ON_CLOSE);
}
}
}
Anyway, I would like to transfer the Selected value of the Combobox to a method of the other class. I know how I can get the itemvalue of the combobox in the method itself (with getselecteditem) But how can I do that in the ActionPerformed Method as I can't access the combobox in the ActionPerformed method.... I created another ActionListener (comboBox_Listener) but I haven't put any code into it...
Any idea? Thanks a lot in advance
Several issues appear to me:
Your main question:
But how can I do that in the ActionPerformed Method as I can't access the combobox in the ActionPerformed method
Your likely best solution is to change your code and variable declaration placement so that you can access the JComboBox fromt he actionPerformed method. If you're declaring the combobox from within a method or constructor, change this so that it is a proper instance field of the class.
Other problems:
You should not be creating multiple JFrames. If you need a dependent window, then one should be a JDialog. If not, then consider swapping views with a CardLayout.
Learn and follow Java naming conventnions so others can better understand your code. Class names begin with capital letters and methods and variable names don't for instance.
I am not sure why you're doing this: System.exit(DO_NOTHING_ON_CLOSE);. Why pass that constant into the exit method?
Use a constructor for your action listener class:
private class main_menuhandler implements ActionListener {
private JComboBox<String> Team_ComboBox;
public main_menuhandler(JComboBox<String> Team_ComboBox){
this.Team_ComboBox = Team_ComboBox;
}
}
Now you can create the class main_menuhandlervia the constructor and add the combobox to it.
In your Overriden action you have access to it.
Try playing around with this as your code snippet isn't broad enough to actually provide proper code. But this should answer your question
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
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 am currently developing my own minesweeper. Swing follows Model-View-Controller design pattern. In MVC, I learnt whenever there is a change in model, the controller will trigger that change in view too. But In this example, I cannot trace how to make the changes in setTitle and setInfo to get reflected in view.
Here, when I set the title of the Dialog box, the actual content(model) is getting changed, But there is no corresponding change in the output(view).
//InfoDisplayer is inner class of class MenuActionListener
class InfoDisplayer extends JDialog {
JLabel info;
BorderLayout infoBorderLayout = new BorderLayout();
public InfoDisplayer(JFrame ownerFrame) {
super(ownerFrame,true);
info = new JLabel();
setFocusable(false);
setSize(300,400);
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
setLayout(infoBorderLayout);
add(info,BorderLayout.SOUTH);
setVisible(true);
}
void setInfo(JLabel info) {
this.info = info;
}
public void setTitle(String title) {
super.setTitle(title);
}
}
if ((event.getActionCommand()).equals("HowToPlay")) {
InfoDisplayer instructionsDisplay = new InfoDisplayer(gUIManagerFrame);
//gUIManagerFrame is an object of its outer class,MenuActionListener
instructionsDisplay.setTitle("INSTRUCTIONS");
instructionsDisplay.setInfo(new JLabel("<html><h1><B>INSTRUCTIONS</B></h1></html>"));
} else {// if about is clicked!!
InfoDisplayer aboutDisplay = new InfoDisplayer(gUIManagerFrame);
aboutDisplay.setTitle("MineSweeper v0.1");
aboutDisplay.setInfo(new JLabel("<html><h1><B>MineSweeperv1.0</B></h1> </html>"));
}
Whenever there is a change in model, the controller will trigger that change in view.
In the Model–View–Controller pattern, when the controller updates the model, the model will notify the view, typically using the observer pattern, and the view then updates itself. The view may interrogate the model and process any resulting update. There's a more detailed answer and example here.
You will need to remove the old jlabel and add the new one to the frame.
Though it would make more sense probably to set the text on the existing label rather than a whole new label.
Swing indeed has a model and a view side. For example in a JTable the JTable is the view and the TableModel is the model. When you construct a JTable, you need to pass it a model either during construction or by using the setter. The JTable will then add a listener to model to get informed about any model changes. You can see this listener as the controller.
However, this does not mean that when you use an arbitrary combination of Swing classes they will auto-magically get informed about each other changes. In your case, the label is certainly not 'the model' of your dialog, and there is no such thing as a 'controller' between your label and the dialog. When you make such a change, you need to inform the dialog yourself (and probably add the label to your dialog as well).
Oh, and I would recommend changing your setTitle method into
public void setTitle( String aTitle ){
super.setTitle( aTittle );
}
or remove it completely. This will avoid a StackOverflowException