Im using Observer Pattern to display whos turn it is to play. And im being given the right data from my Observable, thats why i dont post it. As you can see, the text is being set, as i can get call label.getText().
How come the label wont update?
Ive tried repaint, revalidate, SwingUtilities.invokeLater, updateUI, and all sort of stuff. I do not know what im doing wrong.
Thank you in advanced.
public class PlayersTurnPane extends JPanel implements Observer {
private JLabel label = new JLabel();
public PlayersTurnPane() {
setLayout(new BorderLayout());
setBackground(Color.gray);
label.setText("White");
label.setHorizontalAlignment(getWidth() / 2);
label.setFont(new Font("Serif", Font.PLAIN, 25));
add(label);
}
#Override
public void update(Observable arg0, Object arg1) {
label.setText((String)arg1); //But it will not update label
System.out.println(label.getText()); //The text is being set
}
}
My Observable
public class TurnObserver extends Observable {
public TurnObserver(PlayersTurnPane playersTurnPane) {
addObserver(playersTurnPane);
}
public void setTurn(String turn){
setChanged();
notifyObservers(turn);
clearChanged();
}
}
Where i send the data
private TurnObserver turnObserver = new TurnObserver(new PlayersTurnPane());
public void grabPiece(BoardTile boardTile) {
if(focusedPiece != null && playerTurn.getCurrentTurn() == focusedPiece.getPieceColor()) {
boardTile.add(focusedPiece);
updateUI();
playerTurn.changeTurn();
turnObserver.setTurn(playerTurn.getCurrentTurn());
}
}
The panel is added here
public class EastPanel extends JPanel {
public EastPanel() {
setLayout(new GridLayout(3,0));
add(new TimePane());
add(new MoveHistoryPane());
add(new PlayersTurnPane());
}
}
If System.out.println(label.getText()); is being printed, then you've got a Swing threading issue in code not shown, likely due to long-running code being run on the Swing event thread, preventing this thread from doing its job -- like painting the updated text on the JLabel. Be sure that all long running code is done on a background thread and that Swing state changes and queries are made on the Swing event thread. Read Concurrency in Swing for the details.
re,
And im being given the right data from my Observable, thats why i dont post it.
But it's that unposted code which holds the key to your problem.
Dang, Mad is correct: you're shadowing:
public TurnObserver() {
addObserver(new PlayersTurnPane());
}
You're adding a new PlayersTurnPane as your observer, but it's likely not the displayed PlayersTurnPane! Key: every time you call new on a constructor, you create a new object, here a PlayersTurnPane. You should pass in the displayed PlayersTurnPane, not a new one.
You've got problems with code like this:
public class EastPanel extends JPanel {
public EastPanel() {
setLayout(new GridLayout(3,0));
add(new TimePane());
add(new MoveHistoryPane());
add(new PlayersTurnPane());
}
}
The dsiplayed PlayersTurnPane is being created anonymously -- the object is being added to a display, but this object is not being referenced by any variable, and this needs to be done if you're going to pass the exact same PlayersTurnPane object into the method that makes it an observable.
I suggest that you use class fields and not anonymous objects where you need a reference to the object. So here, perhaps better would be:
public class EastPanel extends JPanel {
public EastPanel(PlayersTurnPane playersTurnPane) {
setLayout(new GridLayout(3,0));
add(new TimePane()); // you likely need parameters for
add(new MoveHistoryPane()); // these two objects as well
add(playersTurnPane);
}
}
The key concept that you seem to be missing is one of the importance of references which are pointers to an object of interest. Read up on this.
Related
I have a custom class CustomField that extends JPanel. As I often have to reuse the same pattern, my custom class is made of 2 JLabels and 2 JComboBox.
It's quite simple; the first JComboBox has ON/OFF choices and the second JComboBox is only visible if the first is set to "ON". I can manage this part.
The part that I however don't know who to design it well is that CustomField instances are in another class that is the main JFrame and in this JFrame, some parts will be visible only if the JComboBox from the CustomField class is set to "ON". I thought about using a MouseAdapter, but I don't know it is good practice.
Here is my CustomField class:
public class CustomField extends JPanel {
/**
*
*/
private static final long serialVersionUID = 1L;
JLabel text, portText;
JComboBox<String> testCB, option;
public CustomField(String text, String opt, String tst) {
this.text = new JLabel(text);
String[] onOffOpt= {"OFF", "ON"};
this.option = new JComboBox<String>(onOffOpt);
this.option.setSelectedItem(opt);
this.option.addItemListener(new ItemListener(){
#Override
public void itemStateChanged(ItemEvent ie) {
portText.setVisible(option.getSelectedIndex() == 1);
testCB.setVisible(option.getSelectedIndex() == 1);
}
});
this.portText = new JLabel("Test:");
String[] testChoices = {"Test", "Test2"};
this.testCB = new JComboBox<String>(testChoices);
this.testCB.setSelectedItem(tst);
this.setLayout(new FlowLayout());
add(this.text);
add(this.option);
add(this.portText);
add(this.testCB);
}
}
And here is the main JFrame:
public class Main {
CustomField cf = new CustomField("test", "ON, "Test2");
public static void main(String s[]) {
JFrame frame = new JFrame("Application");
JPanel panel = new JPanel();
panel.setLayout(new FlowLayout());
panel.add(cf);
JLabel labelTest = new JLabel("Label that should be visible or not");
panel.add(labelTest);
frame.add(panel);
frame.setSize(300, 300);
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}
Basically, I want that the labelTest visibily changes according to the CustomField settings. In the way that it is made, I can not put the labelTest in the CustomField class.
Is there a clean way to do what I want? Should I redesign the actual thing and put all the fields in the same class?
Thanks!
First, you want to expose the combobox's state with a method in CustomField:
public boolean isOn() {
return testCB.getSelectedIndex() == 1;
}
You can get an idea for how listening for state is done by looking at the method signatures in the documentation for various Swing components, which use the standard JavaBean listener pattern: You’ll want to add three public methods, and one protected method:
public void addChangeListener(ChangeListener listener) {
listenerList.add(ChangeListener.class, listener);
}
public void removeChangeListener(ChangeListener listener) {
listenerList.remove(ChangeListener.class, listener);
}
public ChangeListener[] getChangeListeners() {
return listenerList.getListeners(ChangeListener.class);
}
protected void fireChangeListeners() {
ChangeEvent event = new ChangeEvent(this);
for (ChangeListener listener : getChangeListeners()) {
listener.stateChanged(event);
}
}
(The listenerList field is inherited from JComponent.)
Now, you can simply add a call to fireChangeListeners(); whenever you detect that the user has changed the value of the On/Off combobox—that is, you’ll want to call it in your ItemListener.
As you can probably guess, your Main class can now call cf.addChangeListener, and inside that listener adjust the visibility of your label based on the value returned by cf.isOn().
You can learn a lot more by reading these.
I'm making a Maths Game application and recently began implementing MVC.
I have the following structure:
auiAs2
MigJPanel: extends JPanel
ScreenInterface.java: contains global variables, fonts, and difficulty enum
MathsGame.java: extends JFrame
auiAs2.view
DiffView.java: extends MigJPanel implements ScreenInterface
GameView.java: extends MigJPanel implements ScreenInterface
EndGameView.java: extends MigJPanel implements ScreenInterface
auiAs2.controller
DiffControl.java
GameControl.java
EndGameControl.java
auiAs2.model
Model.java: implements ScreenInterface
My MathsGame.java contains a JPanel set to CardLayout which instances of DiffView, GameView, and EndGameView are added to. When I run my program, the diffView 'card' is shown to the user.
If the user clicks "New Game", an ActionListener in DiffControl.java gets the selected difficulty.
public class DiffControl {
private DiffView diffView;
private Model model;
public DiffControl(DiffView diffView, Model model) {
this.diffView = diffView;
this.model = model;
this.diffView.addNewGameListener(new NewGameListener());
}
class NewGameListener implements ActionListener {
String selectedDiff;
#Override
public void actionPerformed(ActionEvent e) {
selectedDiff = diffView.getSelectedDiff();
//MathsGame.setLayCard(panContainer, "New Game"));
}
}
}
This is where I get stuck. Where should I switch between panels in my CardLayout JPanel layCard? (MathsGame.java is shown below with irrelevant code removed. The entire code for relevant classes is linked above if required)
public class MathsGame extends JFrame {
private JPanel panContainer = new JPanel();
private CardLayout layCard = new CardLayout();
public MathsGame() {
panContainer.setLayout(layCard);
setContentPane(panContainer);
setSize(new Dimension(WIDTH, HEIGHT));
setMinimumSize(new Dimension(MIN_WIDTH, MIN_HEIGHT));
setTitle(TITLE);
DiffView panDiffView = new DiffView();
panContainer.add(panDiffView, "Choose Difficulty");
GameView panGameView = new GameView();
panContainer.add(panGameView, "New Game");
EndGameView panEndGameView = new EndGameView();
panContainer.add(panEndGameView, "End Game");
Model model = new Model();
DiffControl diffControl = new DiffControl(panDiffView, model);
//GameControl gameControl = new GameControl(panGameView, model);
//EndGameControl EndGameControl = new EndGameControl(panEndGameView, model);
layCard.show(panContainer, "Choose Difficulty");
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(MathsGame::new);
}
}
So my question is:
Where would be the best place to put code related to switching between Views in my CardLayout container?
The model will have a state field, perhaps an enum, that would reflect the view. You could make this a bound property using a SwingPropertyChangeSupport object. Then the view could listen to the state of this property and swap the Card based on its state. In fact the toString() of each enum constant could be used to add card views to the CardLayout-using container.
Edit
You ask:
Could you elaborate on the usage of SwingPropertyChangeSupport?
You can find examples of this here, here, and especially here.
I've attempted using addPropertyChangeListener in the model. And how would the View control the card change when the CardLayout is in MathsGame.java?
The view would be notified of the state change of the model, and then when this happens, the view would call its code to swap cards.
I've had problems with doing that inside View for "calling a static method from a non-static context".
This is a completely different unrelated issue, a basic core Java issue, that I'm sure with a little work, you'll be able to solve. In brief -- don't try to call instance code in a static way. Always call it on a proper reference, not on the class.
I want my button to run a whole new class that will do different things inside. I don't know if that is even possible cause i'm really bad at java. My code looks like this at the moment:
public class MainMenu {
private class GardenActivities {
public GardenActivities() {
JFrame GardenAct = new JFrame();
GardenAct.setSize(400, 400);
GardenAct.setVisible(true);
}
}
public static void main(String[] args) {
JFrame choice = new JFrame();
choice.setSize(700, 500);
choice.setLocationRelativeTo(null);
choice.setTitle("Seeds");
choice.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel panel = new JPanel();
panel.setLayout(new GridLayout(0, 1));
JButton Labora = new JButton();
Labora.setText("Laboratory");
Labora.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent ev) {
GardenActivities();
}
});
JButton Garden = new JButton();
Garden.setText("Garden");
Garden.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent ev) {
}
});
choice.getContentPane().add(panel);
ButtonGroup group = new ButtonGroup();
group.add(Garden);
group.add(Labora);
panel.add(Garden);
panel.add(Labora);
choice.setVisible(true);
}
}
Just like I said. I need something to run my GardenActivities class just by pressing Garden button.
Your code doesn't compile, does it? When that happens, you need to post compilation errors with your question so that we can help you with them.
You need to add the key word new before the GardenActivities() statement.
#Override
public void actionPerformed(ActionEvent ev) {
new GardenActivities(); // here
}
Also, put the GardenActivities in its own file. There's no reason for making it a private inner class and many reasons not to do this.
Having said this, I recommend against having one JFrame create and display another JFrame since an application should have usually only one JFrame. Instead consider swapping JPanel "views" using a CardLayout, or if you must show a different window, consider showing the second dependent window as a modal or non-modal dialog.
Also more unsolicited advice: Your main method is doing way too much. Most of the code inside of the static main method should go inside the non-static main gui class, whatever that is, perhaps in its constructor or in an initGui() method that the constructor calls. The main method should just create an instance of the main gui class, make it visible, and that's about it.
And regarding:
I don't know if that is even possible cause i'm really bad at java.
Keep writing lots and lots of code, a ton of code, and keep reviewing tutorials and textbooks, and this will change.
I think that you just need to add:
new GardenActivities();
Into your JButton's actionPerformed() method.
Good Luck!
One way to do what you want, we make the GardenActivities class implement ActionListener itself.
Then your code would look something like this:
Garden.addActionListener(new GardenActivities());
Otherwise, your plan should work.
NOTE
See comments for opposing opinions about why one would want to leave the ActionListener in the anonymouse inner class and have it call into GardenActivities.
Thank you #HovercraftFullOfEels
As others have pointed out, this:
#Override
public void actionPerformed(ActionEvent ev)
{
GardenActivities();
}
Should look like:
#Override
public void actionPerformed(ActionEvent ev)
{
new GardenActivities();
}
There is no reason to create an inner class, and GardenActivities can be, and should be, its own class.
I have a few classes to create a very simple GUI (JFrame) with a few objects like a JTabbedPanel and a JTree. And one of the classes that creates a ribbon in the JTabbedPanel creates a JButton, that should have a function that updates the JTree.
I do have some getter, but I have no idea how to use the JButton to update something on an object that gets created in the main method (the GUI object, from where I would be able to get to the JTree).
How do I update something on an object that gets created in the main method in the actual class of the JButton?
I might have to change the structure of my project.
public class Gui extends JFrame{
private Ribbon ribbon;
private Status status;
public Panel panel;
public Gui(){
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setTitle("INIT TITLE");
setExtendedState(Frame.MAXIMIZED_BOTH);
setMinimumSize(new Dimension(600,400));
setVisible(true);
try {
UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsLookAndFeel");
} catch (Exception e) {
e.printStackTrace();
}
ribbon = new Ribbon();
add(ribbon, BorderLayout.NORTH);
status = new Status();
add(status, BorderLayout.SOUTH);
panel = new Panel();
add(panel, JSplitPane.HORIZONTAL_SPLIT);
//panel.setLeftComponent(panel.getTree());
//panel.openProject();
setVisible(true);
panel.loadProject();
}
The Ribbon doesnt do a lot:
public class Ribbon extends JTabbedPane {
public Ribbon(){
addTab("Home", null, new RibbonHome());
addTab("Import", null, new RibbonImport());
addTab("Options", null, new RibbonOptions());
}
}
But the Ribbon-Tab creates some buttons:
public class RibbonHome extends JPanel{
private JButton b1, b2, b3;
public RibbonHome(){
b1 = new JButton("test1");
b2 = new JButton("test2");
b3 = new JButton("test3");
add(b1);
add(b2);
add(b3);
b1.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent arg0) {
//UPDATE the JTree
}
});
}
}
button.addActionListener( main );
main implements ActionListener and provides the method actionPerformed()
A code sample of what you are doing would make things easier, but anyway.
Probably you want to make the component you are planning to update a class member. This way, you can initialize it where it is initialized now, but still reach it from your ActionListener.
Again, this is a bit a guess on how you structured things, so if you can post a code sample, I am sure you can get a much more detailed answer.
EDIT after code sample:
You could give RibbonHome an additional private member of type Gui. I usually call it parent, so it becomes: private Gui parent;
In the constructor of RibbonHome, you add Gui as a parameter, this way, RibbonHome will always have access to its parent.
Also add it to the constructor of Ribbon, because you will want to pass it through there.
As a last step, you will always have to construct Ribbon with Gui as a parameter, so in Gui it will become: ribbon = new Ribbon(this);
As soon as you have done all of this, you can reach the JTree by the getter of you JTree on parent.
There are better solutions, since this will have as a drawback that you will never be able to construct a new Ribbon from another component than a Gui, but if you want that, you can work with interfaces.
The most obvious answer is of course to pass the reference of your Gui class to your Ribbon class, which in turn can pass it to the RibbonHome class.
public Gui{
//...
ribbon = new Ribbon( this );
//...
}
public Ribbon ( Gui gui ){
//...
addTab("Home", null, new RibbonHome( gui ));
//...
}
public RibbonHome( Gui gui){
//...
b1.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent arg0) {
//UPDATE the JTree
}
});
//...
}
However, this ensures a very tight coupling between your different classes. I do not know what exactly you want to update on that JTree but the more fundamental solution might be to share a model between your different views. Your JTree would update itself when the model changes, and the button just updates the model.
I am having a bit of problem regarding Swing. I have a JFrame called FrameMain. Inside it is a JPanel called panelChoices.
When FrameMain is called/created, it fills up the panelChoices object with a number of PanelEntries objects, which is a JPanel with a number of JButtons in it (it is a different class that I wrote).
What I want to do is when I click one of the buttons inside the PanelEntries object, I want to destroy/remove FrameMain, along with the rest of it components (including the PanelEntries object that contains the JButton).
I've tried using super but it returns the JPanel (the PanelEntries object) that holds the JButton and not FrameMain that holds them all together. How can I achieve this?
EDIT: It seems that I am not clear enough, so here's a bit more information from my work. I don't have the actual code right now because I am on a different machine but I hope this will help elaborate my question.
public class FrameMain() {
private JFrame frameMain;
private JPanel panelChoices;
public FrameMain(args) {
createGUI();
loadData();
}
private void createGUI() {
JFrame frameMain = new JFrame();
JPanel panelChoices = new JPanel(new GridLayout(1,1));
frameMain.add(panel);
// removed formatting and other design codes since they are not important.
pack();
}
private void loadData() {
boolean available;
for (int i = 1; i <= 10; i++) {
// do some if/else and give value to boolean available
PanelEntries panel = new PanelEntries(i, available);
frameMain.add(panel);
// more code here to handle data.
}
}
}
public class PanelEntries() extends JPanel {
public PanelEntries(int num, boolean avb) {
JButton button = new JButton("Button Number " + num);
button.setEnabled(avb);
add(button);
// add action listener to created button so that it calls 'nextScreen()' when clicked.
// more code
pack();
}
private void nextScreen() {
// destroy/dispose MainFrame here.
// See Notes.
AnotherFrame anotherFrame = new AnotherFrame();
}
}
Notes:
All classes are inside their own .java file.
I need to know how to dispose FrameMain from the button inside the PanelEntries object, not just disposing a JFrame.
As per the given information,
If you want to exit the application, its not a big deal use System.exit(0); :)
If you mean to dispose the frame, jframe.dispose();
If you want to remove a componet / all components you can use .remove(Component) / .removeAll() etc
If this did not help, please re-write your question with more information.