How to change JButton property? - java

I want to change button text when i click on it, but it does not appears on the GUI. In intellje IDE i can see it is changed but why does not appear in GUI?
This is code snip:
final WebLabel loading = new WebLabel("Disconnected...", IconLib.ICON_19X17_THICK_ARROW_RIGHT_LIGHTBLUE.getIcon(), SwingConstants.CENTER);
final WebLabel ipLabel = new WebLabel(host);
final JPanel horizontalMiddlePanel = new JPanel();
final WebButton disconnect = new WebButton("Connect", IconLib.ICON_16X16_QUESTIONMARK_ON_BLUE_CIRCLE.getIcon());
disconnect.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
if (loading.getText().equals("Connected...")) {
loading.setText("Disconnected...");
loading.setIcon(IconLib.ICON_19X17_THICK_ARROW_RIGHT_LIGHTBLUE.getIcon());
disconnect.setText("Connect");
} else {
loading.setText("test");
loading.setIcon(IconLib.ICON_19X17_THICK_ARROW_RIGHT.getIcon());
ipLabel.setText(ipLabel.getText().replace(" Unreachable try again",""));
ipLabel.setForeground(Color.green);
disconnect.setText("Connecting");
callflexConnection(ipLabel, 3001, loading, disconnect);
}
}
});

than not possible without spliting code to to the two parts
1) update JButton#setText
then
2) executing rest of code
by delaing by using javax.swing.Timer
execute from SwingWorker
wrap inside Runnble#Thread,
3) this code is executed on EDT, then all changes are done on EDT, end in same/one moment

It's hard to tell if it's the source of your current problem or not, but performing logic in code based on the current text on a button is a flimsy way to do things. You should maintain that connection state in a dedicated variable. Something like this:
private enum ConnState {
CONN_DISCONNECTED,
CONN_CONNECTING,
CONN_CONNECTED,
};
private ConnState connState;
private void setConnState(ConnState connState) {
this.connState = connState;
switch (connState) {
case CONN_DISCONNECTED:
loading.setText("Disconnected");
disconnect.setText("Connect");
break;
case CONN_CONNECTING:
loading.setText(...etc...);
disconnect.setText(...);
break;
case CONN_CONNECTED:
loading.setText(...);
disconnect.setText(...);
break;
}
}
And call this when setting up the GUI to initialize the button text and connState:
setConnState(CONN_DISCONNECTED);
Then you can reason robustly about the current state of the program by checking the connState variable instead of having to synchronize button strings everywhere.

Related

Java text-adventure game swing components not updating correctly and enum gets selected twice per function run

I am intrigued by the bug that's appearing in the code that I've written. It works perfectly in C# but somehow it doesn't work in Java. I'm not sure if it has something to do with the second time I set it. I've checked it with my other class that passes the buttons into this class and I find nothing that would affect this class. Another thing to note is that all the Swing components aren't null when they're passed into this class.
My main problem is that all buttons do not update after setting the text for the second time.
public class StoryManager extends Text{
// this is the body of the story
private JTextPane textPane;
// the choices that the user can make
private JButton btnChoice1;
// this would determine which ending the user will get
private short ending = 0;
/**
* checkpoint for each part of the story
* */
public enum Part{
START, A_JOKE, B_BASE
}
// the part that the user is currently in
Part userPart;
/**
* This runs the story.
* */
public void PlayStory() {
printText();
processDecision();
}
/**
* This attaches the buttons and the textPane to their corresponding variables in the class.
* */
public StoryManager(WindowHandler windowHandler) {
// pane
textPane = windowHandler.getTextPane();
// buttons
btnChoice1 = windowHandler.getBtnChoice1();
// set the user to start the story from the beginning
userPart = Part.START;
}
#Override
public void printText() {
switch(userPart) {
case START:
System.out.println("Start event started");
textPane.setText("some text"
+ "[1] option 1\n");
break;
case A_JOKE:
System.out.println("A_JOKE event started");
textPane.setText("another text 1"
+ "[1] option 1\n");
break;
case B_BASE:
System.out.println("B_BASE event started");
textPane.setText("another text 2"
+ "[1] option 1\n");
break;
default:
}
textPane.setCaretPosition(0);
textPane.repaint();
}
/**
* Enables all the buttons.
* */
private void EnableAllButtons() {
if(!btnChoice1.isEnabled()) {
btnChoice1.setEnabled(true);
btnChoice1.setOpaque(true);
btnChoice1.setContentAreaFilled(true);
btnChoice1.setBorderPainted(true);
}
btnChoice1.repaint();
//System.out.println("Button1: "+btnChoice1.isEnabled());
}
/**
* Temporarily disables the other buttons and runs the PlayStory() again to continue the story.
*
* #param button The button that is clicked.
* */
private void SetDecisionText(JButton button) {
button.setText("Next");
btnChoice1.revalidate();
PlayStory();
}
/**
* Sets the action of the button per part.
* */
#Override
public void processDecision() {
switch(userPart) {
case START:
btnChoice1.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent arg0) {
ending -= 10;
userPart = Part.A_JOKE;
SetDecisionText(btnChoice1);
}
});
break;
case A_JOKE:
btnChoice1.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent arg0) {
btnChoice1.setText("Choice 1");
//btnChoice1.revalidate(); <- tried this but it isn't working
GoToBase();
}
});
break;
case B_BASE:
System.out.println("Updated with B_BASE");
btnChoice1.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent arg0) {
ending -= 10;
userPart = Part.B_ARGUE;
SetDecisionText(btnChoice1);
}
});
}
}
private void GoToBase() {
EnableAllButtons();
switch(userPart) {
case A_JOKE:
userPart = Part.B_BASE;
System.out.println("GotoBase: " + userPart);
break;
default:
System.out.println("Error");
}
PlayStory();
}
}
I'll share the output of my program here. I selected the first button twice then this happens. When I press it again, userPart becomes null or something.
// pressed play
Start event started
// click 1st button
A_JOKE event started
// click 1st button again
GotoBase: B_BASE
B_BASE event started
Updated with B_BASE
A_JOKE event started
// click 1st button for the third time
GotoBase: B_BASE
B_BASE event started
Updated with B_BASE
Error
A_JOKE event started
You appear to be adding ActionListeners to your buttons within the logical portion of your code, and your doing this means that buttons will have ActionListeners added to them multiple times, whenever the processDecision() method is called, resulting eventually in buttons with multiple listeners added to them which is not what you want. The listeners should be added once, at component creation.
Or if you are going to want to swap ActionListeners, then be sure to remove the old listeners when replacing with the new.
There are likely other logical problems in your code, but this is the limit of what I can suggest until you are able to provide a valid Minimal, Complete, and Verifiable Example Program for us. This is where you condense your code into the smallest bit that still compiles and runs, has no outside dependencies (such as need to link to a database or images), has no extra code that's not relevant to your problem, but still demonstrates your problem.
A large problem that I see is that your code has very high coupling and low cohesion, making for very brittle code, and code that is hard to debug. Consider refactoring, separating your model (the logic portion of your program) from your view (the GUI portion) and separating the data from the code, getting rid of the hard-coded display text for instance, and putting it into a data repository, be it a text file, database or whatever works best.
example of removing listeners:
case A_JOKE:
for (ActionListener l : btnChoice1.getActionListeners()) {
btnChoice1.removeActionListener(l);
}
btnChoice1.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent arg0) {
btnChoice1.setText("Choice 1");
// btnChoice1.revalidate(); <- tried this but it isn't
// working
GoToBase();
}
});
break;
As an aside, you will want to learn and use Java naming conventions which are different than those used for C#. Variable names should all begin with a lower letter while class names with an upper case letter. Learning this and following this will allow us to better understand your code, and would allow you to better understand the code of others.

JComboBox setSelectedIndex causes Thread to Crash

I am having an issue where calling JComboBox.setSelectedIndex(0) causes
my program to crash. Any help would be greatly appreciated!!
On itemStateChanged() starts a new Thread to handle UpdateAllForms.
UpdateAllForms calls updateComboModel() which Queries an SQL Database to update the ComboBoxModel and adds an additional option 'Select...'
This all works fine, however if i add JComboBox.setSelectedIndex(0) the
program crashes with no exception etc. I assume the issue is with threading?
itemStateChanged() Method:
public void itemStateChanged(ItemEvent e) {
if (e.getStateChange() == ItemEvent.DESELECTED) {
Runnable updateRunnable = new UpdateAllForms(e.getSource());
new Thread(updateRunnable).start();
}
}
UpdateAllForms Class:
// <<=== UpdateAllForms Class ===>>
// Only Updates Forms below the Current Form
// Must be ran as a Separate Thread due to swing concurrency
// ==============================================================================
public class UpdateAllForms implements Runnable {
Object source = null;
public UpdateAllForms(Object source) {
this.source = source;
}
#Override
public void run() {
// TODO Auto-generated method stub
boolean shouldUpdate = false;
Logger.write("PropConfDialog.updateAllForms");
// Loop through Forms
for (int formCount = 0; formCount < dataInputForms.get(1).size(); formCount++) {
Component curForm = dataInputForms.get(1).get(formCount);
// Update Forms after current form
if (shouldUpdate) {
if (curForm instanceof JSQLComboPanel) {
JSQLComboPanel panel = (JSQLComboPanel) curForm;
// Resets the where String
panel.setWhereString(getInputString(panel.getInputID()));
panel.updateComboModel();
shouldUpdate = true;
continue;
} else if (curForm instanceof JSQLLabelPanel) {
JSQLLabelPanel panel = (JSQLLabelPanel) curForm;
panel.setWhereString(getInputString(panel.getInputID()));
panel.updateLabel();
shouldUpdate = true;
Logger.write("LABEL CAN CARRY OUT");
continue;
}// End if/else
} // End should update
if (source == ((JSQLComboPanel) dataInputForms.get(1).get(formCount)).getComboBox()) {
shouldUpdate = true;
}// End if
}// End Loop
}// End updateAllCombos()
}// End UpdateAllForms Class
JSQLComboPanel Class - updateComboModel Method !!THIS IS THE ISSUE!!! if I call
combo.setSelectedIndex(0) in this method the program crashes.
public void updateComboModel(){
if(comboType == TYPE_DRIVEN_COMBO){
ArrayList values = SQLTools.getColValues(lkTable, lkNameCol);
combo.setModel(new DefaultComboBoxModel(values.toArray(new String[values.size()])));
}else if(comboType == TYPE_WHERE_COMBO){
ArrayList values = SQLTools.executeJoin(fkTable, fkIDCol, fkNameCol, lkTable, lkIDCol, lkNameCol, whereString);
combo.setModel(new DefaultComboBoxModel(values.toArray(new String[values.size()])));
}else if(comboType == TYPE_WHERE_LINKED_COMBO){
ArrayList values = SQLTools.executeLinkTableJoin(fkTable, fkIDCol, fkNameCol, linkTable, fkIDCol, lkIDCol, lkTable, lkIDCol, lkNameCol,whereString);
combo.setModel(new DefaultComboBoxModel(values.toArray(new String[values.size()])));
}//End if/else
combo.insertItemAt("Select...", 0);
//combo.setSelectedIndex(0);
combo.repaint();
}//End updateComboModel()
If anybody can shed any light, that would be fantastic! I am fairly new to Java especially Threading!
Thanks again
Tim
The problem is (almost certainly) related to the fact that you are modifying the state of Swing Components on the wrong thread.
The general rule is:
Code that depends on or modifies the state of a Swing Component should be executed on the Event Dispatch Thread.
A violation of this rule may sometimes be hard to detect - particularly, when only a model is modified, which does not necessarily have a connection to a GUI component!
However, in your case, the main problem is more obvious, because there is (at least) the problematic call
combo.setModel(new DefaultComboBoxModel(values.toArray(new String[values.size()])));
which happens on an own thread, and modifies the Swing component directly.
As suggested in the comments, you should definitiely consider using a SwingWorker. More details about the SwingWorker (and threading in Swing in general) can be found in the article about Concurrency In Swing
A quick workaround for your problem could be the following:
...
// Remove this line
//combo.setModel(new DefaultComboBoxModel(values.toArray(new String[values.size()])));
// Replace it with this line
setModelOnEDT(combo, new DefaultComboBoxModel(values.toArray(new String[values.size()]));
and create a method like this:
private static void setModelOnEDT(
final JComboBox comboBox, final ComboBoxModel model)
{
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run()
{
comboBox.setModel(model);
}
});
}
This is certainly not the prettiest solution, but the simplest, until you modify the code to use a SwingWorker.

Java Swing - Detecting a change in the document

For school, I'm attempting to recreate Microsoft's Notepad program using Java's Swing. I'm working on the saving and opening of .txt files, and I'm trying to figure out a way for the program to detect when a change has been made to the document. If a change has been detected and the user chooses to open or create a new file, I want the program to prompt the user if they would like to save their changes before continuing.
My thought for this was to create a flag, called documentChanged, that would initially be false, and which would be set to true whenever a change was made to the JTextArea. To detect this change, I thought of using a TextListener as follows:
public class JNotepad implements ActionListener
{
boolean documentChanged;
JTextArea notepad;
JNotepad()
{
documentChanged = false;
notepad = new JTextArea();
notepad.addTextListener(new TextListener() {
public void textValueChanged(TextEvent te) {
documentChanged = true;
}
});
}
}
However, I learned that Java classes are unable to implement multiple interfaces at once, and I'm already using ActionListeners to implement the items of my notepad's menu bar.
My question is, is there any way to use both TextListener and ActionListener (or any other listener) simultaneously in the same class? If not, what would be my best plan of action for detecting a change in the document?
It was answer in another post. See Text Changed event in JTextArea? How to?
And also see How to Write a Document Listener (DocumentListener) in Oracle, you will see an applet example.
How does your this even compile
notepad = new JTextArea();
notepad.addTextListener(new TextListener() {
// ....
}
since TextListeners are not defined to work with JTextAreas but rather with TextAreas, a completely different beast.
You should add a DocumentListener to your JTextArea's Document.
notepad.getDocument().addDocumentListener(new DocumentListener() {
void insertUpdate(DocumentEvent e) {
documentChanged = true;
}
void removeUpdate(DocumentEvent e) {
documentChanged = true;
}
void changedUpdate(DocumentEvent e) {
documentChanged = true;
}
});
Regarding
My question is, is there any way to use both TextListeners and ActionListeners (or any other listener) simultaneously in the same class?
Use of a DocumentListener has nothing to do with ActionListeners used elsewhere in your program since their domains are orthogonal to each other, i.e., the one has absolutely nothing to do with the other.

How to Subscribe to GUI Events in Other JFrames

What is the best practice for subscribing to events from another JFrame? For example, I have a "settings" form, and when the user presses okay on the settings form, I want the main form to know about this so it can retrieve the settings.
Thanks.
Here is my ideal interface:
public void showSettingsButton_Click() {
frmSettings sForm = new sForm(this._currentSettings);
//sForm.btnOkay.Click = okayButtonClicked; // What to do here?
sForm.setVisible(true);
}
public void okayButtonClicked(frmSettings sForm) {
this._currentSettings = sForm.getSettings();
}
Someone publishes an Event, that something has changed, here the settings. A subscriber that registered for this specifig event, gets notified about it and can do his work, here get the settings. This is called publisher/subscriber.
For this you can use Eventbus or implementing something smaller on your own.
One approach is to have only a single JFrame. All the other 'free floating top level containers' could be modal dialogs. Access the the main GUI will be blocked until the current dialog is dismissed, and the code in the main frame can check the settings of the dialog after it is dismissed.
For anyone interested, here is what I ended up going with. I'm not sure if it's the best way, but it is working for my purposes.
// Method called when the "Show Settings" button is pressed from the main JFrame
private void showSettingsButton_Click() {
// Create new settings form and populate with my settings
frmSettings sForm = new frmSettings(this.mySettings);
// Get the "Save" button and register for its click event...
JButton btnSave = sForm.getSaveButton();
btnSave.addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent evt) {
SaveSettings(sForm);
}
});
// Show the settings form
sForm.setVisible(true);
}
// Method called whenever the save button is clicked on the settings form
private void SaveSettings(frmSettings sForm) {
// Get the new settings and assign them to the local member
Settings newSettings = sForm.getSettings();
this.mySettings = newSettings;
}
And if, like me, you are coming from a .NET perspective, here is the C# version:
private void showSettingsButton_Click(object sender, EventArgs e)
{
frmSettings sForm = new frmSettings(this.mySettings);
sForm.btnSave += new EventHandler(SaveSettings);
sForm.Show();
}
private void SaveSettings(object sender, EventArgs e)
{
frmSettings sForm = (frmSettings)sender; // This isn't the exact cast you need..
Settings newSettings = sForm.Settings;
this.mySettings = newSettings;
}

How can I know when the text of an editable JComboBox has been changed?

I have an editable JComboBox where I want to take some action whenever the text is changed, either by typing or selection. In this case, the text is a pattern and I want to verify that the pattern is valid and show the matches that result in some test data.
Having done the obvious, attach an ActionHandler, I have found that, for typing, the event seems to fire unreliably, at best (selection is fine). And when it does fire as a result of typing, the text retrieved (using getEditor().getItem(), since getSelectedItem() only gets the text when it was selected from the list) seems to be the text as it was when the last event was fired - that is, it's always missing the character was typed immediately before the action event was fired.
I was expecting the action event to fire after some short delay (500ms to 1 second), but it seems immediately fired upon keying (if it is fired at all).
The only workable alternative I can think of is to simply start a 1 second timer on focus-gained, killing it on focus-lost and doing the work as the timer action if the content is different from last time.
Any thoughts or suggestions?
The code snippets are not particularly interesting:
find.addActionListener(this);
...
public void actionPerformed(ActionEvent evt) {
System.out.println("Find: "+find.getEditor().getItem());
}
The action listener is typically only fired when you hit enter, or move focus away from the editor of the combobox. The correct way to intercept individual changes to the editor is to register a document listener:
final JTextComponent tc = (JTextComponent) combo.getEditor().getEditorComponent();
tc.getDocument().addDocumentListener(this);
The DocumentListener interface has methods that are called whenever the Document backing the editor is modified (insertUpdate, removeUpdate, changeUpdate).
You can also use an anonymous class for finer-grained control of where events are coming from:
final JTextComponent tcA = (JTextComponent) comboA.getEditor().getEditorComponent();
tcA.getDocument().addDocumentListener(new DocumentListener() {
... code that uses comboA ...
});
final JTextComponent tcB = (JTextComponent) comboB.getEditor().getEditorComponent();
tcB.getDocument().addDocumentListener(new DocumentListener() {
... code that uses comboB ...
});
You can use somthing like this:
JComboBox cbListText = new JComboBox();
cbListText.addItem("1");
cbListText.addItem("2");
cbListText.setEditable(true);
final JTextField tfListText = (JTextField) cbListText.getEditor().getEditorComponent();
tfListText.addCaretListener(new CaretListener() {
private String lastText;
#Override
public void caretUpdate(CaretEvent e) {
String text = tfListText.getText();
if (!text.equals(lastText)) {
lastText = text;
// HERE YOU CAN WRITE YOUR CODE
}
}
});
this sounds like the best solution
jComboBox.getEditor().getEditorComponent().addKeyListener(new java.awt.event.KeyAdapter() {
public void keyReleased(java.awt.event.KeyEvent evt) { //add your hadling code here:
} });

Categories

Resources