Reputation: 13
I am attempting to make a basic character creation planner in netbeans as an attempt to learn Java so forgive me if my question is a bit silly.
I have created the a text field that needs to be updated with a formula each time the relevant stat is changed.
In this example the luck and charisma stat affects the barter skill. So when I change either of these stats I need the formula to run again to update the barter skill.
Currently the formula runs on creation of the object but not on update of another stat.
Here is my current (relevant) code:
package AppPackage;
import javax.swing.JOptionPane;
public class StartGUI extends javax.swing.JFrame {
public StartGUI() {
initComponents();
}
@SuppressWarnings("unchecked")
// <editor-fold defaultstate="collapsed" desc="Generated Code">
private void initComponents() {
BarterPts = new javax.swing.JTextField();
Chr = new javax.swing.JTextField();
ChrPlus = new javax.swing.JButton();
Luck = new javax.swing.JTextField();
BarterPts.setEditable(false);
BarterPts.setText("0");
BarterPts.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
BarterPtsActionPerformed(evt);
}
});
getContentPane().add(BarterPts, new org.netbeans.lib.awtextra.AbsoluteConstraints(470, 110, 30, -1));
int a = Integer.parseInt(Chr.getText()),l = Integer.parseInt(Luck.getText()),barter;
barter = (2 + (a*2) + (l/2) );
BarterPts.setText(String.valueOf(barter));
//plus button to add to the Charisma stat.
private void ChrPlusActionPerformed(java.awt.event.ActionEvent evt) {
int a = Integer.parseInt(Chr.getText()),i,l = Integer.parseInt(SLeft.getText());
if (a == 10) {
//print error
JOptionPane.showMessageDialog(null, "The Charisma value cannot exceed 10.", "Error", JOptionPane.ERROR_MESSAGE);
}
else if (l == 0) {
JOptionPane.showMessageDialog(null, "You have no special points remaining.", "Error", JOptionPane.ERROR_MESSAGE);
}
else{
l=--l;
SLeft.setText(String.valueOf(l));
i=++a;
Chr.setText(String.valueOf(i));
}
}
private void BarterPtsActionPerformed(java.awt.event.ActionEvent evt) {
}
There are variable declarations below but I have not included them.
Upvotes: 1
Views: 2068
Reputation: 347334
As a learning exercise, now might be a good time to learn about Model-View-Controller. Swing uses a form the MVC which more like M-VC, where the view and contoller are more tightly bound then a pure MVC implementation might have it.
The point here is, you want to separate the model (the data and the rules) from the view(/controller). The view/controller becomes a way in which you can "manipulate" the model but because it's separated, you can change the model and the way it works without need to change the view
Another principle which you should learn is the concept of Programming to interface, to implementation (see Program to an Interface, Fool and Program to an interface, not an implementation for some starters)
Basically, this decouples your code further, meaning that parts of your code aren't making assumptions about the state of other parts or their functionality and allows you to change the physical implementation (the rules) without needing to change a whole bunch of code to accommodate it.
The first thing I might do is create an interface
which describes the very basic operations I would ever want anybody to have from my character sheet
public interface CharacterSheet {
public int getLuck();
public int getCharisma();
public int getBarter();
}
Because I'm particularly paranoid, I don't see why "everybody" should be able to change the character sheet when they want to, instead, I restrict this via another interface. Think about it, once the character is generated, you can't change the stats until sometime later (like after a quest is complete) and then you might want to use some "experience" rule/algorithm to distribute and update the stats, but that's me ;)
public interface MutableCharacterSheet extends CharacterSheet {
public void setLuck(int value);
public void setCharisma(int value);
public void addPropertyChangeListener(PropertyChangeListener listener);
public void removePropertyChangeListener(PropertyChangeListener listener);
}
Okay, now we have a means by which we can update the character sheet. This interface
also includes a Observer Pattern, which will allow you to detect when changes are made to the character sheet, so you can take appropriate actions.
Now, we need an implementation. Normally, I might create one or more abstract
implementations which provide the basic/common functionality, like the support for the PropertyChangeListener
for example, but in this case, I'll head staight to a default implementation...
public class DefaultCharacterSheet implements MutableCharacterSheet {
private PropertyChangeSupport propertyChangeSupport;
private int luck;
private int charisma;
private int barter;
public DefaultCharacterSheet() {
propertyChangeSupport = new PropertyChangeSupport(this);
}
@Override
public int getLuck() {
return luck;
}
@Override
public void setLuck(int value) {
if (luck != value) {
int old = luck;
this.luck = value;
propertyChangeSupport.firePropertyChange("luck", old, luck);
updateBarter();
}
}
@Override
public int getCharisma() {
return charisma;
}
@Override
public void setCharisma(int value) {
if (charisma != value) {
int old = charisma;
this.charisma = value;
propertyChangeSupport.firePropertyChange("charisma", old, charisma);
updateBarter();
}
}
protected void updateBarter() {
int luck = getLuck();
int charisma = getCharisma();
int old = barter;
// Or what ever formula you want to use
barter = (int) ((luck / 2d) * charisma);
propertyChangeSupport.firePropertyChange("barter", old, barter);
}
@Override
public int getBarter() {
return barter;
}
@Override
public void addPropertyChangeListener(PropertyChangeListener listener) {
propertyChangeSupport.addPropertyChangeListener(listener);
}
@Override
public void removePropertyChangeListener(PropertyChangeListener listener) {
propertyChangeSupport.removePropertyChangeListener(listener);
}
}
Now, this might "seem" weird. I've implemented the MutableCharacterSheet
, but what happens if I don't want the code to modify the character sheet? This is where the magic of OO comes into play.
Parts of the code which should never modify the sheet should only support the CharacterSheet
interface, this means I can pass them an instance of DefaultCharacterSheet
, but they will only see and be able to do what ever the interface CharacterSheet
allows them do to, Polymorphism baby!
Now, we need some way to display it to the user and allow them to interact with it...
import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.text.NumberFormat;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSpinner;
import javax.swing.JTextField;
import javax.swing.SpinnerNumberModel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
MutableCharacterSheet characterSheet = new DefaultCharacterSheet();
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new CharacterSheetPane(characterSheet));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class CharacterSheetPane extends JPanel {
private JSpinner luckField;
private JSpinner charismaField;
private JTextField barterField;
private MutableCharacterSheet characterSheet;
public CharacterSheetPane(MutableCharacterSheet sheet) {
this.characterSheet = sheet;
characterSheet.addPropertyChangeListener(new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
if ("barter".equals(evt.getPropertyName())) {
int value = (int) evt.getNewValue();
barterField.setText(NumberFormat.getNumberInstance().format(value));
}
}
});
luckField = new JSpinner(new SpinnerNumberModel(0, 0, 10, 1));
charismaField = new JSpinner(new SpinnerNumberModel(0, 0, 10, 1));
barterField = new JTextField(5);
barterField.setEditable(false);
luckField.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
characterSheet.setLuck((int) luckField.getValue());
}
});
charismaField.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
characterSheet.setCharisma((int) charismaField.getValue());
}
});
setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridx = 0;
gbc.gridy = 0;
gbc.insets = new Insets(2, 2, 2, 2);
gbc.anchor = GridBagConstraints.EAST;
add(new JLabel("Luck: "), gbc);
gbc.anchor = GridBagConstraints.WEST;
gbc.gridx++;
add(luckField, gbc);
gbc.anchor = GridBagConstraints.EAST;
gbc.gridx = 0;
gbc.gridy++;
add(new JLabel("Charisma: "), gbc);
gbc.anchor = GridBagConstraints.WEST;
gbc.gridx++;
add(charismaField, gbc);
gbc.anchor = GridBagConstraints.EAST;
gbc.gridx = 0;
gbc.gridy++;
add(new JLabel("Barter: "), gbc);
gbc.anchor = GridBagConstraints.WEST;
gbc.gridx++;
add(barterField, gbc);
}
}
}
So, as the luck and charisma fields are changed, we update the model, the model intern updates the barter
value, which triggers a PropertyChangeEvent
, which allows us to update the barter field
This might seem like a lot to take in, but if you can get your head around these basic principles, it will take a very long way.
Have a look at:
for more information about some of things I've done in the UI code. At your earliest convince, I highly recommend that you stop using the form editor and start hand coding your UI's. You will gain a greater appreciation of how layout managers work and can work together, the create complex UI's, as well as when and how to separate you code to reduce complexity and increased re-use. This will also help you learn when a form editor is useful and when it's not ;)
Upvotes: 1
Reputation: 2466
So, from your original question it looks like you are expecting an update to the text field based on a button click.
Currently, you have your ActionListener registered to your text field. Instead, your ActionListener should be registered to your button.
ChrPlus.addActionListener(final ActionEvent theEvent) {
BarterPts.setText(//New text here based off a function);
}
Registering the Listener to the button will force behavior on a click as expected.
Upvotes: 1