syntax: public class JEditorPane extends JTextComponent
A JEditorPane object is capable of displaying html and rtf format files. It is often used as a way to display on-line help information for applications.
A JEditorPane object needs an EditorKit object associated with it to provide the JEditorPane object with information on how to display a particular format.
Visuele Gebruikers Omgevingen: Swing 73
73/395
import javax.swing.*; import java.awt.*; import java.awt.event.*; import java.io.*; public class TestJEP extends JFrame { JEditorPane jep = null; JButton helpButton; JDialog helpDialog; public TestJEP() { helpDialog = new JDialog(this, "Help"); helpButton = new JButton("Help"); helpButton.addActionListener(new HelpButtonHandler()); JPanel p = new JPanel(); p.add(helpButton); getContentPane().add(p, BorderLayout.SOUTH); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); addWindowListener(new WinClosing()); setBounds(100, 100, 400, 400); setVisible(true); } // When the "Help" button is pressed, the actionPerformed() method // is called and the JEditorPane() object is created and displayed. class HelpButtonHandler implements ActionListener { public void actionPerformed(ActionEvent ae) { jep = new JEditorPane(); // The location of the HTML file will have to be changed to // correspond to its location on your system. String file = "file:///j:/i2_2001/help.html"; try {
jep.setPage(file);
} catch (IOException ioe) {
System.out.println(ioe);
}
helpDialog.getContentPane().add(new JScrollPane(jep)); helpDialog.setBounds(200, 200, 400, 300); helpDialog.show(); } } public static void main(String args[]) {
TestJEP tjep = new TestJEP(); } }
Visuele Gebruikers Omgevingen: Swing 74
74/395
JTree Een JTree is één van de ingewikkeldste onderwerpen van Java. We geven gewoon zeer eenvoudige vormen ervan: Hierdoor weet je wat een JTree is.
public class Swing9 extends JApplet {public void init() {Container c=getContentPane(); c.setLayout(new FlowLayout()); JTree l= new JTree(); c.add(l); JTree l2= new JTree(new String[]{"een","twee","drie"}); c.add(l2); } }
Als je gewoon een object maakt van de klasse JTree, zal een standaard test JTree worden getoond. Je kan een JTree ook initialiseren met behulp van een array van strings. Andere mogelijkheden komen later.
Een eenvoudige JTable
import javax.swing.*; import javax.swing.table.*; import java.awt.*; import java.awt.event.*; public class SwingJTable extends JFrame { JTable jt; public SwingJTable() {
Object[][] data = { {"Jackson", new Integer(4), "March 21" }, { "Zachary", new Integer(2), "May 12" } }; String[] headers = {
"Name", "Age", "Birthday" };
jt = new JTable(data,headers); jt.setGridColor(Color.red); jt.setRowSelectionAllowed(true); jt.setRowSelectionInterval(1, 1); getContentPane().add(jt, BorderLayout.CENTER); addWindowListener(new WinClosing()); setBounds(100, 100, 300, 200); setVisible(true);
Visuele Gebruikers Omgevingen: Swing 75
75/395
try {Thread.sleep(10000);}catch(Exception e){} data[0][0]="gewijzigd"; repaint(); } public static void main(String[] args) {
SwingJTable tjt = new SwingJTable(); } }
class WinClosing extends WindowAdapter { public void windowClosing(WindowEvent we) { System.exit(0); } }
In bovenstaand programma initializeren we een JTable met behulp van een matrix van objecten. Voor elk van deze objecten zal de toString functie worden opgeroepen. U kan ook elementen wijzigen. We veranderen bijvoorbeeld de inhoud van data[0][0]. Je moet dan wel een repaint doen opdat de nieuwe waarde automatisch hertekend wordt. Hetzelfde kan je ook bereiken met:
jt.setValueAt("gewijzigd",0,0); In dit geval is er geen repaint nodig.
JTable Je kan tabellen ook initializeren met behulp van een vector die alle rijen bevat. Elk element van deze vector (een rij) is zelf terug een vector die alle waarden in die rij bevat. Dit is een voor de hand liggende constructor. Zie hiervoor de API documentatie bij JTable.
public class Swing10 extends JApplet {public void init() {Container c=getContentPane(); c.setLayout(new FlowLayout()); TableModel dataModel = new AbstractTableModel() { public int getColumnCount() { return 5; } public int getRowCount() { return 10;} public Object getValueAt(int row, int col) { return new Integer(row*col); } }; JTable table = new JTable(dataModel); //JScrollPane scrollpane = new JScrollPane(table); c.add(table); } }}
Tabellen en ook JList kunnen niet alleen met een opsomming van concrete waarden geïnitializeerd worden, maar ook met een Abstract…Model. Dit is een klasse die de waarden zal genereren. Bij een AbstractTableModel is er een routine getValueAt(row, col) die u moet opgeven en die voor een bepaalde row,col een object moet teruggeven dat dan in de tabel op die rij en kolom zal verschijnen. Elk element kan dusdanig berekend worden.
Visuele Gebruikers Omgevingen: Swing 76
76/395
JTable (Grant Palmer) Bij lijsten konden we twee soorten lijsten maken : de eerste soort werd geïnitialiseerd met behulp van een array van strings. Dit type lijst kon tijdens de uitvoering niet meer uitgebreid worden. De tweede type lijst werkte met een DefaultListModel. Het voordeel hiervan was dat deze lijst tijdens de uitvoering van het programma nog uitgebreid kon worden. Zo ook bij tabellen. Wil je werken met tabellen waar je achteraf nog rijen aan kunt toevoegen, dan moet je werken met een DefaultTableModel.
import javax.swing.*; import javax.swing.table.*; import java.awt.*; import java.awt.event.*; public class TestJTable extends JFrame implements ActionListener { JTable jt; DefaultTableModel dtm; JLabel nameLabel, ageLabel, birthdayLabel; JTextField nameTF, ageTF, birthdayTF; JButton addButton;
Visuele Gebruikers Omgevingen: Swing 77
77/395
public TestJTable() { Object[][] data = { { "Jackson", new Integer(4), "March 21" String[] headers = { "Name", "Age", "Birthday" };
}, { "Zachary", new Integer(2), "May 12"
// A DefaultTableModel object is created and initialized with some // data and headers. dtm = new DefaultTableModel(2, 3); dtm.setDataVector(data, headers); // A JTable object is created using the DefaultTableModel object // as its data model. jt = new JTable(dtm); jt.setGridColor(Color.red); jt.setRowSelectionAllowed(true); jt.setRowSelectionInterval(1, 1); // Some GUI components are added that are used to add rows to // the JTable. nameLabel = new JLabel("Name: "); nameLabel.setForeground(Color.black); ageLabel = new JLabel(" Age: "); ageLabel.setForeground(Color.black); birthdayLabel = new JLabel(" Birthday: "); birthdayLabel.setForeground(Color.black); nameTF = new JTextField(10); ageTF = new JTextField(3);
birthdayTF = new JTextField(12);
addButton = new JButton("add"); addButton.addActionListener(this); JPanel p = new JPanel(); p.add(nameLabel); p.add(nameTF); p.add(ageLabel); p.add(ageTF); p.add(birthdayLabel); p.add(birthdayTF); p.add(addButton); // The JTable is placed inside a JScrollPane before it is placed // within the JFrame JPanel pc = new JPanel(); pc.add(new JScrollPane(jt, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED)); getContentPane().add(pc, BorderLayout.CENTER); getContentPane().add(p, BorderLayout.SOUTH); addWindowListener(new WinClosing()); setBounds(100, 100, 700, 500); setVisible(true); } // whenever the "add" JButton is pressed, the actionPerformed() // method is called and a new row is added to the JTable public void actionPerformed(ActionEvent ae) { String name = nameTF.getText(); Integer age = new Integer(ageTF.getText()); String birthday = birthdayTF.getText(); Object[] newData = { name, age, birthday dtm.addRow(newData);
};
} public static void main(String[] args) {
TestJTable tjt = new TestJTable(); } }
// The WinClosing class terminates the program when the window is closed class WinClosing extends WindowAdapter {
Visuele Gebruikers Omgevingen: Swing 78
78/395
} };
public void windowClosing(WindowEvent we) { System.exit(0); } }
Visuele Gebruikers Omgevingen: Swing 79
79/395
Listeners Laat ons even een eigen component maken. We maken een tekstveldje waarop je kan klikken, zodat het de focus krijgt en geel kleurt. Je kan dan letters intypen. De letters bewegen wel constant op en neer. Hiervoor zijn verschillende listeners nodig. Je moet er met de muis op kunnen klikken om het de focus te geven: MouseListener. Je moet lettes kunnen intypen: KeyListener (of KeyAdapter). Je moet ook kunnen dedecteren wanneer een veldje de focus verliest: FocusListener. Tenslotte wordt er met eenTimer gewerkt die 5 maal per seconde zijn luisteraars zal verwittigen: ActionListener.
import java.awt.*; import javax.swing.*; import java.awt.event.*; class myTextField extends JPanel {boolean focus=false; Timer tim; char [] letter=new char[100]; int aantal=0,offset; public myTextField() {setPreferredSize(new Dimension(100,30)); grabFocus(); addKeyListener(new KeyAdapter() {public void keyTyped(KeyEvent e) {if(aantal<100){ letter[aantal]=e.getKeyChar(); aantal++;} repaint(); } } ); addMouseListener(new MouseAdapter() {public void mousePressed(MouseEvent e) {focus=true; repaint(); grabFocus(); } } ); addFocusListener(new FocusAdapter() {public void focusLost(FocusEvent e) {focus=false;repaint(); } } ); tim=new Timer(200,new ActionListener() {public void actionPerformed(ActionEvent e) {repaint();}} ); tim.start(); } public void paint(Graphics g) {super.paint(g); if(focus) g.setColor(Color.yellow); else g.setColor(Color.white); g.fillRect(1,1,100,30); g.setColor(Color.black); g.drawRect(1,1,100,30); for(int i=0;i
Als je met de muis op het tekstveldje klikt, gaan we de veranderlijke focus op true zetten. Door de repaint zal paint worden opgeroepen waardoor de achtergrond geel wordt. Let vooral op de grabFocus oproep. Deze is nodig omdat we overerven van een JPanel. Een JPanel kan nooit de focus krijgen. (Je kan een panel normaal niet met de muis selecteren;). Dit is geen probleem omdat we zelf muiskliks opvangen en expliciet met behulp van grabFocus de focus geven aan onze JPanel. Zonder focus worden de ingetypte letters niet opgevangen. We moeten dan nog wel dedecteren wanneer een andere JPanel de focus krijgt. Dit kunnen we doen met behulp van een FocusAdapter. We zetten dan focus op false en hertekenen. Als we letters intypen, zal onze keylistener deze letters in een array plaatsen en vervolgens hertekenen met behulp van repaint. Onze timer gaat gewoon om de 200 milliseconden hertekenen. In de paint gaan we met behulp van een random functie een offset per letter bepalen. We gaan aldus letter per letter op een andere plaats tekenen.
Visuele Gebruikers Omgevingen: Swing 80
80/395
In de volgende toepassing gebruiken we gewoon 4 objecten van onze eigen tekstveldje. public class SwingKeyListener extends JFrame {Container c; public SwingKeyListener() {setBounds(10,10,250,180); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); c=getContentPane(); c.setLayout(new FlowLayout()); c.add(new myTextField()); c.add(new myTextField()); c.add(new myTextField()); c.add(new myTextField()); setVisible(true); } public static void main(String args[]){new SwingKeyListener();}}
KeyListeners Om op key’s te reageren hebben we de eenvoudige setMnemonic waarmee je eenvoudig een ‘alt’ toets kunt verbinden met een JButton, JRadioButton, JCheckBox. Je kan ook met ActionMap’s en InputMap’s acties verbinden aan toetsaanslagen. Als je algemener zelf alle toetsaanslagen voor een component wilt dedecteren (je wilt bijvoorbeeld een kleine editor zelf maken), dan moet je met KeyListeners werken.
KeyListener: keyPressed, keyTyped, keyReleased
y
oorspronkelijk stond er 'y', door bij het maken van schermafdruk moet ik de 'alt' toets indrukken. Maar er stond een y In volgende toepassing kan je de kleur van de panel veranderen in geel of
groen door de toetsen ‘y’ of ‘g’ in te drukken. Deze zijn verbonden met een KeyListener die bij de applet zelf hoort. Het tekstveldje heeft ook een keylistener die op de ‘r’ of ‘b’ zal reageren en hierdoor de kleur van het paneeltje in rood of blauw veranderen. Het tekstveldje zal echter alleen maar key events opvangen indien het de focus heeft. (Daarom staat er in de toepassing ook een JButton ‘druk’: door hier op te klikken kunnen we expliciet de focus aan het tekstveldje ontnemen en toekennen aan de JButton).
t t
Als we vervolgens het tekstveldje de focus geven door er een 't' in te typen, dan krijgt de applet ook nog eens de keypressed en keyreleased van de t door. (Dit komt omdat het tekstveldje een deeltje van de applet is en klikken op het tekstveldje is ook klikken op het venster. Merk op dat keytyped event vanuit het tekstveldje niet wordt doorgegeven naar de applet omdat het tekstveldje deze zelf opvangt.
Visuele Gebruikers Omgevingen: Swing 81
81/395
We hebben drie event’s: keyPressed (u drukt in), keyReleased (u laat los), keyTyped (de samenvatting). Een hoofdletter ‘A’ zal 5 events veroorzaken: keyPressed shift, keyPressed a, keyReleased shift, keyReleased a, keyTyped A. Voor het JTextField vangen we alleen de keyPressed op. opgevangen door het tekstveldje zelf.
De keyTyped worden al
public class SwingH extends JApplet { private JLabel l,l2,l3,l4; private JTextField t; private JPanel p; public void init() {Container c=getContentPane(); c.setLayout(new FlowLayout()); l=new JLabel(); c.add(l); l2=new JLabel(); c.add(l2); l3=new JLabel(); c.add(l3); l4=new JLabel(); c.add(l4); p=new JPanel(); p.setPreferredSize(new Dimension(50,50));c.add(p); c.add(new JButton("druk")); t=new JTextField(10); c.add(t); //twee listeners t.addKeyListener( new KeyAdapter() {public void keyPressed(KeyEvent e) {l.setText("textfield keypressed :"+e.getKeyChar()); if(e.getKeyCode()==KeyEvent.VK_R) p.setBackground(Color.red); if(e.getKeyCode()==KeyEvent.VK_B) p.setBackground(Color.blue); } }); this.addKeyListener( new KeyListener() {public void keyTyped(KeyEvent e) {l2.setText("applet keytyped :"+e.getKeyChar());} public void keyPressed(KeyEvent e) {if(e.getKeyCode()==KeyEvent.VK_F1 ) JOptionPane.showMessageDialog(SwingH.this,"F1"); l3.setText("applet keyPressed :"+e.getKeyChar());} public void keyReleased(KeyEvent e) {l4.setText("applet keyReleased :"+e.getKeyChar()); if(e.getKeyChar()=='y') p.setBackground(Color.yellow); if(e.getKeyChar()=='g') p.setBackground(Color.green); } }); this.requestFocus(); } }
Voor de applet gaan we alle keyevent opvangen. Hiervoor gaan we een KeyListener aan ‘this’ verbinden. We vangen alle keyTyped, keyPressed, keyReleased events op. U merkt dat u zelfs onderscheid kunt maken tussen het indrukken en het loslaten van de toets. Stel dat u een tekenprogramma maakt en dat de lijn die u tekent in het rood moet verschijnen wanneer u de ‘r’ ingedrukt houdt, dan is deze optie handig. Probeer dit maar eens als opdracht: een tekenprogramma waarbij u tijdens het tekenen de kleur van de lijnen met toetsen kunt kiezen, en dit zonder de muis los te laten tijdens het tekenen. De ingetypte letters kunnen we op twee wijzen opvragen: getKeyCode en getKeyChar. De getKeyCode is algemener en hiermee kan je ook speciale toetsen zoals ‘escape’, ‘pijltje’ en zo opvangen. Ben je alleen in letters geïnteresseerd, dan moet je met getKeyChar werken.
Visuele Gebruikers Omgevingen: Swing 82
82/395
ActionListener Naast setAction kan je ook met ActionListener’s werken voor JButton . Hierdoor kunnen meerdere geïnteresseerde klassen zich melden als luisteraar van de button.
public class Swing2 extends JApplet {String str="eerstebutton";int tel=0; JButton l; public void init() {Container c=getContentPane(); c.setLayout(new FlowLayout()); l= new JButton(str); l.addActionListener( new ActionListener() {public void actionPerformed(ActionEvent e) {str=str+(tel++)+" "; l.setText(str); Swing2.this.setSize(150+tel*2,70+tel*2); if(tel<5) l.doClick(); } }); c.add(l); }}
door 1x te klikken, wordt er automatisch 5x geklikt op de button.(programmatorisch)
Elke maal als we klikken zal een teller verhogen en zal de grootte van het venster aangepast worden. We kunnen hiervoor met Swing2.this.setSize werken. (this is niet voldoende daar dit wijst naar de innerklasse die overerft van ActionListener).
L isteners voor JSlider en JList
public class Swing3 extends JApplet { JLabel lab=new JLabel(); DefaultListModel v=new DefaultListModel(); JList lis=new JList(v); // MODEL CONTROL VIEW JSlider l= new JSlider();
Visuele Gebruikers Omgevingen: Swing 83
83/395
public void init() {Container c=getContentPane(); c.setLayout(new FlowLayout()); c.add(lab); c.add(l); v.addElement("een"); // de JList zelf bevat GEEN elementen v.addElement("twee");// maar wordt verwittigd (is observer) v.addElement("drie");// l.addChangeListener(new ChangeListener() {public void stateChanged(ChangeEvent e) { lab.setText(""+Swing3.this.l.getValue() ); }}); c.add(new JScrollPane(lis)); lis.setVisibleRowCount(10); lis.addListSelectionListener(new ListSelectionListener() {public void valueChanged(ListSelectionEvent e) {//JOptionPane.showMessageDialog(null,"selected"); v.addElement(""+lis.getSelectedValue()+ "_"+lis.getSelectedIndex() ); }}); ListModel bigData = new AbstractListModel() { public int getSize() { return 100; } public Object getElementAt(int index) { return "Index " + index; } }; JList bigDataList = new JList(bigData); c.add(new JScrollPane(bigDataList)); } }
Een ChangeListener zal reageren op het verschuiven van een JSlider. Een ListSelectionListener zal reageren op het selecteren uit een JList. (In bovenstaand geval zullen er steeds nieuwe waarden aan de lijst worden toegevoegd: bij selecteren uit de lijst, wordt hij langer en langer). Leuk is de bigDataList. Hiervoor worden geen concrete waarden opgesomd. Je kan hiermee evengoed een lijst van 100000 elementen maken. Je werkt hiervoor met een AbstractListModel die een methode getElementAt(index) heeft en waarmee waarden kunnen gegenereerd worden.(berekend worden).
Menu samen met JInternalFrame Het komt dikwijls voor dat je met internalframes werkt en dat je met behulp van een menu acties wilt ondernemen die voor het internal frame dat de focus heeft. U weet dat u met een JDesktopPane moet werken als u internal frames wilt gebruiken. De methode getSelectedFrame hebben we hiervoor nodig: het bevat een referentie naar het frame dat de focus heeft.
Visuele Gebruikers Omgevingen: Swing 84
84/395
import import import import
javax.swing.*; java.awt.*; java.awt.event.*; java.beans.*;
public class Swing5 extends JFrame {JMenuBar bar=new JMenuBar(); JMenu men=new JMenu("opties"); JMenuItem item=new JMenuItem("sluit actief subvenster"); JMenuItem item2=new JMenuItem("plaats venster links boven"); public Swing5() {Container c=getContentPane(); final JDesktopPane theDesktop = new JDesktopPane(); //theDesktop.setLayout(new FlowLayout()); JInternalFrame cb= new JInternalFrame("stip mij aan",true,true,true,true); theDesktop.add(cb);cb.setVisible(true); cb.setBounds(10,10,120,40); JInternalFrame cb2= new JInternalFrame("stip mij aan2",true); //cb2 is vergrootbaar theDesktop.add(cb2);cb2.setVisible(true); cb2.setBounds(150,10,100,30); c.add(theDesktop); men.add(item);men.add(item2); bar.add(men); setJMenuBar(bar); setBounds(1,1,3500,250); setVisible(true); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); item.addActionListener(new ActionListener() {public void actionPerformed(ActionEvent e) {JInternalFrame frame=theDesktop.getSelectedFrame(); if(frame!=null) {frame.dispose();} theDesktop.repaint(); } }); item2.addActionListener(new ActionListener() {public void actionPerformed(ActionEvent e) {JInternalFrame frame=theDesktop.getSelectedFrame(); frame.setLocation(10,10); theDesktop.repaint(); } }); } public static void main(String args[]) {new Swing5(); } }
Visuele Gebruikers Omgevingen: Swing 85
85/395
JCombobox: ItemListener Als je selecteert uit een List, JList, JComboBox, zullen de geregistreerde ItemListeners verwittigd worden.
public class Swing7 extends JApplet {JComboBox comb= new JComboBox(new String[]{"een","twee","drie"}); JLabel lab=new JLabel(); JLabel lab2=new JLabel(); JButton but=new JButton("voeg toe aan combobox"); public void init() {Container c=getContentPane(); c.setLayout(new FlowLayout()); c.add(lab); c.add(comb); c.add(lab2); comb.addItemListener(new ItemListener() {public void itemStateChanged(ItemEvent ee) {lab2.setText("item "+ ee.getItem()); } }); JPanel p =new JPanel(); p.add(new JLabel("ha")); p.add(but); p.add(new JSlider()); but.addActionListener(new ActionListener() {public void actionPerformed(ActionEvent e) { lab.setText("");lab2.setText(""); comb.addItem("button "+e.getActionCommand()); } }); JTabbedPane t=new JTabbedPane(); t.setSize(300,300); t.insertTab("1",null,new JLabel("11"),"111",0); t.addTab("een",p); t.addTab("twee",new JLabel("label2")); c.add(t); } }
JS crollBar en AdjustmentListener (Grant Palmer) Als je een scrollbar verschuift, worden de geregistreerde adjustmentlisteners verwittigd doordat de routines adjustmentValueChange wordt opgeroepen.
import javax.swing.*;
Visuele Gebruikers Omgevingen: Swing 86
86/395
import java.awt.*; import java.awt.event.*; public class TestJSB extends JFrame { JScrollBar jsb; JTextField jtf; int i, maxValue, extentValue; public TestJSB() { maxValue = 100; extentValue = 5; jsb = new JScrollBar(JScrollBar.VERTICAL, 1, extentValue, 1, maxValue + extentValue); jsb.setPreferredSize(new Dimension(20, 85)); jsb.addAdjustmentListener(new JScrollBarHandler()); jtf = new JTextField(3); jtf.addActionListener(new JTextFieldHandler()); i = maxValue + 1 - jsb.getValue(); jtf.setText("" + i); JPanel p = new JPanel(); p.add(jtf); p.add(jsb); getContentPane().add(p); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setBounds(100, 100, 300, 150); setVisible(true); } class JScrollBarHandler implements AdjustmentListener { public void adjustmentValueChanged(AdjustmentEvent ae) { i = maxValue + 1 - jsb.getValue(); jtf.setText("" + i); } } class JTextFieldHandler implements ActionListener { public void actionPerformed(ActionEvent ae) { i = maxValue + 1 - Integer.parseInt(jtf.getText()); jsb.setValue(i); } } public static void main(String args[]) { TestJSB tjsb = new TestJSB(); } }
JScrollBar z elf zetten Elke keer we op de button klikken, zal de scrollbar 10 verder gezet worden.
public class Swing8 extends JApplet
Visuele Gebruikers Omgevingen: Swing 87
87/395
{JButton but=new JButton("verhoog Scroller"); JScrollBar scrol= new JScrollBar(); int i=0; public void init() {Container c=getContentPane(); c.setLayout(new FlowLayout()); c.add(scrol); scrol.setPreferredSize(new Dimension(30,200)); JToolBar jt=new JToolBar(); jt.add(but); jt.add(new JButton("twee")); but.addActionListener(new ActionListener() {public void actionPerformed(ActionEvent e) {scrol.setValue(i+=10);if(i>100)i=0; } }); c.add(jt); } }
WindowListener WindowListeners kunnen gebruikt worden voor reactie op verkleinen tot icoon, terug vergroten vanuit icoon, sluiten. De interface WindowListener heeft vele routines en omdat we meestal maar geïnteresseerd zijn in één (vb WindowClosing), zal men meestal overerven van WindowAdapter. Deze klasse voorziet lege versies voor alle routines uit de interface WindowListener.
import javax.swing.*; import java.awt.*; import java.awt.event.*; public class Swing16 extends JFrame { static public void main(String args[]) { new mijnFrame("nog een ander venster"); } } class mijnFrame extends JFrame {DefaultListModel lm =new DefaultListModel(); public mijnFrame(String titel) { super(titel); Container c=this.getContentPane(); c.setLayout(new FlowLayout()); c.add(new JList(lm)); this.setSize( 120,50); this.setVisible(true); addWindowListener(new WindowListener() { public void windowActivated(WindowEvent e) // Invoked when the window is set to be the user's active window, // which means the window (or one of its subcomponents) will receive keyboard //events. {lm.addElement("Activated");} public void windowClosed(WindowEvent e) // Invoked when a window has been closed as the result of
Visuele Gebruikers Omgevingen: Swing 88
88/395
//
calling dispose on the window. {JOptionPane.showMessageDialog(null,"Closed");} public void windowClosing(WindowEvent e) // Invoked when the user attempts to close the window // from the window's system menu. {JOptionPane.showMessageDialog(null,"Closing");} public void windowDeactivated(WindowEvent e) //Invoked when a window is no longer the user's active window, which means //that keyboard events will no longer be delivered //to the window or its subcomponents. {lm.addElement("DeActivated");} public void windowDeiconified(WindowEvent e) //Invoked when a window is changed from a minimized to a normal state. {lm.addElement("Deiconified");} public void windowIconified(WindowEvent e) //Invoked when a window is changed from a normal to a minimized state. {lm.addElement("Iconified");} public void windowOpened(WindowEvent e) //Invoked the first time a window is made visible. {lm.addElement("windowOpened");} }); }}
Visuele Gebruikers Omgevingen: Swing 89
89/395
Listeners: dansende letters Laat ons even een eigen component maken. We maken een tekstveldje waarop je kan klikken, zodat het de focus krijgt en geel kleurt. Je kan dan letters intypen. De letters bewegen wel constant op en neer. Hiervoor zijn verschillende listeners nodig. Je moet er met de muis op kunnen klikken om het de focus te geven: MouseListener. Je moet lettes kunnen intypen: KeyListener (of KeyAdapter). Je moet ook kunnen dedecteren wanneer een veldje de focus verliest: FocusListener. Tenslotte wordt er met eenTimer gewerkt die 5 maal per seconde zijn luisteraars zal verwittigen: ActionListener.
import java.awt.*; import javax.swing.*; import java.awt.event.*; class myTextField extends JPanel {boolean focus=false; Timer tim; char [] letter=new char[100]; int aantal=0,offset; public myTextField() {setPreferredSize(new Dimension(100,30)); grabFocus(); addKeyListener(new KeyAdapter() {public void keyTyped(KeyEvent e) {if(aantal<100){ letter[aantal]=e.getKeyChar(); aantal++;} repaint(); } } ); addMouseListener(new MouseAdapter() {public void mousePressed(MouseEvent e) {focus=true; repaint(); grabFocus(); } } ); addFocusListener(new FocusAdapter() {public void focusLost(FocusEvent e) {focus=false;repaint(); } } ); tim=new Timer(200,new ActionListener() {public void actionPerformed(ActionEvent e) {repaint();}} ); tim.start(); } public void paint(Graphics g) {super.paint(g); if(focus) g.setColor(Color.yellow); else g.setColor(Color.white); g.fillRect(1,1,100,30); g.setColor(Color.black); g.drawRect(1,1,100,30); for(int i=0;i
Als je met de muis op het tekstveldje klikt, gaan we de veranderlijke focus op true zetten. Door de repaint zal paint worden opgeroepen waardoor de achtergrond geel wordt. Let vooral op de grabFocus oproep. Deze is nodig omdat we overerven van een JPanel. Een JPanel kan nooit de focus krijgen. (Je kan een panel normaal niet met de muis selecteren;). Dit is geen probleem omdat we zelf muiskliks opvangen en expliciet met behulp van grabFocus de focus geven aan onze JPanel. Zonder focus worden de ingetypte letters niet opgevangen. We moeten dan nog wel dedecteren wanneer een andere JPanel de focus krijgt. Dit kunnen we doen met behulp van een FocusAdapter. We zetten dan focus op false en hertekenen. Als we letters intypen, zal onze keylistener deze letters in een array plaatsen en vervolgens hertekenen met behulp van repaint. Onze timer gaat gewoon om de 200 milliseconden hertekenen. In de paint gaan we met behulp van een random functie een offset per letter bepalen. We gaan aldus letter per letter op een andere plaats tekenen.
Visuele Gebruikers Omgevingen: Swing 90
90/395
In de volgende toepassing gebruiken we gewoon 4 objecten van onze eigen tekstveldje. public class SwingKeyListener extends JFrame {Container c; public SwingKeyListener() {setBounds(10,10,250,180); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); c=getContentPane(); c.setLayout(new FlowLayout()); c.add(new myTextField()); c.add(new myTextField()); c.add(new myTextField()); c.add(new myTextField()); setVisible(true); } public static void main(String args[]){new SwingKeyListener();}}
Opdracht
Maak een toepassing waarin je twee rechthoeken hebt met elk vier delen. U voorziet drie type luisteraars die verwittigd worden indien in één van de vier kwadranten wordt geklikt: - een staafjes diagram dat visueel weergeeft hoeveel maal in elk kwadrant werd geklikt - een rechthoek waarin gewoon 4 getallen komen - een rechthoek waarin de procentuele verhouding van het aantal kliks per kwadrant wordt weergegeven. - U kunt er nog bij verzinnen: een pie-chart,…. Van elke luisteraar moeten twee objecten worden gemaakt die elk bij een andere eventgenerator worden geregistreerd.
Voorbeeld in Java zelf van het Observer patroon
Visuele Gebruikers Omgevingen: Swing 91
91/395
In het onderstaand programma moet de gebruiker niets doen, het programma 'beweegt' zelf. Timer gaan op regelmatige tijdsintervallen de luistenaars verwittigen door de functie actionPerformed op te roepen. Dit programma heeft twee timers. De eerste zal om de seconde (1000 milliseconden) zijn
luistenaars verwittigen. De eerste luisteraar wordt aan de constructor van de timer doorgegeven. Dit komt omdat een timer zonder luisteraar geen zin heeft. De andere luisteraars worden toegevoegd met behulp van addActionListener. Alle luisteraars moeten de interface ActionListener implementeren. Door de eerste timer zal om de seconde de woorden tak en tok op het scherm verschijnen. De tweede timer zal om de 5 seconden zijn luisteraars verwittigen. Hierdoor wordt er op het scherm naar een nieuwe lijn gegaan en zal er een steeds grotere rechthoek op scherm worden getekend. /* leuk idee : maak een auto klasse. De auto moet zich over het scherm verplaatsen telkens hij door een timer wordt verwittigd. */ import javax.swing.Timer; import java.awt.event.*; class Luistenaar implements ActionListener {public void actionPerformed(ActionEvent e) {Scherm.schrijf("tok"); } } class Luistenaar2 implements ActionListener {public void actionPerformed(ActionEvent e) {Scherm.schrijf("tak"); } } class Luistenaar3 implements ActionListener {int y=2; public void actionPerformed(ActionEvent e) {Scherm.gotoxy(1,y++); } } class Generator implements ActionListener {public void run() {Scherm.clearDevice(); Timer tim=new Timer(1000, new Luistenaar() ); tim.start(); tim.addActionListener( new Luistenaar2() ); Timer tim2=new Timer(5000, new Luistenaar3() ); tim2.addActionListener(this);
Visuele Gebruikers Omgevingen: Swing 92
In dit geval geven we 'this' door als parameter naar 92/395 addActionListener. Hierdoor geven we aan dat de
tim2.start(); } public void actionPerformed(ActionEvent e) {Scherm.rectangle(300-x,70-x,310+x,70+x);x+=2; } private int x=1; } public class Test {public static void main(String []arg){new Generator().run();}}
Visuele Gebruikers Omgevingen: Swing 93
93/395
Eigen dingen maken Een eigen border maken Hiervoor moet je gewoon een interface Border implementeren. Deze interface heeft drie methoden: Insets getBorderInsets(Component c) Returns the insets of the border. boolean isBorderOpaque() Returns whether or not the border is opaque. void paintBorder(Component c, Graphics g, int x, int y, int width, int height) Paints the border for the specified component with the specified position and size.
Onze eigen boord bestaat uit twee blauwe lijnen opgevuld met rode lijnen en een kleine tekst: “firma x”. Vervolgens kunnen we deze boord overal gebruiken. import javax.swing.*; import javax.swing.border.*; import java.awt.event.*; import java.awt.*;
Visuele Gebruikers Omgevingen: Swing 95
94/395
/*interface javax.swing.Border Insets getBorderInsets(Component c) Returns the insets of the border. boolean isBorderOpaque() Returns whether or not the border is opaque. void paintBorder(Component c, Graphics g, int x, int y, int width, int height) Paints the border for the specified component with the specified position and size. */
class EigenBorder implements Border {public Insets getBorderInsets(Component c) {return new Insets(4,4,4,4);} public boolean isBorderOpaque() {return false;} public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) { Color oldColor = g.getColor(); g.setColor(Color.BLUE); g.drawRect(x+0, y+0, width-0-0-1, height-0-0-1); g.setColor(Color.RED); g.drawRect(x+1, y+1, width-1-1-1, height-1-1-1); g.drawRect(x+2, y+2, width-2-2-1, height-2-2-1); g.drawRect(x+3, y+3, width-3-3-1, height-3-3-1); g.drawRect(x+4, y+4, width-4-4-1, height-4-4-1); g.drawRect(x+5, y+5, width-5-5-1, height-5-5-1); g.setColor(Color.BLUE); g.drawRect(x+6, y+6, width-6-6-1, height-6-6-1); g.setColor(Color.BLACK); g.setFont(new Font("Dialog",Font.ITALIC,8)); g.drawString("firma x",7,7); g.setColor(oldColor); } }
public class Test1 extends JFrame { public void Test1() { } public static void main(String args[]) { Test1 mainFrame = new Test1(); mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); Container c=mainFrame.getContentPane(); c.setLayout(null); JButton b,b2,b3; b=new JButton("test");b2=new JButton("test2"); b3=new JButton("test3"); b.setBorder(new EigenBorder()); b.setBounds(10,30,70,30); b2.setBorder(new EigenBorder()); b2.setBounds(100,30,100,30); Visuele Gebruikers Omgevingen: Swing 96
95/395
b3.setBounds(220,30,70,50); b3.setBorder(new EigenBorder()); c.add(b);c.add(b2);c.add(b3); mainFrame.setSize(400, 400); mainFrame.setTitle("Eigen_layout"); c.setBackground(Color.CYAN); mainFrame.setVisible(true); } }
public class Test1 extends JFrame { public void Test1() { } public static void main(String args[]) { Test1 mainFrame = new Test1(); mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); Container c=mainFrame.getContentPane(); c.setLayout(null); JButton b,b2,b3; b=new JButton("test"); b2=new JButton("test2"); b3=new JButton("test3"); b.setBorder(new EigenBorder()); b.setBounds(10,30,70,30); b2.setBorder(new EigenBorder()); b2.setBounds(100,30,70,30); b3.setBounds(200,30,70,30); b3.setBorder(new EigenBorder()); c.add(b);c.add(b2);c.add(b3); mainFrame.setSize(400, 400); mainFrame.setTitle("Eigen_layout"); c.setBackground(Color.CYAN); mainFrame.setVisible(true); } }
Visuele Gebruikers Omgevingen: Swing 97
96/395
Een JTextField die alleen maar integers toelaat
We maken een klasse JIntegerField. Als de gebruiker iets intypt dat geen integer is, zal de achtergrond oranje kleuren. Als hij het terug verbeterd, zal de achtergrond terug wit worden. Hiervoor gaan we overerven van JTextField en in de constructor een keylistener toevoegen. Telkens de gebruiker iets ingetypt heeft, gaan we terug checken of het geheel wel een integer is. Indien niet gaan we de achtergrond oranje kleuren. We voegen ook een extra functie getGetal toe om de uiteindelijke waarde als een int op tek kunnen vragen. import javax.swing.*; import java.awt.event.*; import java.awt.*;
class JIntegerTextField extends JTextField {public JIntegerTextField() {super(); addKeyListener(new KeyAdapter() { public void keyReleased(KeyEvent e) {setBackground(Color.white); String tekst=getText(); try{int i=Integer.parseInt(tekst); } catch(Exception ee) {if(!tekst.equals("")){setBackground(Color.orange);} } } }); } public int getGetal() {int i; String tekst=getText(); try{i=Integer.parseInt(tekst); } catch(Exception ee){i=0;} return i; } }
Visuele Gebruikers Omgevingen: Swing 98
97/395
public class IntegerTextField extends JFrame { static JIntegerTextField b,b2,b3; public void IntegerTextField() { } public static void main(String args[]) { IntegerTextField mainFrame = new IntegerTextField(); mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); Container c=mainFrame.getContentPane(); c.setLayout(new FlowLayout()); b =new JIntegerTextField(); b.setPreferredSize(new Dimension(70,30)); b2=new JIntegerTextField(); b2.setPreferredSize(new Dimension(70,30)); b3=new JIntegerTextField(); b3.setPreferredSize(new Dimension(70,30)); c.add(b);c.add(b2); JButton b4=new JButton("som"); b4.setPreferredSize(new Dimension(70,30)); c.add(b4);c.add(b3); b4.addActionListener(new ActionListener() {public void actionPerformed(ActionEvent a) {b3.setText( ""+(b.getGetal()+b2.getGetal()) ); } }); mainFrame.setSize(400, 400); mainFrame.setTitle("integer textfield"); mainFrame.setVisible(true); }}
Een InputVerifier Een JComponent heeft een methode setInputVerifier. Hiermee kan een klasse die overerft van de abstracte klasse InputVerifier worden ingeplugd. Men moet hiervoor maar één methode herdefinieren: verify. Deze methode ‘verify’ geeft ‘true’ terug indien de input goed is. De methode verify krijgt de component waarvan de inhoud moet geverifieerd worden mee als argument.
Visuele Gebruikers Omgevingen: Swing 99
98/395
Zolang de methode verify geen true teruggeeft, blijft het veld de focus houden. (Je kan de component gewoon niet verlaten en verder gaan met een andere component. (Tab werkt niet)). De vorige oplossing om de component echt anders te kleuren, was mooier. Met een InputVerifier kan je wel iets verkeerd intypen, je kan gewoon niet meer het veld verlaten.
import javax.swing.*; import java.awt.event.*; import java.awt.*;
class IntegerVerifier extends InputVerifier { public boolean verify(JComponent input) { JTextField tf = (JTextField) input; String tekst=tf.getText(); try{int i=Integer.parseInt(tekst); } catch(Exception ee){return false;} return true; } }
public class IntegerTextField2 extends JFrame { static JTextField b,b2,b3;
Visuele Gebruikers Omgevingen: Swing 100
99/395
public void IntegerTextField2() { } public static void main(String args[]) { IntegerTextField2 mainFrame = new IntegerTextField2(); mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); Container c=mainFrame.getContentPane(); c.setLayout(new FlowLayout()); b =new JTextField();b.setPreferredSize(new Dimension(70,30)); b2=new JTextField();b2.setPreferredSize(new Dimension(70,30)); b3=new JTextField();b3.setPreferredSize(new Dimension(70,30)); b.setInputVerifier(new IntegerVerifier()); b2.setInputVerifier(new IntegerVerifier()); b3.setInputVerifier(new IntegerVerifier()); c.add(b);c.add(b2);c.add(b3); mainFrame.setSize(400, 400); mainFrame.setTitle("integer textfield"); mainFrame.setVisible(true); } }
Een eigen Button
import java.awt.*; import javax.swing.*; class myButton extends JButton { public myButton() {setPreferredSize(new Dimension(100,100)); } public void paint(Graphics g) {super.paint(g); g.setColor(Color.yellow); g.fillOval(50,50,20,20); } } public class SwingJButton extends JFrame {Container c; public SwingJButton() {setBounds(10,10,250,180); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); c=getContentPane(); c.setLayout(new FlowLayout()); c.add(new myButton()); setVisible(true); } public static void main(String args[]){new SwingJButton();}
Visuele Gebruikers Omgevingen: Swing 101
100/395
De routine paint staat in voor het tekenen van de JButton. We geven hiervan een eigen versie, maar roepen voor zekerheid eerst de basis versie super.paint op. Als je dit niet doet, dan heb je gewoon geen rechthoek, dan moet je alles tekenen, en ook zorgen dat de boord anders wordt getekend indien je de button indrukt. Eigenlijk roept de paint ook een paintComponent op die meer instaat voor het tekenen van het binnenste van de component. Je kan ook alleen paintComponent herdefinieren. tweede versie class myButton extends JButton { public myButton() {setPreferredSize(new Dimension(100,100)); } protected void paintComponent(Graphics g) {super.paintComponent(g); g.setColor(Color.yellow); g.fillOval(50,50,20,20); } }
Eigen JPanel Als je gewoon een eigen tekenvlak wilt hebben, kan je best overerven van Canvas (awt) of JPanel (Swing). Dit zijn de eenvoudigste lege Componenten. Je kan voor een JPanel een eigen paint voorzien. Weet wel dat een JPanel ook een Container is, maar probeer best niet naast het opgeven van een eigen paint ook de container te gebruiken door eigen componenten hieraan toe te voegen. Wil je toch een toepassing met een eigen tekening en
ook componenten, zet dan de eigen componenten in een andere gewone JPanel. class mijnCanvas extends Canvas { Image logo1; public mijnCanvas() {this.setSize(40,40); logo1= Toolkit.getDefaultToolkit().getImage("globe.gif"); } public void paint(Graphics g) {g.drawImage(logo1,0,0,this); g.drawLine(0,0,40,40); g.drawLine(0,40,40,0); } } class mijnpanel extends JPanel {public mijnpanel(){ setPreferredSize(new Dimension(100,50));
Visuele Gebruikers Omgevingen: Swing 102
101/395
} public void paint(Graphics g) { g.setColor(Color.blue); g.fillPolygon(new int[]{10,20 ,30,40, 50,60, 70,80, 90,100,1}, new int[] {1,50,1,50,1,50,1,50,1,50,50},11); } } public class Swing20 extends JApplet { public void init() {Container c=getContentPane(); c.setLayout(new FlowLayout()); c.add(new mijnCanvas()); c.add(new mijnpanel()); c.add(new mijnCanvas()); JPanel p=new JPanel(); p.add(new JButton("druk")); p.add(new mijnpanel()); p.add(new mijnCanvas()); p.add(new JSlider()); c.add(p); } }
In een gewone toepassing ga je een Image laden met behulp van Toolkit klasse. Toolkit.getDefaultToolkit geeft de Toolkit klasse voor uw besturingssysteem terug.Een polygon is een veelhoek waarvan je gewoon de coordinaten van de hoekpunten moet opgeven.
Uw eigen controls gebruiken Een Button die doorzichtig is: je kan de ondergrond erdoor zien:
Hiervoor gaan we gewoon overerven van de klasse JButton en in de constructor de methode setOpaque(false) oproepen. Op deze manier kunnen we onze eigen buttons, met eigen kleuren gebruiken. Het voordeel om eerst een eigen type button te maken is, dat je achteraf alle buttons van heel uw toepassing kunt aanpassen van look, gewoon door die eigen klasse wat aan te passen.
import javax.swing.*; import java.awt.event.*; import java.awt.*;
Visuele Gebruikers Omgevingen: Swing 103
102/395
class DoorzichtigButton extends JButton {public DoorzichtigButton(String titel) {super(titel); setOpaque(false);// doorzichtig }} public class Test1 extends JFrame { public void Test1() { } public static void main(String args[]) { Test1 mainFrame = new Test1(); mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); Container c=mainFrame.getContentPane(); c.setLayout(null); JButton b,b2,b3; b=new DoorzichtigButton("test");b2=new DoorzichtigButton("test2"); b3=new DoorzichtigButton("test3"); b.setBounds(10,30,70,30); b2.setBounds(100,30,70,30); b3.setBounds(200,30,70,30); c.add(b);c.add(b2);c.add(b3); mainFrame.setSize(400, 400); mainFrame.setTitle("Eigen_layout"); c.setBackground(Color.CYAN); mainFrame.setVisible(true); }}
Onze doorzichtige button geplaatst over een tekening
Je merkt dat de button echt doorzichtig zijn: de onderliggende tekening komt erdoor.
Visuele Gebruikers Omgevingen: Swing 104
103/395
import javax.swing.*; import java.awt.event.*; import java.awt.*;
class DoorzichtigButton extends JButton {public DoorzichtigButton(String titel) {super(titel); setOpaque(false);// doorzichtig }}
class TekeningPanel extends JPanel{ private Image img; MediaTracker tracker; public TekeningPanel(Image img){ tracker = new MediaTracker(this); this.img=img; tracker.addImage(img,0); try {// tekening wordt normaal alleen geladen als het echt getekend wordt. // met waitForAll wachten totdat hij echt geladen is //Start downloading the images. Wait until they're loaded. tracker.waitForAll(); } catch (InterruptedException e) {} }
Visuele Gebruikers Omgevingen: Swing 105
104/395
public void paintComponent(Graphics g){ super.paintComponent(g); g.drawImage(img,0,0,this); g.drawString("copywright",0,15); } }
public class Test2 extends JFrame { public void Test2() { } public static void main(String args[]) { Test2 mainFrame = new Test2(); mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); Container c=mainFrame.getContentPane(); c.setLayout(null); JButton b,b2,b3; b=new DoorzichtigButton("test");b2=new DoorzichtigButton("test2"); b3=new DoorzichtigButton("test3"); b.setBounds(10,30,70,30); b2.setBounds(100,30,70,30); b3.setBounds(200,30,70,30); c.add(b);c.add(b2);c.add(b3); Toolkit tk=Toolkit.getDefaultToolkit(); Image img=tk.getImage("water.gif"); JPanel tek1=new TekeningPanel(img); tek1.setBounds(10,10,200,200); c.add(tek1); mainFrame.setSize(400, 400); mainFrame.setTitle("Eigen_layout"); c.setBackground(Color.CYAN); mainFrame.setVisible(true); //!!!!even wachten en alles hertekenen is nodig als we geen mediatracker //gebruiken!!!!!!!!! //try{Thread.sleep(1000); }catch(Exception e){} //c.repaint(); }} We hebben een eigen klasse TekeningPanel gemaakt. Dit is een panel dat een tekening bevat. Het probleem met tekeningen tonen zit erin dat ze alleen maar van schijf geladen worden op het moment dat ze echt getekend worden. In dit voorbeeld tekenen we worden de button BOVEN de tekening getekend als alles geladen is. De eerste keer echter werden de buttons ACHTER de tekening getoond omdat de tekening nog eerst van schijf moest gehaald worden. Dit komt omdat het programma niet blijft wachten totdat de tekening geladen is: dit gebeurt in een achtergrond proces. Dit probleem werd opgevangen met een zogenaamde MediaTracker. Met het bevel tracker.waitForAll stopt het programma totdat het achtergrond proces de tekening(en) echt van schijf gehaald heeft. Dan pas wordt er verder gegaan en echt getekend. tracker = new MediaTracker(this);
Visuele Gebruikers Omgevingen: Swing 106
105/395
this.img=img; tracker.addImage(img,0); try { tracker.waitForAll();// wachten geblazen } catch (InterruptedException e) {} }
JList is flexible ontworpen Een eigen ListCellRenderer voor een JList
Hoe dat een lijst reageert op het selecteren van een element kan eenvoudig worden aangepast door een object van een klasse die de interface ListCellRenderer implementeert, in te pluggen. Het is een heel eenvoudige interface met maar één methode ‘getListCellRendererComponent’. Dit is een methode die een component (zoals een JLabel, JTextField, een JPanel,..) teruggeeft. Deze methode wordt door de JList opgeroepen voor elk element van de lijst. Voor elk van deze elementen wordt meegegeven als parameter : - de referentie naar de lijst zelf (list) - het element dat moet getekend worden (value) - de plaats die het element in de lijst inneemt (index) - of dit element al dan niet geselecteerd is (isSelected) - of de lijst de focus heeft (cellHasFocus). In onze versie van ListCellRenderer zal elk geselecteerd element een zwarte achtergrond krijgen met witte letters. De andere elementen krijgen zwarte letters op een witte achtergrond. Niets houdt u tegen om deze kleur te laten afhangen van de plaats die het element in de lijst inneemt. import javax.swing.*; import javax.swing.border.*; import java.awt.*; import java.awt.event.*;
Visuele Gebruikers Omgevingen: Swing 107
106/395
class MyCellRenderer implements ListCellRenderer { public Component getListCellRendererComponent( JList list, Object value, // value to display int index, // cell index boolean isSelected, // is the cell selected boolean cellHasFocus) // the list and the cell have the focus { ((Component)value).setBackground(isSelected?Color.black:Color.white); ((Component)value).setForeground(isSelected?Color.white:Color.darkGray); return (Component)value; } }
class EigenBorder implements Border {public Insets getBorderInsets(Component c) {return new Insets(4,4,4,4);} public boolean isBorderOpaque() {return false;} public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) { Color oldColor = g.getColor(); g.setColor(Color.BLUE); g.drawRect(x+0, y+0, width-0-0-1, height-0-0-1); g.setColor(Color.RED); g.drawRect(x+1, y+1, width-1-1-1, height-1-1-1); g.drawRect(x+2, y+2, width-2-2-1, height-2-2-1); g.drawRect(x+3, y+3, width-3-3-1, height-3-3-1); g.drawRect(x+4, y+4, width-4-4-1, height-4-4-1);
Visuele Gebruikers Omgevingen: Swing 108
107/395
g.drawRect(x+5, y+5, width-5-5-1, height-5-5-1); g.setColor(Color.BLUE); g.drawRect(x+6, y+6, width-6-6-1, height-6-6-1); g.setColor(Color.BLACK); g.setFont(new Font("Dialog",Font.ITALIC,8)); g.drawString("firma x",7,7); g.setColor(oldColor); } }
class Test1 extends JFrame { public Test1() { setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); Container c=getContentPane(); c.setLayout(new FlowLayout()); Object []twee=new Object[5]; twee[0]=new JButton("tof"); JButton b=new JButton("test"); b.setBorder(new EigenBorder()); twee[1]=b; twee[2]=new JButton("tweede button"); twee[3]=new JTextField("vierde lijn"); twee[4]=new JTextField("vijfde lijn"); JList l=new JList(twee); l.setCellRenderer(new MyCellRenderer()); c.add(l); } public static void main(String args[]) { System.out.println("Starting Test..."); Test1 mainFrame = new Test1(); mainFrame.setSize(400, 400); mainFrame.setTitle("Test"); mainFrame.setVisible(true); } }
Visuele Gebruikers Omgevingen: Swing 109
108/395
Een ListModel die de gegevens uit een file gaat lezen en terug naar wegschrijven bij beëindigen. Inhoud namen.txt:
Bij een JList zijn de gegevens al gescheiden van de visuele component door middel van een interface ListModel. We gaan overerven van de klasse DefaultListModel die deze interface implementeert en hierdoor een eigen FileListModel gaan maken die in zijn constructor heel de file gaat lezen en doormiddel van het overgeërfde bevel ‘addElement’ gaat toevoegen aan het ListModel. We voorzien ook een methode saveListModelToFile die we kunnen oproepen indien het programma sluit om zo heel de inhoud van de basis klasse DefaultListModel terug naar een file te schrijven. (Hiervoor maken we gebruik van de overgeërfde methode getSize en getElementAt(i) van de klasse DefaultListModel).
inhoud bestand namen2.txt
Visuele Gebruikers Omgevingen: Swing 110
109/395
import javax.swing.*; import java.awt.*; import java.awt.event.*; import java.io.*;
class FileListModel extends DefaultListModel {FileReader fr; BufferedReader br; String line; FileWriter fw; BufferedWriter bw; public FileListModel(String naamfile) {try{ fr=new FileReader(naamfile); br=new BufferedReader(fr); line=br.readLine(); while(line!=null) { super.addElement(line); line=br.readLine(); } }catch(Exception ee){} } public void saveListModelToFile(String naamfile) {try { fw=new FileWriter(naamfile); bw=new BufferedWriter(fw); int aantal=super.getSize(); for(int i=0;i
class Test2 extends JFrame { static FileListModel flm; public Test2() { addWindowListener( new WindowAdapter() {public void windowClosing(WindowEvent ww) {flm.saveListModelToFile("namen2.txt"); dispose(); } }); Container c=getContentPane(); Visuele Gebruikers Omgevingen: Swing 111
110/395
c.setLayout(new FlowLayout()); flm = new FileListModel("namen.txt"); JList l=new JList(flm); c.add(l); flm.addElement("extra tekst"); flm.addElement("extra tekst2"); } public static void main(String args[]) { System.out.println("Starting Test..."); Test2 mainFrame = new Test2(); mainFrame.setSize(400, 400); mainFrame.setTitle("Test"); mainFrame.setVisible(true); } }
Visuele Gebruikers Omgevingen: Swing 112
111/395
Een JList opgevuld vanuit een sql databank -
Maak eerst een database db1.mdb Zet hierin een tabel ‘personen’ met een ‘naam’ ‘voornaam’ ‘leeftijd’ Je moet deze databank als odbc databank registreren. Ofwel moet je hiervoor naar het control panel gaan naar ‘odbc gegevens’. Als je dit niet vindt onder control panel, moet je gewoon onder de help maar zoeken achter odbc en je krijgt een link ernaar:
Je moet hier onze databank registreren onder een logische odbc naam. We hebben dit gedaan met ‘test’. Je moet hiervoor op add klikken. Vervolgens ‘test’ intypen en de databank db1.mdb hiermee verbinden.
Hier zie je twee lijsten die opgevuld werden vanuit een databank. De sql query was ‘select * from personen’. In dat geval wordt de eerste kolom van het antwoord getooond. import javax.swing.*; import java.awt.*; Visuele Gebruikers Omgevingen: Swing 113
112/395
import java.awt.event.*; import java.io.*; import java.sql.*;
class SqlListModel extends DefaultListModel {String line; Connection conn; Statement statement; ResultSet rs; public SqlListModel(String driver,String databank,String sqlquery) {try{ Class.forName(driver).newInstance(); conn = DriverManager.getConnection(databank); statement = conn.createStatement(); rs = statement.executeQuery(sqlquery); while (rs.next()) { super.addElement( rs.getString(1) ); } if (statement != null) statement.close(); if (conn != null) conn.close(); } catch (Exception e) {addElement("fout"+e);} } } We erven gewoon over van DefaultListModel en reopen de functie super.addElement op voor elke rij van de ResultSet. Hierdoor wordt elke rij van de resultset toegevoegd aan de basisklasse DefaultListModel.
class JDatabaseList extends JFrame { static SqlListModel flm; public JDatabaseList() { setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); Container c=getContentPane(); c.setLayout(new FlowLayout()); flm = new SqlListModel("sun.jdbc.odbc.JdbcOdbcDriver", "jdbc:odbc:test", "select * from personen"); JList l=new JList(flm); JList cb=new JList(flm); c.add(l);c.add(new JScrollPane(cb)); flm.addElement("extra tekst"); flm.addElement("extra tekst2"); } public static void main(String args[]) { System.out.println("Starting Test..."); Visuele Gebruikers Omgevingen: Swing 114
113/395
JDatabaseList mainFrame = new JDatabaseList(); mainFrame.setSize(400, 400); mainFrame.setTitle("Test"); mainFrame.setVisible(true); }}
Ook comboboxen verbinden met een databank Omdat we ook een klasse DefaultComboBoxModel moeten aanpassen om in te pluggen in een JComboBox en deze aanpassing zeer analoog is aan de aanpassing gedaan voor DefaultListModel, zullen we een hulp klasse maken ‘SqlModel’ die we zullen gebruiken om de gemeenschappelijke code (namelijk het accessen van de databank) in te plaatsen.
import javax.swing.*; import java.awt.*; import java.awt.event.*; import java.io.*; import java.sql.*;
class SqlModel { String line; Connection conn; Statement statement; ResultSet rs; String fout=null; public SqlModel(String driver,String databank,String sqlquery) {try{ Class.forName(driver).newInstance(); conn = DriverManager.getConnection(databank); statement = conn.createStatement(); rs = statement.executeQuery(sqlquery); }
Visuele Gebruikers Omgevingen: Swing 115
114/395
catch (Exception e) {fout=e.toString();} } public String volgendeElement() {if(fout!=null) {String copy=new String(fout); fout=null;} try{if(rs.next()) return rs.getString(1) ; } catch(Exception e){return e.toString();} return null; } } SqlModel is een eigen klasse waarmee we de rijen van het resultaat van een sqlquery kunnen aflopen met behulp van de methode ‘volgendeElement’. We moeten hiervoor gewoon ‘volgendeElement’ oproepen in een lusje totdat deze methode null teruggeeft.
class SqlListModel extends DefaultListModel {SqlModel sqlmodel; public SqlListModel(String driver,String databank,String sqlquery) {sqlmodel=new SqlModel(driver,databank,sqlquery); String element=sqlmodel.volgendeElement(); while (element!=null) { super.addElement( element ); element=sqlmodel.volgendeElement(); } } }
class SqlComboBoxModel extends DefaultComboBoxModel {SqlModel sqlmodel; public SqlComboBoxModel(String driver,String databank,String sqlquery) {sqlmodel=new SqlModel(driver,databank,sqlquery); String element=sqlmodel.volgendeElement(); while (element!=null) { super.addElement( element ); element=sqlmodel.volgendeElement(); } } } De klassen SqlListModel en SqlComboBoxModel worden hierdoor veel eenvoudiger.
class JDatabaseList2 extends JFrame { static SqlListModel flm,flm2; static SqlComboBoxModel cbm; public JDatabaseList2() { setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); Container c=getContentPane(); c.setLayout(new FlowLayout()); flm = new SqlListModel("sun.jdbc.odbc.JdbcOdbcDriver", "jdbc:odbc:test", "select naam from personen"); JList l=new JList(flm); flm2 = new SqlListModel("sun.jdbc.odbc.JdbcOdbcDriver",
Visuele Gebruikers Omgevingen: Swing 116
115/395
"jdbc:odbc:test", "select leeftijd from personen"); JList l2=new JList(flm2); cbm = new SqlComboBoxModel("sun.jdbc.odbc.JdbcOdbcDriver", "jdbc:odbc:test", "select voornaam from personen"); JComboBox combo=new JComboBox(cbm); c.add(l); c.add(new JScrollPane(l2)); c.add(combo); flm.addElement("extra tekst"); flm.addElement("extra tekst2"); } public static void main(String args[]) { System.out.println("Starting Test..."); JDatabaseList2 mainFrame = new JDatabaseList2(); mainFrame.setSize(400, 400); mainFrame.setTitle("Test"); mainFrame.setVisible(true); }}
Visuele Gebruikers Omgevingen: Swing 117
116/395
Composite Een array van Componenten Je kan alle elementen van een container aflopen door de array van Componenten (alle visuele elementen zijn componenten) af te lopen. GetComponents geeft een array van Componenten terug. Je kan deze array aflopen en voor elk element de ‘Class’ vragen. Elk object heeft een Class object waarin beschreven staat hoe de klasse van het object eruit ziet. Voor een willekeurig object kan je met Class klasse= object.getClass(); het ‘Class’ object opvragen. Aan dit object kan je bijvoorbeeld de naam vragen: object.getClass().getName(); Maar je kan evengoed alle methodes of alle datavelden van het object opvragen. Voor elke component kunnen we met getParent ook de container opvragen waarin hij zit.
We merken dat but2 in een JPanel zit die op zijn beurt terug in een JPanel zit. Dit komt omdat de contentPane van de JFRame een JPanel is. We kunnen deze bevelen zelfs in een lus uitvoeren (totdat er geen parent meer is). Het blijkt dat de contentPane (JPanel) zelf in een JLayeredPane zit die op zijn beurt in een JRootPane zit. Waarom kan het handig zijn om alle componenten van een container af te lopen? Stel dat je alle componenten een andere kleur of grootte wilt geven (als reactie op een user event). Dan moet u gewoon al die componenten aflopen en van grootte of kleur veranderen. import javax.swing.*; import java.awt.*; import javax.swing.border.*; public class SwingComp extends JFrame {JButton but=new JButton("druk"); JLabel lab=new JLabel("tekst"); JPanel panel=new JPanel(); JButton but2=new JButton("druk2"); DefaultListModel datamodel=new DefaultListModel(); JList list=new JList(datamodel); public SwingComp() {Container c=getContentPane(); c.setLayout(new FlowLayout());
Visuele Gebruikers Omgevingen: Swing 118
117/395
c.add(but); c.add(lab); c.add(list); panel.add(but2); panel.setBorder(new LineBorder(Color.blue,2)); panel.setPreferredSize(new Dimension(120,100)); c.add(panel); setSize(200,180); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setVisible(true); datamodel.addElement("--alle componenten van contentPane--"); Component [] comps=c.getComponents(); for(int i=0;i
Visuele Gebruikers Omgevingen: Swing 119
118/395
Dialog JFrames vanuit JApplet
class mijnFrame extends JFrame { public mijnFrame(String titel) { super(titel); Container c=this.getContentPane(); c.setLayout(new FlowLayout()); c.add(new JSlider()); c.add(new JTextField(13)); this.setSize( 120,50); this.setVisible(true); } } public class Swing16 extends JApplet { public void init() { JFrame fr=new JFrame("ander scherm"); Container c=fr.getContentPane(); c.setLayout(new FlowLayout()); c.add(new JButton("druk op mij")); c.add(new JLabel("een label")); fr.setVisible(true); fr.setSize(200,100); new mijnFrame("nog een ander venster"); Container cc=this.getContentPane(); cc.add(new JLabel("we hebben nu drie vensters")); } }
Een JFrame maken vanuit een andere JFrame In het volgend voorbeeld kunnen we vanuit het basis venster, andere vensters aanmaken. De nieuwe vensters komen steeds op een nieuwe positie op het scherm. Door middel van de knop ‘show, hide last window’ kunnen we het laatst gemaakte venster verbergen en terug te voorschijn toveren.
Visuele Gebruikers Omgevingen: Swing 120
119/395
import javax.swing.*; import java.awt.*; import java.awt.event.*; public class Swing_frame01 extends JFrame {private JButton but=new JButton("blabla"); private JButton but2=new JButton("blabla"); private JFrame frame; public Swing_frame01(){ Container c=getContentPane(); c.add(but);c.add(but2);c.setLayout(new FlowLayout()); but.setAction(new Aktie("nieuw venster")); but2.setAction(new Aktie("show, hide last windows")); setLocation(100,100); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); pack(); setVisible(true); } public static void main(String args[]) {new Swing_frame01(); } private class Aktie extends AbstractAction /* INNER KLASSE*/{ private int i=0; public Aktie(String s){super(s);} public void actionPerformed(ActionEvent e){ if(e.getSource()==but){ frame=new JFrame("extra"+i); frame.setBounds(150+i*10,150+i*10,80,50); frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); Container c2=frame.getContentPane(); c2.add(new JLabel("extra venster: "+i++),BorderLayout.CENTER); frame.setVisible(true); } if(e.getSource()==but2) { if(frame!=null) frame.setVisible( ! frame.isVisible() ); } } } }
In de globale veranderlijke ‘frame’ houden we de referentie van het laatst gemaakt frame bij. Hierdoor kunnen we dit frame met behulp van setVisible(true/false) verbergen en terug tonen.
Visuele Gebruikers Omgevingen: Swing 121
120/395
Communicatie tussen vensters Informatie doorgeven van ‘kind’ venster naar ‘ouder’ Het hoofdscherm gaat drie andere JFrames maken (ze erven over van JFrame). Elk van deze ‘kind’ vensters krijgt bij zijn constructie een referentie van zijn vader mee (this). Het kindvenster zal deze referentie in een veranderlijke ‘parent’ steken. Hierdoor kan het kind venster steeds communiceren met zijn ‘vader’ door methode van zijn vader op te roepen (parent.voegtoeAanLijst(…) ).
import javax.swing.*; import java.awt.*; import java.awt.event.*; public class Swing_frame02 extends JFrame {private DefaultListModel listmodel=new DefaultListModel(); private JList list=new JList(listmodel); public Swing_frame02(){ Container c=getContentPane(); c.add(new JLabel("toegevoegde elementen:")); c.add(list);c.setLayout(new FlowLayout()); setLocation(100,100); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); new Swing_frame03(this); new Swing_frame03(this); new Swing_frame03(this); pack(); setVisible(true); }
public void voegtoeAanLijst(String s) {listmodel.addElement(s); } public static void main(String args[]) {new Swing_frame02(); } } class Swing_frame03 extends JFrame {private JButton but=new JButton("blabla"); private JTextField text=new JTextField(20); private Swing_frame02 parent; public Swing_frame03(Swing_frame02 parent) {super("ingavescherm"); this.parent=parent; Container c=getContentPane(); c.setLayout(new FlowLayout());
Visuele Gebruikers Omgevingen: Swing 122
121/395
this.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); c.add(but);c.add(new JLabel("tekst:")); c.add(text); but.setAction(new Aktie("voegtoe aan lijst")); this.setVisible(true); this.pack(); } private class Aktie extends AbstractAction{ private int i=0; public Aktie(String s){super(s);} public void actionPerformed(ActionEvent e){ if(e.getSource()==but){ String t=text.getText(); parent.voegtoeAanLijst(t); } } } }
De publieke routine voegtoeAanLijst maakt het mogelijk om van buiten af elementen aan de lijst toe te voegen. (De lijst ‘list’ wordt best private gehouden.)
Communicatie tussen twee objecten van dezelfde klasse We tonen twee vensters naast elkaar. Als we tekst invullen op het eerste venster en op de button klikken, dan verschijnt deze tekst in de list van het tweede venster. En omgekeerd: tekst ingetypt in het tweede venster wordt naar het eerste venster gestuurd.
Omdat we altijd één van de twee vensters moeten creëren voordat de andere wordt gecreëerd, kunnen we elkaars referentie niet meer langs de constructor doorgeven. Daarom voegen we een ‘setBroer’ functie toe aan de klasse. Hiermee kunnen we na de creatie van beide objecten (van dezelfde klasse) de referenties doorgeven aan hun broer object.
import javax.swing.*; import javax.swing.border.*; import java.awt.*; import java.awt.event.*;
Visuele Gebruikers Omgevingen: Swing 123
122/395
public class Swing_frame04 extends JFrame {private DefaultListModel listmodel=new DefaultListModel(); private JList list=new JList(listmodel); private JButton but=new JButton("blabla"); private JTextField text=new JTextField(10); private Swing_frame04 broer; public void setBroer(Swing_frame04 broer) { this.broer=broer;} public Swing_frame04(){ Container c=getContentPane(); JPanel p1=new JPanel(),p2=new JPanel(); p1.add(new JLabel("boodschappen van broer:")); p1.add(list); p2.add(new JLabel("tekst voor broer:")); p2.add(text); p2.add(but); c.add(p1);c.add(p2); p1.setBorder(new BevelBorder(BevelBorder.RAISED)); p2.setBorder(new BevelBorder(BevelBorder.RAISED)); c.setLayout(new FlowLayout()); setLocation(100,100); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); but.setAction(new AbstractAction("zend naar broer") { public void actionPerformed(ActionEvent e) {if(e.getSource()==but) { String t=text.getText(); broer.voegtoeAanLijst(t); } } } ); pack(); setVisible(true); } public void voegtoeAanLijst(String s) {listmodel.addElement(s); } public static void main(String args[]) {Swing_frame04 fr1=new Swing_frame04(); Swing_frame04 fr2=new Swing_frame04(); fr1.setBroer(fr2); fr2.setBroer(fr1); } }
In dit voorbeeld werken we ook met een naamloze innerklasse die overerft van AbstractAction. Omdat we toch maar éénmaal een object van deze klasse moeten maken, is het niet nodig deze klasse apart te definiëren en hier een naam voor te kiezen.
Gemeenschappelijke code in basisklasse zetten We willen vorige oefening wat uitbreiden. Stel dat we twee broervensters willen hebben met als gemeenschappelijke eigenschap dat ze aan de lijst van hun broer iets willen toevoegen. Ze hebben naast gemeenschappelijke eigenschappen (zullen in de basisklasse komen) ook individuele eigenschappen: een eerste subklasse zal als extra mogelijkheid het leegmaken van de lijst van zijn broer hebben. Een tweede subklasse zal als extra mogelijkheid het opzoeken van tekst in de lijst van broer aanbieden.
Visuele Gebruikers Omgevingen: Swing 124
123/395
De twee subklassen zullen daarom alleen de individuele verschillen programmeren zoals de extra componenten en de reactie erop. Het voordeel hiervan is dat bij noodzakelijke wijzigingen aan gemeenschappelijke delen we alleen de basisklasse moeten aanpassen. Om de lijst (listmodel) in de basisklasse gedefinieerd is en deze veranderlijke private is ( liefst ), worden we gedwongen om extra functies toe te voegen in de basisklasse: komtvoor, maakleeg, getBroer. Weersta aan de verleiding om de veranderlijke ‘listmodel’ public te maken. Als je dit zou doen, kom je in de verleiding om in de afgeleide klassen dadelijk met deze veranderlijke te werken. Dit heeft als nadeel dat alle klassen tezamen één groot geheel vormen. Als je dan later de veranderlijke ‘listmodel’ van naam verandert in de basisklasse, ben je verplicht om deze naamverandering door te voeren in alle afgeleide klassen.
import javax.swing.*; import javax.swing.border.*; import java.awt.*; import java.awt.event.*; public class Swing_frame05 extends JFrame {private DefaultListModel listmodel=new DefaultListModel(); private JList list=new JList(listmodel); private JButton but=new JButton("blabla"); private JTextField text=new JTextField(10); private Swing_frame05 broer; public void setBroer(Swing_frame05 broer){ this.broer=broer;} public Swing_frame05 getBroer() {return broer;} public void maakleeg() {listmodel.clear();} public boolean komtvoor(String s) {return listmodel.contains(s);} public void voegtoeAanLijst(String s) {listmodel.addElement(s);} public Swing_frame05(){ Container c=getContentPane(); JPanel p1=new JPanel(),p2=new JPanel(); p1.add(new JLabel("boodschappen van broer:")); p1.add(list); p2.add(new JLabel("tekst voor broer:"));
Visuele Gebruikers Omgevingen: Swing 125
124/395
p2.add(text); p2.add(but); c.add(p1);c.add(p2); p1.setBorder(new BevelBorder(BevelBorder.RAISED)); p2.setBorder(new BevelBorder(BevelBorder.RAISED)); c.setLayout(new FlowLayout()); setLocation(100,100); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); but.setAction(new AbstractAction("zend naar broer") {public void actionPerformed(ActionEvent e) {if(e.getSource()==but) { String t=text.getText(); broer.voegtoeAanLijst(t); } } } ); pack(); setVisible(true); } public static void main(String args[]) {Swing_frame05 fr1=new Swing_frame06(); Swing_frame05 fr2=new Swing_frame07(); fr1.setBroer(fr2); fr2.setBroer(fr1); } } class Swing_frame06 extends Swing_frame05 {JButton laatweg=new JButton(); public Swing_frame06() {super(); Container c=getContentPane(); JPanel p=new JPanel(); p.setBorder(new LineBorder(Color.blue,3)); p.add(new JLabel("maak lijst van broer leeg")); p.add(laatweg); c.add(p); pack(); laatweg.setAction(new AbstractAction("clear broer") {public void actionPerformed(ActionEvent e) {getBroer().maakleeg(); } } ); } } class Swing_frame07 extends Swing_frame05 {JButton zoekop=new JButton(); JTextField text=new JTextField(10); public Swing_frame07() {super(); Container c=getContentPane(); JPanel p=new JPanel(); p.setBorder(new LineBorder(Color.blue,3)); p.add(new JLabel("zoek op in lijst van broer")); p.add(text); p.add(zoekop); c.add(p); pack(); zoekop.setAction(new AbstractAction("zoek bij broer") {public void actionPerformed(ActionEvent e) {String s=text.getText(); if( getBroer().komtvoor(s)) text.setText("gevonden bij broer"); else text.setText("niet gevonden"); } } ); } }
Eenvoudige eigen JWindow
class MijnVenster extends JWindow // normaal neemt men wel // een JFrame als hoofdvenster
Visuele Gebruikers Omgevingen: Swing 126
125/395
{ public MijnVenster() { Container c=this.getContentPane(); c.setLayout(new FlowLayout()); c.add(new JSlider()); c.add(new JTextField(13)); this.setSize( 220,180); this.setVisible(true); } } public class Swing17 {static public void main(String args[]) { new MijnVenster(); } }
JWindow
Bovenstaand scherm is een JWindow dat 10 seconden zal verschijnen. Het vult heel het scherm. Een van de grote verschilpunten met JFrame is het ontbreken van sluit toetsen. Thread.sleep zal het programma 10 seconden laten wachten. We moeten hiervoor wel een exception opvangen. (de thread zou door een externe oorzaak ruw wakker geschud kunnen worden).
Na de 10 seconden, start de normale toepassing. Hier is dat een gewoon leeg JFrame.
import javax.swing.*; import java.awt.*; import java.awt.event.*;
Visuele Gebruikers Omgevingen: Swing 127
126/395
public class TestJW extends JFrame { public TestJW() { addWindowListener(new WinClosing()); setBounds(100, 100, 300, 300); splashScreen(); setVisible(true); } public void splashScreen() { JWindow jw = new JWindow(); Dimension screen = Toolkit.getDefaultToolkit().getScreenSize(); jw.setSize(screen.width, screen.height); JLabel pict = new JLabel( new ImageIcon("gardening.jpg"), JLabel.CENTER); JLabel lbl = new JLabel("Life on the Farm", JLabel.CENTER); lbl.setFont(new Font("Serif", Font.BOLD, 24)); lbl.setForeground(Color.black); jw.getContentPane().add(pict, BorderLayout.CENTER); jw.getContentPane().add(lbl, BorderLayout.SOUTH); jw.setVisible(true); try { Thread.sleep(10000); } catch (InterruptedException e) {} jw.setVisible(false); } public static void main(String args[]) { TestJW tjw = new TestJW(); } } class WinClosing extends WindowAdapter { public void windowClosing(WindowEvent we) {
System.exit(0); } }
De klasse Toolkit geeft meestal informatie over het platform waarop java draait. Met Toolkit.getDefaultToolkit() krijgen we de Toolkit van het huidige platform. Met getScreenSize krijgen we de grootte van het huidige scherm. Als we onze JWindow dan even groot maken als de dimensie van het scherm, dan zal de toepassing er over voor zorgen dat het volledige scherm wordt opgevuld.
Een venster met een eigen sluittoets In deze toepassing gaan we twee vensters onder elkaar tonen. Een JWindow heeft geen sluit toets, daarom moeten we zelf een button toevoegen om het venster te sluiten. Een venster sluiten kunnen we doen met de ‘dispose’ methode. (Als we zouden werken met System.exit(0) zou heel de toepassing stoppen.
import java.awt.*; import javax.swing.*; import java.awt.event.*; import javax.swing.border.*; public class Swing_frame08 extends JWindow {private Container c; private JButton but; public Swing_frame08() { c=getContentPane(); JPanel p=new JPanel(); p.setToolTipText("paneeltje"); p.setLayout(new GridLayout(0,1)); p.setBackground(Color.white);
Visuele Gebruikers Omgevingen: Swing 128
127/395
p.setOpaque(true); p.setBorder(new LineBorder(Color.red,2)); p.add(new JLabel(" dit venster heeft geen default sluittoetsen ")); but=new JButton("blabla"); but.setAction(new AbstractAction("sluit mij") {public void actionPerformed(ActionEvent e) {if (e.getSource()==but) { dispose();// venster vrij geven. }}} ); p.add(but); c.add(p); pack(); setVisible(true); } public static void main(String args[]) {new Swing_frame08().setLocation(10,10); new Swing_frame08().setLocation(10,110); } }
We maken twee objecten van dezelfde klasse en met behulp van setLocation zetten we ze op een andere plaats.
JDialog JDialog zijn een soort vensters die erg lijken op JFrame, wel zijn ze ondergeschikt aan een bijhorende JFrame of JDialog. Hierdoor zullen bij het verkleinen of sluiten van de frame waarbij ze horen, de JDialog’s ook mee verkleind of gesloten worden. JDialog’s blijven steeds voor de frame (of andere dialog) vanwaar ze opgestart werden. Er zijn twee types van JDialogs: modaal of niet. Modale JDialogs zijn dwingend: slechts als ze gesloten zijn, kan je met andere schermen werken: ze blokkeren alles. Voor een modale JDialog moet je als laatste parameter van de constructor ‘true’ meegeven. In bijhorende schermafdruk, werden de twee modale JDialogs ‘twee’ en ‘vier’ reeds gesloten.
import java.awt.*; import javax.swing.*; public class SwingJDialog extends JFrame {JDialog diag1=new JDialog(this,"eerste",false); JDialog diag2=new JDialog(this,"tweede",true); JDialog diag3=new JDialog(this,"derde",false); JDialog diag4=new JDialog(this,"vierde",true); public SwingJDialog() {setSize(400, 400); setVisible(true); diag1.setVisible(true); diag2.setVisible(true); diag3.setVisible(true); diag4.setVisible(true); Container c=getContentPane(); c.setLayout(new FlowLayout());
Visuele Gebruikers Omgevingen: Swing 129
128/395
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } public static void main(String args[]) {new SwingJDialog(); } }
Overerven v an JDialog
J e kan eenvoudig eigen dialoog vensters maken die opgestart worden vanuit een JFrame of een andere JDialog. Het zijn dan dialoog vensters die horen bij hun ‘vader’, samen met hen verkleind of gesloten worden en steeds ‘in front of’ blijven staan. Je moet ervoor gewoon overerven van JDialog en zelf componenten toevoegen aan de contentPane van de JDialog. import java.awt.*; import javax.swing.*; import java.awt.event.*; class MijnJDialog extends JDialog {JButton but=new JButton("bla bla"); public MijnJDialog(Frame fr,String str, boolean b) {super(fr,str,b); Container c=getContentPane(); c.setLayout(new FlowLayout()); c.add(but); but.setAction(new AbstractAction("sluit mij") {public void actionPerformed(ActionEvent e) {if (e.getSource()==but) { dispose();// venster vrij geven. }}} ); setSize(100,50); } } public class SwingJDialog2 extends JFrame {JDialog diag1=new MijnJDialog(this,"eerste",false); JDialog diag2=new MijnJDialog(this,"tweede",true); JDialog diag3=new MijnJDialog(this,"derde",false); JDialog diag4=new MijnJDialog(this,"vierde",true); public SwingJDialog2() {setSize(400, 400); setVisible(true); diag1.setVisible(true); diag2.setVisible(true); diag3.setVisible(true); diag4.setVisible(true); Container c=getContentPane(); c.setLayout(new FlowLayout()); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } public static void main(String args[])
Visuele Gebruikers Omgevingen: Swing 130
129/395
{new SwingJDialog2(); } }
awt en Swing door elkaar
class MijnFrame extends JFrame // een JFrame als hoofdvenster { public MijnFrame() { super("frame"); Container c=this.getContentPane(); c.setLayout(new FlowLayout()); c.add(new Button("windows button")); c.add(new JButton("lightweightButton")); c.add(new Scrollbar()); c.add(new JScrollBar()); List l=new List(); l.add("1");l.add("2");l.add("3"); c.add( l ); c.add(new JList(new String[]{"1","2","3"})); c.add(new Checkbox("check")); c.add(new JCheckBox("check")); this.setSize( 220,180); this.setVisible(true); } } public class Swing18 {static public void main(String args[]) { JFrame fr=new MijnFrame(); JOptionPane.showMessageDialog(null, "U ziet deze dadelijk", "na Frame", JOptionPane.INFORMATION_MESSAGE); JDialog d=new JDialog( fr ); d.getContentPane().add(new JLabel("dit is een dialog")); d.setVisible(true);d.setSize(150,120);d.pack(); JOptionPane.showMessageDialog(null,
Visuele Gebruikers Omgevingen: Swing 131
130/395
"gedaan", "gedaan", JOptionPane.INFORMATION_MESSAGE);
}}
JColorChooser als een aparte dialoog Met JColorChooser.showDialog kan je eenvoudig een dialoog venster laten verschijnen om de gebruiker een kleur te laten kiezen. Er zijn drie parameters: de ‘ouder’ frame/dialoog , titel van dialoog, default Color.
import java.awt.*; import javax.swing.*; import java.awt.event.*; import javax.swing.event.*; public class SwingJColorChooser2 extends JFrame {JLabel lab=new JLabel("gewoon tekst"); public SwingJColorChooser2() {setSize(400, 400); lab.setOpaque(true);// ondoorzichtig lab.setPreferredSize(new Dimension(200,50)); Container c=getContentPane(); c.setLayout(new FlowLayout()); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); c.add(lab); Color newColor = JColorChooser.showDialog( lab.setBackground(newColor); setVisible(true); } public static void main(String args[])
this,"Choose label Color", Color.red);
Visuele Gebruikers Omgevingen: Swing 132
131/395
{new SwingJColorChooser2(); } }
JColorChooser in uw eigen frame Je kan een JColorChooser object gewoon als component toevoegen aan uw contentPane. Vervolgens kan je de gebruiker bijvoorbeeld een extra JButton laten intypen en kan je met getColor de gekozen kleur verkrijgen. In het voorbeeld hieronder werd een ChangeListener verbonden met de JColorChooser. Hierdoor kan je een kleur kiezen en wordt er dadelijk code uitgevoerd bij elke selectie. Daardoor zal in de toepassing hieronder, onze label dadelijk de kleur krijgen die we in de JColorChooser paneel kiezen.
import java.awt.*; import javax.swing.*; import java.awt.event.*; import javax.swing.event.*; public class SwingJColorChooser extends JFrame {JLabel lab=new JLabel("gewoon tekst"); JColorChooser tcc = new JColorChooser(); public SwingJColorChooser() {setSize(400, 400); lab.setOpaque(true);// ondoorzichtig lab.setPreferredSize(new Dimension(200,50)); Container c=getContentPane(); c.setLayout(new FlowLayout()); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); tcc.getSelectionModel().addChangeListener( new ChangeListener() { public void stateChanged(ChangeEvent e) { Color newColor = tcc.getColor(); lab.setBackground(newColor); }} ); c.add(lab); c.add(tcc); setVisible(true); }
Visuele Gebruikers Omgevingen: Swing 133
132/395
public static void main(String args[]) {new SwingJColorChooser(); } }
### ##############################################################
JFileChooser
We hebben drie mogelijkheden om een object van de klasse JFileChooser te tonen: • • •
fc.showOpenDialog(this); fc.showSaveDialog(this); fc.showDialog(this,”titel”);
Standaard zijn er twee dialogen voorzien voor ‘openen’ en ‘saven’ van files. Je kan ook een eigen titel aan uw dialoog geven met de derde mogelijkheid. Merk op dat je (zoals bij alle dialogen) een ‘ouder’ venster voor de dialoog moet opgeven.
Na de oproep, krijg je een return code waarop je moet testen om na te gaan of de gebruiker wel ‘ok’ gekozen heeft.
import java.io.*; import java.awt.*; import javax.swing.*; import java.awt.event.*; import javax.swing.event.*; public class SwingJFileChooser extends JFrame {JLabel lab=new JLabel("gewoon tekst"); public SwingJFileChooser()
Visuele Gebruikers Omgevingen: Swing 134
133/395
{setSize(400, 400); lab.setOpaque(true);// ondoorzichtig Container c=getContentPane(); c.setLayout(new FlowLayout()); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); c.add(lab); JFileChooser fc = new JFileChooser(); int returnVal = fc.showOpenDialog(this); if (returnVal == JFileChooser.APPROVE_OPTION) { File file = fc.getSelectedFile(); String naam=file.getPath()+file.getName(); lab.setText(naam); } setVisible(true); } public static void main(String args[]) {new SwingJFileChooser(); } }
Visuele Gebruikers Omgevingen: Swing 135
134/395
Eigen dialoogboxen maken Overerven van JDialog is dé manier om eigen dialoogboxen te maken. Wat is het onderscheid tussen eigen dialoogboxen en JFrames? JFrames zijn volwaardige vensters. JDialog vensters zijn subdialoog vensters die horen bij een JFrame of bij een andere JDialog. Daarom zal de constructor van een JDialog steeds vragen om de referentie van een ouderobject. Dit ouder-object is het object van waaruit de JDialog werd gemaakt. Elk kind-jdialog zal steeds boven het bijhorende ouder-object getoond worden. Als het ouder-object gesloten wordt, worden alle bijhorende kind-jdialogs ook automatisch gesloten. Als het ouder-object verkleind wordt tot een icoontje, worden de bijhorende kind-jdialogs ook mee verkleind tot een icoontje. Als een ouder-object terug vergroot wordt, worden de bijhorende kind-jdialogs ook terug meer vergroot (uitgaande van een icoontje).
Dit ouder-object is een JFrame die een knop heeft om kind-jdialogs objecten mee aan te maken. Stel dat we drie maal op deze knop drukken. Er verschijnen dan drie extra JDialogs:
In deze drie extra JDialogs kan je een naam, voornaam, leeftijd intypen. Bij het intypen van een fout voor leeftijd kleurt het veld anders.
Visuele Gebruikers Omgevingen: Swing 136
135/395
Bij het sluiten van een kind-jdialog, zal vlug eerst de ingevulde informatie worden overgezonden naar het ouder-object. Daar wordt de ingevulde informatie in JTextArea’s velden achteraan toegevoegd.
De klasse PersoonDialoog heeft een extra veranderlijke papa. Hierin komt de referentie naar het ouder-object. Deze referentie kan altijd opgevraagd worden met behulp van de routine ‘getOwner’. Eigendialoog papa=getOwner(); Het volgende zou een fout geven: getOwner().setNaam(“qsdf”);
Visuele Gebruikers Omgevingen: Swing 137
136/395
Dit komt omdat de routine getOwner bij de klasse Window hoort (JDialog erft over van Window). De routine setNaam is alleen geldig voor objecten van de klasse Eigendialoog. papa.setNaam(“qsdf”); is wel goed. Je kan ook expliciet de referentie ‘downcasten’ naar het juiste type: ((Eigendialoog)getOwner()).setNaam(“qsdf”). Hoe krijgt de routie getOwner de juiste waarde? Bij de constructor van JDialog wordt de referentie van het ouder-object doorgegeven. Deze zal door de constructor van de klasse JDialog worden doorgegeven naar de constructor van zijn basisklasse Window. Deze klasse Window zal deze referentie stockeren in een globale veranderlijke van zijn klasse. De routine getOwner van de klass Window zal deze referentie teruggeven. import java.awt.*; import java.awt.event.*; import javax.swing.*; import javax.swing.border.*;
class PersoonDialoog extends JDialog { Eigendialoog papa; String naam; String voornaam; int leeftijd; Container c; JIntegerTextField jleeftijd=new JIntegerTextField(); JTextField jnaam=new JTextField(); JTextField jvoornaam=new JTextField(); public PersoonDialoog(Frame frame) {super(frame,"persoonsgegevens"); papa = ((Eigendialoog)getOwner()); c=getContentPane(); c.setLayout(new GridLayout(4,2)); c.add(new JLabel("geef leeftijd")); c.add(jleeftijd); jnaam.setPreferredSize( new Dimension(150,30)); jvoornaam.setPreferredSize(new Dimension(150,30)); c.add(new JLabel("geef naam"));c.add(jnaam); c.add(new JLabel("geef voornaam"));c.add(jvoornaam); JButton but=new JButton("sluit"); but.addActionListener( new ActionListener() {public void actionPerformed(ActionEvent ae) { papa.setNaam(jnaam.getText()); papa.setVoornaam(jvoornaam.getText()); papa.setLeeftijd(jleeftijd.getText()); PersoonDialoog.this.dispose(); } } );
Visuele Gebruikers Omgevingen: Swing 138
137/395
c.add(but); setSize(300,250); setVisible(true); } } PersoonDialoog.this.dispose(); Met behulp van dispose() wordt de dialog gesloten en wordt het object uit het geheugen gehaald. ‘this.dispose()’ zou een fout geven omdat dit bevel in een naamloos actionlistener-object wordt gegeven. ‘this’ zou in dit geval wijzen naar dit naamloos actionlistener-object. Willen we de this van de omsluitende PersoonDialoog klasse, dan moeten we dit expliciet opgeven.
public class Eigendialoog extends JFrame { Container c; JTextArea naam=new JTextArea(50,4), voornaam=new JTextArea(50,4), leeftijd=new JTextArea(50,4); public void setNaam(String naam){this.naam.append(naam+'\n');} public void setVoornaam(String voornaam) {this.voornaam.append(voornaam+'\n');} public void setLeeftijd(String leeftijd){this.leeftijd.append(leeftijd+'\n');} public Eigendialoog() { setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); c=getContentPane(); JButton but=new JButton("maak nieuw dialoog venster"); c.setLayout(new GridLayout(4,2)); c.add(new JLabel("namen"));c.add(naam); c.add(new JLabel("voornamen"));c.add(voornaam); c.add(new JLabel("leeftijden"));c.add(leeftijd); naam.setBorder(new LineBorder(Color.red)); voornaam.setBorder(new LineBorder(Color.red)); leeftijd.setBorder(new LineBorder(Color.red)); but.addActionListener( new ActionListener() {public void actionPerformed(ActionEvent ae) {new PersoonDialoog(Eigendialoog.this); }} ); c.add(but); } public static void main(String args[]) { System.out.println("Starting Eigendialoog..."); Eigendialoog mainFrame = new Eigendialoog(); mainFrame.setSize(400, 400); mainFrame.setTitle("Eigendialoog"); mainFrame.setVisible(true); } }
Visuele Gebruikers Omgevingen: Swing 139
138/395
new PersoonDialoog(Eigendialoog.this); Elke keer dat er op de button wordt geklikt moet er een nieuw PersoonDialoog object worden aangemaakt. Hierbij moet de referentie van het ouder-object worden doorgegeven aan de constuctor. Weeral is dit niet gewoon ‘this’, maar ‘Eigendialoog.this’. Eigendialoog is de omsluitende klasse van het naamloos actionlistenerobject. (this zou hier wijzen naar dit naamloos innerklasse.)
class JIntegerTextField extends JTextField {public JIntegerTextField() {super(); addKeyListener(new KeyAdapter() {public void keyReleased(KeyEvent e) {setBackground(Color.white); String tekst=getText(); try{int i=Integer.parseInt(tekst); } catch(Exception ee) {if(!tekst.equals("")){setBackground(Color.orange);}}}}); } public int getGetal() {int i;String tekst=getText(); try{i=Integer.parseInt(tekst);} catch(Exception ee){i=0;} return i; } public Dimension getPreferredSize() {return new Dimension(70,30);} }
Visuele Gebruikers Omgevingen: Swing 140
139/395
Communicatie tussen twee klassen Eens de communicatie tussen twee klassen gedefinieerd, zal deze communicatie gelden voor alle objecten van deze twee klassen.
In bovenstaand schermafdruk zijn twee ‘Bewegenddeel’-objecten aanwezig. Je kan ze met je muis verslepen. Als je ergens rechts klikt op het scherm, komen er twee objecten bij van de klassen Kind en Ouder. Het Ouder object is iets groter dan het Kind object. Omdat ze terzelfdertijd worden gekreëerd, krijgen ze hetzelfde nummer. Ze horen immers bij elkaar. Ze zullen steeds met elkaar kunnen communiceren. Dit geldt voor alle paren van objecten van de klassen Ouder en Kind. Ze erven over van de klasse Bewegenddeel en kunnen dus allemaal versleept worden.
In bovenstaande schermafdruk zijn drie Kind-Ouder paren bijgekreëerd. Als je echter op een Ouder object klikt, zal dit object geel gekleurd worden. Door middel van communicatie met zijn bijhorend Kind object, zal dit ook geel gekleurd worden. Omgekeerd: klikken op een Kind object zal zowel het Kind object als het bijhorend Ouder object groen doen kleuren. Hierdoor zijn bijelkaar horende objecten eenvoudiger terug te vinden.
Visuele Gebruikers Omgevingen: Swing 141
140/395
import javax.swing.border.*; import javax.swing.*; import java.awt.*; import java.awt.event.*; import java.util.*;
class Bewegenddeel extends JPanel { int x,y,px,py; int posx,posy; int nummer; public Bewegenddeel(int x,int y,int nummer) {this.x=x;this.y=y;this.nummer=nummer; setLocation(x,y); setSize(30,70); setBackground(Color.blue); setBorder(new EtchedBorder()); addMouseMotionListener (new MouseMotionAdapter() {public void mouseDragged(MouseEvent e) { Graphics g=getGraphics(); posx=e.getX(); posy=e.getY(); Bewegenddeel.this.moveBy(posx - px, posy -py); px = posx; py = posy;}}); addMouseListener (new MouseAdapter() {public void mousePressed(MouseEvent e) { px=e.getX(); py=e.getY(); }}); } public void moveBy(int x,int y) {Point p=getLocation(); setLocation((int)(p.getX()+x),(int)(p.getY()+y)); } public void paintComponent(Graphics g) {super.paintComponent(g); g.drawString(""+nummer,10,20); } public Color getKleur(){return Color.blue;}
Visuele Gebruikers Omgevingen: Swing 142
141/395
}
class Ouder extends Bewegenddeel {Kind kind; public Ouder(int x,int y,int nummer) {super(x,y,nummer); addMouseListener (new MouseAdapter() {public void mousePressed(MouseEvent e) {setBackground(Color.yellow); kind.setBackground(Color.yellow);}}); } public void setKind(Kind kind){this.kind=kind;} }
class Kind extends Bewegenddeel {Ouder ouder; public Kind(int x,int y,int nummer) {super(x,y,nummer); setSize(20,40); addMouseListener (new MouseAdapter() {public void mousePressed(MouseEvent e) {setBackground(Color.green); ouder.setBackground(Color.green);}}); } public void setOuder(Ouder ouder){this.ouder=ouder;} public int getBreedte(){return 20;} public int getHoogte(){return 50;} }
class TekenPaneel extends JPanel { int aantal=1; int posx,posy; int px, py; public TekenPaneel() {setLayout(null); add(new Bewegenddeel(10,10,0));add(new Bewegenddeel(60,100,0)); setDoubleBuffered(false); setOpaque(false); addMouseListener (new MouseAdapter() {public void mousePressed(MouseEvent e) { if(e.isMetaDown())//rechtsgeklikt {px=e.getX(); py=e.getY(); Ouder ouder=new Ouder(px,py,aantal); Kind kind=new Kind(px+35,py+10,aantal++); ouder.setKind(kind); kind.setOuder(ouder); add(ouder);add(kind); repaint(); } }}); }}
Visuele Gebruikers Omgevingen: Swing 143
142/395
public class Communicatietussentweeklassen extends JFrame { Container c; TekenPaneel tekenpaneel; public Communicatietussentweeklassen() { setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); c=getContentPane(); tekenpaneel=new TekenPaneel(); c.add(BorderLayout.CENTER,tekenpaneel); setSize(400,400); setVisible(true); } public static void main(String args[]) { Communicatietussentweeklassen mainFrame = new Communicatietussentweeklassen(); mainFrame.setSize(400, 400); mainFrame.setTitle("Communicatietussentweeklassen"); mainFrame.setVisible(true); }}
Ouder ouder=new Ouder(px,py,aantal); Kind kind=new Kind(px+35,py+10,aantal++); ouder.setKind(kind); kind.setOuder(ouder);
Visuele Gebruikers Omgevingen: Swing 144
143/395
Telkens een Ouder-Kind paar wordt aangemaakt, zal met behulp van de methoden setKind en setOuder de referenties van elkaar worden doorgegeven.
Hoe goed verslepen in Swing goedenavond meneer janssens, u vroeg mij de code voor een verplaatsbaar component door te sturen, de code werkt als volgt, boven een paneel waar de componenten die niet gewijzigd kunnen worden door de gebruiker (zoals bvb. labels, fotos, lijsten waar niets aan verandert moet worden, etc.) op staan plaatst u een tweede doorzichtig paneel. op dat doorzichtig paneel plaatst u de componenten waar u als gebruiker wel mee moet werken (invoerschermen en dergelijke). het doorzichtige paneel "onderschept" de muisbewegingen en muisbewerkingen en zo kan geregistreerd worden waar er geklikt is. ik neem aan dat de code enkel werkt met panelen waar de layout "null" van toepassing is. (het idee is weliswaar genomen van de "setGlassPanel"methode van java :P) ik heb geen idee hoe je gewoon op een paneel muisbewerkingen kunt controleren als er op een component van dat paneel geklikt wordt (dus er wordt geklikt op een knop, maar ik zou willen weten waar op het paneel dat exact geklikt is) dus het is niet mogelijk om belangrijke invoerschermen op die manier te verplaatsen. (bij labels wordt raar genoeg wel de klik of muisbeweging/bewerking als een muisbewerking op het paneel aanzien, daarom is het mogelijk om zonder dat doorzichtig paneel te werken voor enkel labels) ik heb een aantal vlugge aanpassingen in mijn oefening gemaakt zodat ALLE componenten die niet door de gebruiker gewijzigd worden (labels, foto, lijst) verplaatsbaar zijn. hier is de code die van belang is: jTabbedPane1.setBounds(0, 0, 430, 320); jTabbedPane1.addTab("Lingman", jPanel1); jTabbedPane1.addTab("Spelregels", desktop); jTabbedPane1.addTab("Cheats", jPanel3); jPanel1.setLayout(null);
//maken van een doorzichtige panel emptyPanel.setLayout(null); emptyPanel.setOpaque(false); emptyPanel.setVisible(true); emptyPanel.setBounds(0, 0,jTabbedPane1.getWidth(), jTabbedPane1.getHeight());
//we voegen het doorzichtige paneel toe aan het huidige paneel, //als eerste, zodat het bovenop de andere componenten //ligt jPanel1.add(emptyPanel); jPanel1.add(picture); jPanel1.add(raadwoord); jPanel1.add(lijst); jPanel1.add(letterLabel); jPanel1.add(woordLabel);
Visuele Gebruikers Omgevingen: Swing 145
144/395
//alle items waar veranderingen of invoer aan moeten gebracht worden //worden toegevoegd aan het doorzichte panel (als deze onder het //paneel zitten raken we er niet meer aan) emptyPanel.add(woord); emptyPanel.add(raden); emptyPanel.add(vasteLijst); emptyPanel.add(start); emptyPanel.add(letter);
//we voegen een MouseMotionListener toe aan het lege paneel //bij mouseDragged gaat, als er een component in zit, //het verplaatsbare component verschoven worden adhv het verschil //tov de muiscoördinaten (nieuwe locatie toekennen) emptyPanel.addMouseMotionListener(new MouseMotionListener() { public void mouseDragged(MouseEvent e) { if (dragComponent != null) {dragComponent.setLocation(e.getX()+picX, e.getY()+picY);} } public void mouseMoved(MouseEvent e) {} }); //we voegen bij het lege paneel een mouselistener toe, //deze registreert, als er op een component geklikt wordt //het verschil tussen de huidige coördinaten //en plaats het object in de "te verplaatsten component" //als de muis gerleased wordt wordt de component terug op "null" //geplaatsd. emptyPanel.addMouseListener( new MouseListener() { MouseEvent b; public void mouseClicked(MouseEvent e) {} public void mouseEntered(MouseEvent e) public void mouseExited(MouseEvent e)
{} {}
public void mousePressed(MouseEvent e) { //als je een nieuwe 'verplaatsbare' component wilt toevoegen //moet je gewoon hier setDraggable(componentnaam) zetten //(en hem toevoegen op het doorzichtige paneel) b=e; setDraggable(picture); setDraggable(letterLabel); setDraggable(lijst); setDraggable(raadwoord); setDraggable(woordLabel);
Visuele Gebruikers Omgevingen: Swing 146
145/395
} public void mouseReleased(MouseEvent e) { dragComponent=null; } public void setDraggable(Component com) { //als er geklikt is binnen de grenzen van de component //dan wordt het verschil met de muis tov de bovenrand van de //component berekend. if (b.getX()>=com.getX() && b.getX() < (com.getX()+com.getWidth()) && b.getY() >= com.getY() && b.getY() <= com.getY()+com.getHeight()) { picX = com.getX() - b.getX(); picY = com.getY() - b.getY(); dragComponent = com; } } }); (als de opmaak in de mail onduidelijk zou gemaakt worden staat dezelfde code ook nog 'ns in een apart txt-file) Als u het misschien iets te onduidelijk zou vinden, of nog iets wil vragen, stuur dan maar gerust een mailtje terug. Met Vriendelijke Groeten, David Van den Bergh
Visuele Gebruikers Omgevingen: Swing 147
146/395
Images Als je gewoon een eigen tekenvlak wilt hebben, kan je best overerven van Canvas (awt) of JPanel (Swing). Dit zijn de eenvoudigste lege Componenten. Je kan voor een JPanel een eigen paint voorzien. Weet wel dat een JPanel ook een Container is, maar probeer best niet naast het opgeven van een eigen paint ook de container te gebruiken door eigen componenten hieraan toe te voegen. Wil je toch een toepassing met een eigen tekening en ook componenten, zet dan de eigen componenten in een andere gewone JPanel.
class mijnCanvas extends Canvas { Image logo1; public mijnCanvas() {this.setSize(40,40); logo1= Toolkit.getDefaultToolkit().getImage("globe.gif"); } public void paint(Graphics g) {g.drawImage(logo1,0,0,this); g.drawLine(0,0,40,40); g.drawLine(0,40,40,0); } } class mijnpanel extends JPanel {public mijnpanel(){ setPreferredSize(new Dimension(100,50)); } public void paint(Graphics g) { g.setColor(Color.blue); g.fillPolygon(new int[]{10,20 ,30,40, 50,60, 70,80, 90,100,1}, new int[] {1,50,1,50,1,50,1,50,1,50,50},11); } } public class Swing20 extends JApplet { public void init() {Container c=getContentPane(); c.setLayout(new FlowLayout()); c.add(new mijnCanvas()); c.add(new mijnpanel()); c.add(new mijnCanvas()); JPanel p=new JPanel(); p.add(new JButton("druk")); p.add(new mijnpanel()); p.add(new mijnCanvas()); p.add(new JSlider()); c.add(p); } }
In een gewone toepassing ga je een Image laden met behulp van Toolkit klasse. Toolkit.getDefaultToolkit geeft de Toolkit klasse voor uw besturingssysteem terug.Een polygon is een veelhoek waarvan je gewoon de coordinaten van de hoekpunten moet opgeven.
Visuele Gebruikers Omgevingen: Swing 148
147/395
ImageIcon
import java.awt.*; import javax.swing.*; import java.net.*; public class SwingImageIcon extends JFrame {Container c; JLabel lab,lab2; ImageIcon icon=new ImageIcon("globe2.gif","eerste label"); ImageIcon icon2; public SwingImageIcon() {setBounds(10,10,250,180); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); c=getContentPane(); c.setLayout(new FlowLayout()); lab=new JLabel(icon); // algemene wijze : werkt ook met jar, zoekt classpath, jar's... URL iconURL = ClassLoader.getSystemResource("globe.gif"); if (iconURL != null) { icon2 = new ImageIcon(iconURL,"tweede label"); } lab2=new JLabel(icon2); c.add(lab);c.add(lab2); setVisible(true); } public static void main(String args[]) {new SwingImageIcon(); } }
ImageIcon is simpelder dan Image. Het is eigenlijk bedoeld voor kleine icoontjes. Het verschil met Image is dat uw toepassing steeds wacht totdat de ImageIcon volledig in het geheugen geladen is, voordat verder wordt gegaan. Je kan ook een tekst meegeven met de constructor van ImageIcon. Dit is voor accessibility (blinden): de tekst zal getoond worden in plaats van de tekening. ClassLoader.getSystemResource zal de tekening zoeken volgens classpath. (Als je bepaalde packages nodig hebt, worden deze gezocht volgens uw classpath. Zie later maken van packages. DIT KAN OOK EEN JAR FILE ZIJN, dit is een soort ZIP file, waarmee u uw toepassing over internet kunt versturen.)
Image
import java.awt.*; import javax.swing.*; import java.net.*;
Visuele Gebruikers Omgevingen: Swing 149
148/395
class Paneel extends JPanel {ImageIcon icon=new ImageIcon("globe2.gif","eerste label"); public Paneel(){setPreferredSize(new Dimension(100,100));} public void paint(Graphics g) {Image im=icon.getImage(); g.drawImage(im,0,0,100,100,this); } } class Paneel2 extends JPanel {Image im=Toolkit.getDefaultToolkit().getImage("globe2.gif"); public Paneel2(){setPreferredSize(new Dimension(100,100));} public void paint(Graphics g){ g.drawImage(im,0,0,100,100,this);} } public class SwingImageIcon2 extends JFrame {Container c; public SwingImageIcon2() {setBounds(10,10,250,180); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); c=getContentPane(); c.setLayout(new FlowLayout()); Paneel paneel=new Paneel(); Paneel2 paneel2=new Paneel2(); c.add(paneel);c.add(paneel2); setVisible(true); } public static void main(String args[]) {new SwingImageIcon2(); } }
Je kan eenvoudig een ImageIcon omzetten naar een Image door de methode getImage op te roepen voor de ImageIcon. Vervolgens kan je deze image gebruiken in drawImage.
Communicatie tussen Luisteraar en verwittigaar
class Luisteraar implements ActionListener
Visuele Gebruikers Omgevingen: Swing 150
149/395
{ private JLabel ll; public Luisteraar(JLabel par){ll=par;} public void actionPerformed(ActionEvent e) { ll.setText(e.getActionCommand() ); } } public class SwingB extends JApplet {public void init() {Container c=getContentPane(); c.setLayout(new FlowLayout()); JLabel l=new JLabel("vul in en druk enter"); c.add(l); JTextField t=new JTextField(10); c.add(t); t.addActionListener(new Luisteraar(l)); }}
Stel dat de luisteraar een label van de ‘verwittigaar’ moet wijzigen? Omdat de luisteraar een andere klasse is, kan hij niet dadelijk aan de label van de de verwittigaar (hier SwingB). We moeten hiervoor dan een referentie van de label doorgeven aan de constructor van de luisteraar. Deze referentie wordt dan gestockeerd in de veranderlijk ‘ll’. Hiermee kunnen we dan de tekst van de label veranderen. Merk op dat e.getActionCommand ook de tekst van het ingevulde JTextField bevat. (Je kan deze tekst natuurlijk ook nog altijd ophalen uit het tekstveldje.)
Je hebt geen problemen met communicatie tussen luisteraar en verwittigaar indien de verwittigaar zelf een luisteraar is. Hiervoor moet je SwingC de interface ActionListener zelf laten implementeren. De klasse SwingC zal hiervoor dan zelf de routine actionPerformed moeten opgeven. This zal dan moeten worden doorgegeven aan addActionListener. ZELFDE UITVOER als vorig programma: public class SwingC extends JApplet implements ActionListener { private JLabel l; public void init() {Container c=getContentPane(); c.setLayout(new FlowLayout()); l=new JLabel("vul in en druk enter"); c.add(l); JTextField t=new JTextField(10); c.add(t); t.addActionListener(this); } public void actionPerformed(ActionEvent e) { l.setText(e.getActionCommand() ); }} NOGMAALS ZELFDE UITVOER:
Ten slotte kunnen we ook werken met een innerklasse om het probleem van communicatie op te lossen. Een al dan niet naamloze innerklasse kan rechtstreeks aan de informatie van zijn omliggende klasse. Dit gebeurt in het volgende voorbeeld. public class SwingD extends JApplet { private JLabel l; public void init() {Container c=getContentPane(); c.setLayout(new FlowLayout()); l=new JLabel("vul in en druk enter"); c.add(l); JTextField t=new JTextField(10); c.add(t); t.addActionListener(new ActionListener() {public void actionPerformed(ActionEvent e) { l.setText(e.getActionCommand() );}
Visuele Gebruikers Omgevingen: Swing 151
150/395
}); } }
De naamloze innerklasse kan rechtstreeks de label ‘l’ benaderen.
Muis luisteraars Muis bewegingen worden opgevangen door MouseMotionListeners. Gewone muiskliks worden opgevangen door MouseListeners. Zie Grant Palmer om na te gaan welke methodes deze luisteraars moeten hebben. Grant Palmer bevat ook uitleg over de informatie die MouseEvent bevat. Zoek eens op. Nu.
public class SwingE extends JApplet implements MouseMotionListener { private JLabel l; public void init() {Container c=getContentPane(); c.setLayout(new FlowLayout()); l=new JLabel("vul in en druk enter"); c.add(l); JTextField t=new JTextField(10); c.add(t); this.addMouseMotionListener(this); } public void mouseDragged(MouseEvent e) { l.setText("beweegt en ingedrukt: "+ e.getX()+" "+e.getY() + (e.isAltDown()?" Alt ":"") + (e.isShiftDown()?" Shift ":"") + (e.isControlDown()?" Control ":"") + " when : "+ e.getWhen() ); } public void mouseMoved(MouseEvent e) { l.setText("beweegt: " +e.getX()+" "+e.getY() + (e.isAltDown()?" Alt ":"") + (e.isShiftDown()?" Shift ":"") + (e.isControlDown()?" Control ":"") + " when : "+ e.getWhen() ); }
Visuele Gebruikers Omgevingen: Swing 152
151/395
}
Als je met de muis over de applet beweegt, wordt steeds de positie van de muis getoond, samen met nog andere informatie: is shift, control, alt toets ingedrukt of niet. ‘mouseDragged’ zal worden opgeroepen als de muis is ingedrukt en beweegt. GENEREERT ONGEVEER DEZELFDE UITVOER (alleen bij ingedrukt muisbewegingen public class SwingF extends JApplet { private JLabel l; public void init() {Container c=getContentPane(); c.setLayout(new FlowLayout()); l=new JLabel("vul in en druk enter"); c.add(l); JTextField t=new JTextField(10); c.add(t); this.addMouseMotionListener(new MouseMotionListener() {public void mouseDragged(MouseEvent e) { l.setText("beweegt en ingedrukt: " + e.getX()+" "+e.getY() t + (e.isAltDown()?" Alt ":"") + (e.isShiftDown()?" Shift ":"") + (e.isControlDown()?" Control ":"") + " when : "+ e.getWhen()); } public void mouseMoved(MouseEvent e) { // ook al wil je niets doen bij deze event, toch moet // je deze lege routine geven om de listener te // implementeren. In dit geval kan je beter adapter // gebruiken } } ); }} Analoog aan vorige oplossing, maar nu met Adapter: public class SwingG extends JApplet { private JLabel l; public void init() {Container c=getContentPane(); c.setLayout(new FlowLayout()); l=new JLabel("vul in en druk enter"); c.add(l); JTextField t=new JTextField(10); c.add(t); this.addMouseMotionListener(new MouseMotionAdapter() {public void mouseDragged(MouseEvent e) { l.setText("beweegt en ingedrukt: " + e.getX()+" "+e.getY() + (e.isAltDown()?" Alt ":"") + (e.isShiftDown()?" Shift ":"") + (e.isControlDown()?" Control ":"") + " when : "+ e.getWhen()); } //overbodig:public void mouseMoved(MouseEvent e) // deze routine staat in de klasse MouseMotionAdapter // deze implementeer de Listener interface met lege // routines, zodat wij dat niet meer moeten doen. } ); }}
Een adapter klasse is een lege luisteraar: De interface wordt geïmplementeerd met lege versies van de gevraagde routines. Indien een interface (zoals WindowListener) meerdere routines verplicht te implementeren en je bent maar geïnteresseerd in één routine, dan kan je beter overerven van de bijhorende adapter. Je hoeft dan alleen maar
Visuele Gebruikers Omgevingen: Swing 153
152/395
uw versie van de routine waarin je geïnteresseerd bent, te implementeren. Voor de andere routines erf je de lege versies over.
Ee n eenvoudig teken programma De eenvoudigste wijze om een tekenprogramma te maken, is een JPanel nemen en hieraan een MouseMotionListener verbinden. Hiermee vang je de muisbewegingen op. Telkens je een muisbeweging dedecteert, kan je met getGraphics() het ‘tekenbord’ opvragen en hierop tekenen. Wil je bijvoorbeeld met de muis vloeiende lijnen tekenen, dan moet je steeds werken met een ‘vorige’ positie. Hiermee kan je dan steeds een lijn tekenen van de ‘vorige’ positie naar de huidige positie.
Er is wel een probleem met dit tekenprogramma: als je iets getekend hebt en je verandert de grootte van uw venster, dan wordt paint terug opgeroepen, die terug een lege rechthoek zal tekenen. Uw tekening is weg. Dit komt omdat de paint automatisch wordt opgeroepen.
We hebben ook een MouseAdapter nodig om de coordinaten van het begin punt van de bewegende lijn te registreren.
class MijnPanel extends JPanel {int x=0,y=0,oldx,oldy; Vector v=new Vector(); public MijnPanel() { setBackground(Color.yellow); addMouseListener(new MouseAdapter() {public void mousePressed(MouseEvent e) {x=e.getX();y=e.getY();} });
Visuele Gebruikers Omgevingen: Swing 154
153/395
addMouseMotionListener( new MouseMotionAdapter() { public void mouseDragged(MouseEvent e) {oldx=x;oldy=y;x=e.getX();y=e.getY(); Graphics g=getGraphics(); g.drawLine(oldx,oldy,x,y); } }); } public void paintComponent(Graphics g) { super.paintComponent(g); g.drawRect(10,10,250,200); }} public class Swing4 extends JApplet { JCheckBox cb= new JCheckBox("stip mij aan"); JRadioButton cb2=new JRadioButton("kies mij"); JPanel ca=new MijnPanel(); public void init() {Container c=getContentPane(); c.setLayout(new BorderLayout()); c.add(cb,BorderLayout.SOUTH); c.add(cb2,BorderLayout.NORTH); c.add(ca,BorderLayout.CENTER); setVisible(true); }}
Je merkt dat de JPanel waarop getekend wordt, slechts één van de componenten is die in de container van de applet zitten. Gebruik een aparte component om op te tekenen. Probeer niet aan de JPanel waarop je tekent ook nog eens componenten toe te voegen. ===================================
De volgorde van oproepen is vrij ingewikkeld:
JComponent.update->JComponent.paint->JComponent.paintComponent->ComponentUI.update ->ComponentUI.paint->JComponent.paintBorder->JComponent.paintChildren
update gaat eerst het veld leegmaken en vervolgens paint oproepen. (Wil je bijvoorbeeld dat u veld niet eerst leeg gemaakt wordt, dan moet je een eigen versie van update voorzien die gewoon paint oproept (zonder eerst het veld leeg te maken).
De tekening herste llen bij paint We gaan nu in een Vector de coordinaten van alle punten van de vloeiende lijnen bijhouden. Als je nu uw venster verkleint en terug vergroot, staat de tekening er terug. Toch zijn er enkele extra lijnen . Dit komt omdat we in de vector gewoon alle coordinaten achter elkaar steken zonder aan te geven welke punten begin of eindpunten zijn. Bij het hertekenen worden in onze versie alle punten verbonden met de vorige punten. Ook als je de muis even oplicht en verplaatst. Probeer dit zelf te verhelpen.
Visuele Gebruikers Omgevingen: Swing 155
154/395
Na vergroten en verkleinen staat er een extra lijn. Hoe komt dit? Wat kan je er aan doen? class MijnPanel extends JPanel {int x=0,y=0,oldx,oldy; Vector v=new Vector(); public MijnPanel() { setBackground(Color.yellow); addMouseListener(new MouseAdapter() {public void mousePressed(MouseEvent e) {x=e.getX();y=e.getY();v.addElement(new Point(x,y));} }); addMouseMotionListener( new MouseMotionAdapter() { public void mouseDragged(MouseEvent e) {oldx=x;oldy=y;x=e.getX();y=e.getY(); Graphics g=getGraphics(); g.drawLine(oldx,oldy,x,y); v.addElement(new Point(x,y)); } }); } public void paintComponent(Graphics g) { super.paintComponent(g); g.drawRect(10,10,250,200); int x,y,oldx,oldy; Enumeration e=v.elements(); if(e.hasMoreElements()) { Point p= (Point)e.nextElement(); oldx=(int)p.getX();oldy=(int)p.getY(); while(e.hasMoreElements()) { p= (Point)e.nextElement(); x=(int)p.getX();y=(int)p.getY(); g.drawLine(oldx,oldy,x,y); oldx=x;oldy=y; } } }} public class Swing4 extends JApplet
Visuele Gebruikers Omgevingen: Swing 156
155/395
{JCheckBox cb= new JCheckBox("stip mij aan"); JRadioButton cb2=new JRadioButton("kies mij"); JPanel ca=new MijnPanel(); public void init() {Container c=getContentPane(); c.setLayout(new BorderLayout()); c.add(cb,BorderLayout.SOUTH); c.add(cb2,BorderLayout.NORTH); c.add(ca,BorderLayout.CENTER); setVisible(true); }}
Om de vector af te lopen werk je met Enumeration e=v.elements(); ‘hasMoreElements’ en ‘nextElement’ kunnen gebruikt worden om alle elementen van ‘e’ af te lopen. Wel moet je het bekomen object ‘casten’ naar Point.
Componenten sam en met een tekenveld Omdat de drie radiobutton is een groupbox staan, wordt bij aanklikken de listener 2x opgeroepen: 1x om de oude keuze te 'on-selecten' en 1x om de nieuwe keuze te selecteren tekst verandert alternerend in 'selected' ' not selected'
Als je een tekenveld samen met andere componenten wilt hebben, moet je dit tekenveld gewoon als een component behandelen. Je mag zeker geen componenten op het tekenveld zelf zetten. We zullen in dit voorbeeld ook met luisteraars voor JRadioButton’s en JCheckButton’s werken. We hebben al vroeger gezien dat je met setAction kunt werken. Maar je kunt ook met ItemListeners werken. Deze zullen de routine itemStateChanged moeten definieren. De naam van deze luisteraar is anders dan bij JButton omdat zowel JRadioButton als JCheckButton twee toestanden hebben. Een JButton heeft maar één toestand. Het voordeel om te werken met het algemenere itemListener is dat er meer dan één luisteraar per component kunnen zijn in tegenstelling tot setAction. Elke keer dat we een radiobutton selecteren, verandert de tekst van de radiobutton door het nummer van selectie toe te voegen achter de vorige waarde. class MijnPanel extends JPanel {int x=0,y=0,oldx,oldy; public MijnPanel() { setBackground(Color.yellow); addMouseListener(new MouseAdapter() {public void mousePressed(MouseEvent e) {x=e.getX();y=e.getY();} }); addMouseMotionListener( new MouseMotionAdapter() { public void mouseDragged(MouseEvent e) {oldx=x;oldy=y;x=e.getX();y=e.getY(); getGraphics().drawLine(oldx,oldy,x,y); }
Visuele Gebruikers Omgevingen: Swing 157
156/395
}); } public void paintComponent(Graphics g) { super.paintComponent(g); g.drawRect(10,10,250,200); } } class Handler implements ItemListener {int i=0; public void itemStateChanged(ItemEvent e) { JRadioButton but = ((JRadioButton)e.getSource()); String str=but.getText(); // JOptionPane.showMessageDialog(null,str); but.setText(str+" "+(i++)); } } public class Swing4C extends JApplet { JCheckBox cb= new JCheckBox("stip mij aan"); JRadioButton cb2=new JRadioButton("kies mij"); JRadioButton cb3=new JRadioButton("kies mij2"); JRadioButton cb4=new JRadioButton("kies mij3"); JPanel panel=new JPanel(); ButtonGroup group =new ButtonGroup(); JPanel ca=new MijnPanel(); public void init() {Container c=getContentPane(); c.setLayout(new BorderLayout()); c.add(cb,BorderLayout.SOUTH); panel.add(cb2);panel.add(cb3);panel.add(cb4); group.add(cb2);group.add(cb3);group.add(cb4); Handler h=new Handler(); cb2.addItemListener(h);cb3.addItemListener(h); cb4.addItemListener(h); c.add(panel,BorderLayout.NORTH); c.add(ca,BorderLayout.CENTER); cb.addItemListener( new ItemListener() {public void itemStateChanged(ItemEvent e) {if(e.getStateChange()==ItemEvent.SELECTED) cb.setText("selected");else cb.setText("not selected"); } }); setVisible(true);
}}
We werken ook met een ButtonGroup. De drie radiobuttons worden toegevoegd aan deze buttongroup. Hierdoor worden bij selectie van één radiobutton gewoon de anderen ge-unselecteerd. Zonder dat je dit moet programmeren.
Bij de checkbox gaan we in zijn luisteraar nagaan wat de toestand is met getStateChange.
Visuele Gebruikers Omgevingen: Swing 158
157/395
Properties Hashtable import java.util.*; public class TestHashtable { public static void main(String args[]) { Hashtable ht = new Hashtable(); ht.put("Jackson Palmer", new Integer(1051)); ht.put("Lisa Reid", new Integer(5678)); ht.put("Cheryl Spada", new Integer(2345)); System.out.println("size of Hashtable is " + ht.size()); String str = "Jackson Palmer"; if (ht.containsKey(str)) { System.out.println(str + "'s account number is " + ht.get(str)); } } } size of Hashtable is 3
Jackson Palmer's account number is 1051 Een hashtable dient om keys te associëren met waarden. In dit voorbeeld wordt “Jackson Palmer” verbonden met het Object ‘new Integer(1051)’. Het kan verbonden worden met gelijk welk Object. Later kan het geassociëerde object teruggevonden worden met get. Indien er geen is, wordt null teruggegeven. U kan ook nagaan of de waarde wel voorkomt met containsKey. Er kan dus snel worden teruggezocht in een hashtable.
File met properties Uitvoer van het programma: size of Hashtable is 6 titel.slot.einde's account number is bye De waarden kunnen nu op schijf worden bijgehouden. Nu kunnen alleen maar strings worden geassocieerd met de sleutels.
inhoud in.properties: #test #Thu Feb 28 14:24:34 CET 2002 titel.slot.einde=bye titel.vraag=Hoe gaat het? titel.naam=Goede morgen import java.util.*; import java.io.*; public class TestProperties { public static void main(String args[]) { Properties ht = new Properties();
Visuele Gebruikers Omgevingen: Swing 159
158/395
try { FileInputStream input; input=new FileInputStream("in.properties"); ht.load(input); } catch(IOException ex){ ex.printStackTrace();} ht.setProperty("titel.naam", "Goede morgen"); ht.setProperty("titel.vraag", "Hoe gaat het?"); ht.setProperty("titel.slot.einde", "bye"); System.out.println("size of Hashtable is " + ht.size()); String str = "titel.slot.einde"; if (ht.containsKey(str)) { System.out.println(str + "'s account number is " + ht.getProperty(str)); try { FileOutputStream output; output=new FileOutputStream("in.properties"); ht.store(output,"test"); } catch(IOException ex){ ex.printStackTrace();} } }}
Property erft over van Hashtable. Toch werk je best met setProperty in plaats van met put. Dit omdat je eigenlijk alleen maar strings mag associeren met een sleutel. (Put zou alle Objecten toelaten als waarde en dit zou tijdens de uitvoering een fout geven.) Je kunt een Property eerst laden van schijf met de methode load en een FileInputStream. Op het einde kan je de waarden ook saven met store en een FileOutputStream.
ResourceBundle Er is een eenvoudigere wijze om property files te lezen: beschouw ze als ResourceBundle. Het onderscheid met Property is dat je ze alleen maar kunt lezen. Als je nu een naam van een file opgeeft, moet je properties als achtervoegsel weglaten.
Goede morgen Hoe gaat het? bye Exception in thread "main" java.util.MissingResourceException: Can't find resour ce for bundle java.util.PropertyResourceBundle, key bestaat niet at java.util.ResourceBundle.getObject(ResourceBundle.java:382) at java.util.ResourceBundle.getString(ResourceBundle.java:354) at TestResourceBoundle.main(TestResourceBoundle.java:12) import java.util.*; import java.io.*; public class TestResourceBoundle { public static void print(String s){System.out.println(s);} public static void main(String args[]) { ResourceBundle rb = ResourceBundle.getBundle("in"); print(rb.getString("titel.naam")); print(rb.getString("titel.vraag")); print(rb.getString("titel.slot.einde")); print(rb.getString("bestaat niet")); } }
Visuele Gebruikers Omgevingen: Swing 160
159/395
Eenvoudigere wijze om eigen events te maken. In vorig voorbeeld werd duidelijk dat er toch heel wat klassen moesten gemaakt worden voor een eigen event: GokEvent, GokListener, programmeren van een Vector. Daarom werd de event propertyChange voorzien. Hiermee kunt u alle luisteraars voorzien als een eigenschap veranderd. U mag de naam van deze eigenschap zelf kiezen. U mag bijvoorbeeld een eigenschap ‘stemming’ of ‘gemoedstoestand’ nemen.
PropertyChange event import java.beans.*; public class X {java.beans.PropertyChangeSupport pcs; private String gemoed,oldgemoed; public X(){pcs=new PropertyChangeSupport(this); gemoed=”goed gezind”; } public void addPropertyListener(PropertyChangeListener pcl) { pcs.addPropertyChangeListener(pcl); } public void removePropertyListener(PropertyChangeListener pcl) { pcs.removePropertyChangeListener(pcl); } public void setGemoed(String newgemoed) {oldgemoed=gemoed; gemoed=newgemoed; pcs.firePropertyChange(“gemoed”,oldgemoed,newgemoed); // doe iets met het nieuw gemoed naargelang de toepassing } }
Roept de routine propertyChange op van elke luisteraar.
class Luisteraar implements PropertyChangeListener { public void propertyChange(PropertyChangeEvent e) {// doe iets met e.getOldValue(), e.getNewValue(), e.getPropertyName() } }
We hebben een property ‘gemoed’. Als deze verandert, zullen alle luisteraars verwittigd worden doordat hun routine ‘propertyChange’ zal worden opgeroepen. We gaan nu zelf geen Vector gebruiken. Omdat ‘propertyChange’ events veel voorkomen, hebben ze een klasse PropertyChangeSupport voorzien die deze Vector voor ons bijhoudt. De routine firePropertyChange van die klasse zal heel de interne vector aflopen, een object van de klasse PropertyChangeEvent maken en dit object doorgeven aan de routine propertyChange van elke luisteraar.
Visuele Gebruikers Omgevingen: Swing 161
160/395
Toepassingen los koppelen met propertyChange Venster1: drie properties
Toepassing1 implements PropertyChangeListener PropertyChange(PropertyChangeEvent e) {e.getOldValue() e.getNewValue() e.getPropertyName(); }
addPropertyChangeListener removePropertyChangeListener
bij verandering van properties: firePropertyChange
Toepassing2 implements PropertyChangeListener PropertyChange(PropertyChangeEvent e) {e.getOldValue() e.getNewValue() e.getPropertyName(); }
U kunt bijvoorbeeld een apart venster in uw toepassing hebben om bepaalde eigenschappen in te geven. U hebt twee toepassingen die gebruik maken van deze eigenschappen. De twee toepassingen worden als PropertyChangeListener geregistreerd bij het eigenschappen-venster. Telkens deze eigenschappen zich wijzigen zullen de twee toepassingen hiervan verwittigd worden. Uw toepassing is hierdoor modulairder opgebouwd.
Visuele Gebruikers Omgevingen: Swing 162
161/395
VetoableChangeListener import java.beans.*; public class X {java.beans.PropertyChangeSupport pcs; java.beans.VetoableChangeSupport vcs; private String gemoed,oldgemoed; public X(){pcs=new PropertyChangeSupport(this); vcs=new VetoableChangeSupport(this); gemoed=”goed gezind”; } public void addVetoableChangeListener(VetoableChangeListener pcl) { vcs.addVetoableChangeListener(pcl); } public void removeVetoableChangeListener(VetoableChangeListener pcl) { vcs.removeVetoableChangeListener(pcl); } public void addPropertyListener(PropertyChangeListener pcl) { pcs.addPropertyChangeListener(pcl); } public void removePropertyListener(PropertyChangeListener pcl) { pcs.removePropertyChangeListener(pcl); } public void setGemoed(String newgemoed) {oldgemoed=gemoed; try {vcs.fireVetoableChange(“gemoed”,oldgemoed,newgemoed); gemoed=newgemoed; pcs.firePropertyChange(“gemoed”,oldgemoed,newgemoed); } catch(PropertyVetoException e){} // doe iets met het nieuw gemoed naargelang de toepassing } }
Roept de routine vetoableChange op van elke luisteraar.
class Luisteraar implements VetoableChangeListener { public void vetoableChange(PropertyChangeEvent e) throws PropertyVetoException {// doe iets met e.getOldValue(), e.getNewValue(), e.getPropertyName() bij niet akkoord gaan met de voorgestelde wijziging: throw new PropertyVetoException(“slecht gemoed”,e);
Visuele Gebruikers Omgevingen: Swing 163
162/395
} } VetoableChangeListeners worden verwittigt indien iemand probeert een property te wijzigen. Ze worden verwittigd doordat hun routine vetoableChange wordt opgeroepen. Deze routine kan echter een exceptie werpen. Wat gebeurt er indien er een exceptie wordt geworpen? De routine die de luisteraar verwittigde zal dan onderbroken worden. Hoe dit begrijpen? De kern zit hem in de try-catch blok: try {vcs.fireVetoableChange(“gemoed”,oldgemoed,newgemoed); gemoed=newgemoed; pcs.firePropertyChange(“gemoed”,oldgemoed,newgemoed); } catch(PropertyVetoException e){} vcs.fireVetoableChange zal iedere luisteraar verwittigen door de routine vetoableChange op te roepen. Indien deze echter een exceptie werpt, zal er uit het try-catch blok worden gesprongen en zullen de overblijvende instructies ( gemoed=newgemoed; pcs.firePropertyChange(“gemoed”,oldgemoed,newgemoed);) niet meer uitgevoerd worden. Pas indien geen enkele luisteraar van het type VetoableChangeListener een exceptie geworpen heeft, zal gemoed worden gewijzigd en zullen alle geregistreerde PropertyChangeListeners verwittigd worden dat de property daadwerkelijk gewijzigd werd.
Visuele Gebruikers Omgevingen: Swing 164
163/395
Voorbeeld van PropertyChangeListener Er zijn properties (background, foreground, font) die bij w ijziging andere (luisteraars) automatisch verwittigen. Sommige properties (eigenschappen die kunnen veranderd worden met set… en opgevraagd worden met get…) verwittigen al hun luisteraars indien ze veranderen. De foreGround, backGround color en font zijn zulke properties. De geregistreerde PropertyChangeListeners zullen verwittigd worden als één van bovengenoemde properties verandert. Een property XXX zal steeds een getXXX functie hebben om zijn waarde op te vragen en meestal een setXXX functie om zijn waarde te veranderen. Telkens de property XXX verandert (doordat iemand setXXX heeft opgeroepen) zullen de luisteraars (PropertyChangeListeners) verwittigd worden. Dit is zo automatisch voor de properties foreground, background en font.
import import import import
javax.swing.*; java.awt.*; java.awt.event.*; java.beans.*;
// BackGround, foreGround en Font zijn bound properties. // luisteraars worden verwittigd indien er een wijziging // optreedt. class EigenschapLuisteraar implements PropertyChangeListener {public void propertyChange(PropertyChangeEvent e)
Visuele Gebruikers Omgevingen: Swing 165
164/395
{JOptionPane.showMessageDialog(null, "algemene luisteraar "+ e.getPropertyName()+"\n old: "+e.getOldValue() +" new: " +e.getNewValue() ); } } public class Swing21 extends JApplet {JTextField field=new JTextField(10); JButton but1=new JButton("achtergrond groen"), but2=new JButton("achtergrond geel"), but3=new JButton("letters rood"), but4=new JButton("letters blauw"); public void init() {Container c=getContentPane(); c.setLayout(new FlowLayout()); c.add(field); c.add(but1); c.add(but2); c.add(but3); c.add(but4); but1.addActionListener(new ActionListener() {public void actionPerformed(ActionEvent e) {field.setBackground(Color.green);}}); but2.addActionListener(new ActionListener() {public void actionPerformed(ActionEvent e) {field.setBackground(Color.yellow);}}); but3.addActionListener(new ActionListener() {public void actionPerformed(ActionEvent e) {field.setForeground(Color.red);}}); but4.addActionListener(new ActionListener() {public void actionPerformed(ActionEvent e) {field.setForeground(Color.blue);}}); field.addPropertyChangeListener(new EigenschapLuisteraar()); } }
Visuele Gebruikers Omgevingen: Swing 166
165/395
Exceptions public class Exception01 {static public void main(String args[]) {routine1(); } static public void routine1() {routine2(); } static public void routine2() {routine3(); } static public void routine3() {int i=0; int j=0; int k; k=i/j; } }
java.lang.ArithmeticException: / by zero at Exception01.routine3(Exception01.java:19) at Exception01.routine2(Exception01.java:13) at Exception01.routine1(Exception01.java:10) at Exception01.main(Exception01.java:7) Exception in thread "main" Process Exit... public class Exception01 {static public void main(String args[]) {routine1(); } static public void routine1() {routine2(); } static public void routine2() {routine3(); } static public void routine3() {int i=0; int j=0; int k=999; try{ k=i/j;} catch(Exception a) {System.out.println("fout:"+a.getMessage()); } } }
fout:/ by zero public class Exception01 {static public void main(String args[]) {routine1(); } static public void routine1() {routine2(); } static public void routine2() {try{routine3(); }catch(Exception a) {System.out.println("fout:"+a.getMessage()); System.out.println("de fout trad op in : "); a.printStackTrace(); } System.out.println("dit wordt nog uitgevoerd"); System.out.println("en dit ook"); } static public void routine3() {int i=0; int j=0; int k=999; k=i/j; } }
fout:/ by zero de fout trad op in : java.lang.ArithmeticException: / by zero at Exception01.routine3(Exception01.java:26) at Exception01.routine2(Exception01.java:13) at Exception01.routine1(Exception01.java:10) at Exception01.main(Exception01.java:7)
Visuele Gebruikers Omgevingen: Swing 167
166/395
dit wordt nog uitgevoerd en dit ook public class Exception01 {static public void main(String args[]) {try{routine1(); }catch(Exception a) {System.out.println("fout:"+a.getMessage()); System.out.println("de fout trad op in : "); a.printStackTrace(); } System.out.println("dit wordt nog uitgevoerd"); System.out.println("en dit ook"); } static public void routine1() {routine2(); System.out.println("dit wordt NIET uitgevoerd"); } static public void routine2() {routine3(); System.out.println("dit wordt NIET uitgevoerd"); } static public void routine3() {int i=0; int j=0; int k=999; k=i/j; System.out.println("dit wordt NIET uitgevoerd"); } }
fout:/ by zero de fout trad op in : java.lang.ArithmeticException: / by zero at Exception01.routine3(Exception01.java:28) at Exception01.routine2(Exception01.java:21) at Exception01.routine1(Exception01.java:17) at Exception01.main(Exception01.java:7) dit wordt nog uitgevoerd en dit ook class MijnException extends Exception {public MijnException() {super("we delen door een te klein getal: we verliezen precisie"); } } public class Exception01 {static public void main(String args[]) {try{routine1(); System.out.println("dit wordt NIET uitgevoerd"); }catch(MijnException a) {System.out.println("fout:"+a.getMessage()); } System.out.println("dit wordt nog uitgevoerd"); System.out.println("en dit ook"); } static public void routine1() throws MijnException {routine2(); System.out.println("dit wordt NIET uitgevoerd"); } static public void routine2() throws MijnException {routine3(); System.out.println("dit wordt NIET uitgevoerd"); } static public void routine3() throws MijnException {int i=0; int j=0; int k=999; if( j<0.0001) throw new MijnException(); else k=i/j; System.out.println("dit wordt NIET uitgevoerd"); }
Visuele Gebruikers Omgevingen: Swing 168
167/395
}
fout:we delen door een te klein getal: we verliezen precisie dit wordt nog uitgevoerd en dit ook
import import import import
javax.swing.*; java.awt.event.*; java.awt.*; java.text.*;
class MijnException extends Exception {public MijnException(){super("deler is te groot");} } class delen extends JFrame {public delen() {super("we delen 5 door een door ons te kiezen getal"); Container c=getContentPane(); final JLabel lab=new JLabel("geef deler"); final JTextField field=new JTextField(5); final JTextField result=new JTextField(10); final DecimalFormat precision3=new DecimalFormat("0.000"); result.setEditable(false); c.setLayout(new FlowLayout()); c.add(lab);c.add(field);c.add(result); field.addActionListener(new ActionListener() {public void actionPerformed(ActionEvent e) {try{ int getal=Integer.parseInt(field.getText()); result.setText(precision3.format(deel(5,getal)) ); } catch(NumberFormatException nfe) {JOptionPane.showMessageDialog(null,"geef getal"); } catch(MijnException me) {JOptionPane.showMessageDialog(null,me.getMessage()); } catch(Exception ee){System.out.println(ee.getMessage()); } finally // wordt steeds uitgevoerd {// resources vrijgeven die in de try werden gebruikt } } }); setVisible(true); pack(); } public int deel(int a,int b) throws MijnException {if( b>10) throw new MijnException(); else return a/b; } } public class Exception02 { public static void main(String args[]) {new delen(); } }
Gewoon drie tellers We hebben een klasse Teller die drie button heeft om de teller te verhogen, verlagen en terug op nul te zetten. Van deze klasse maken we drie objecten. Visuele Gebruikers Omgevingen: Swing 169
168/395
class Teller extends JPanel {JButton but1=new JButton("+"), but2=new JButton("-"), but3=new JButton("0"); JLabel lab=new JLabel("0"); int waarde=0; public Teller() {setLayout(new FlowLayout()); add(but1);add(but2);add(but3);add(lab); but1.addActionListener(new ActionListener() {public void actionPerformed(ActionEvent e) {waarde++;displayWaarde();}}); but2.addActionListener(new ActionListener() {public void actionPerformed(ActionEvent e) {waarde--;displayWaarde();}}); but3.addActionListener(new ActionListener() {public void actionPerformed(ActionEvent e) {waarde=0;displayWaarde();}}); } public void displayWaarde() {lab.setText(""+waarde); } } public class Swing22 extends JApplet {Teller teller1,teller2,teller3; public void init() {Container c=getContentPane(); c.setLayout(new FlowLayout()); teller1=new Teller();c.add(teller1); teller2=new Teller();c.add(teller2); teller3=new Teller();c.add(teller3); } }
Drie tellers , maar nu met propertyChangeListeners (zelf gemaakt). In dit voorbeeldje hebben we drie tellers met elk een bijhorende Staaf die visueel de grootte van de teller weergeeft. Telkens een teller van waarde verandert, zal zijn luisteraar verwittigd worden en hertekend worden.
Dit voorbeeldje heeft drie tellers en elke teller heeft een property ‘waarde teller’. Eigenlijk zouden we ook functies setWaardeTeller en getWaardeTeller moeten hebben voor een complete property. Maar die hebben we even weggelaten. Elke keer dat een teller van waarde verandert, zal het zijn luisteraars verwittigen. Hiervoor houden we een array van luisteraars bij. Maar in plaats van zelf deze array te programmeren maken we gebruikt van de klasse PropertyChangeSupport. Deze klasse beheert een array van PropertyChangeListeners. De klasse PropertyChangeSupport heeft een methode
Visuele Gebruikers Omgevingen: Swing 170
169/395
firePropertyChange die voor alle luisteraar in de verborgen array de methode propertyChange zal oproepen. Ook heeft deze klasse de methodes addPropertyChangeListener en removePropertyChangeListener. Hiermee kunnen we aan de klasse Teller ook methodes addPropertyChangeListener en (We moeten toch nieuwe luisteraar removePropertyChangeListener toevoegen. kunnen toevoegen. Het enige dat je moet onthouden van PropertyChangeSupport is : het omvat een interne array van objecten die de interface PropertyChangeListener implementeren. Dit houdt in dat deze objecten een routine propertyChange moeten hebben. Met behulp van de routine firePropertyChange, zal heel de interne array worden afgelopen en zal voor elk object in de array de routine propertyChange worden opgeroepen.
class Staaf extends JPanel implements PropertyChangeListener {int hoogteStaaf=0; public Staaf() {setPreferredSize(new Dimension(150,30)); } public void paint(Graphics g) { g.clearRect(0,0,150,30); g.setColor(Color.blue); g.fillRect(0,0,hoogteStaaf*5,30); } public void propertyChange(PropertyChangeEvent e) {if(e.getPropertyName().equals("waarde teller")) {hoogteStaaf=((Integer)e.getNewValue()).intValue() ; repaint(); } } } class Teller extends JPanel {JButton but1=new JButton("+"), but2=new JButton("-"), but3=new JButton("0"); JLabel lab=new JLabel("0"); int waarde=0; PropertyChangeSupport luisteraars= new PropertyChangeSupport(this); public void addPropertyChangeListener(PropertyChangeListener l) {luisteraars.addPropertyChangeListener(l); } public void removePropertyChangeListener(PropertyChangeListener l)
Visuele Gebruikers Omgevingen: Swing 171
170/395
{luisteraars.removePropertyChangeListener(l); } public Teller() {setLayout(new FlowLayout()); add(but1);add(but2);add(but3);add(lab); but1.addActionListener(new ActionListener() {public void actionPerformed(ActionEvent e) {luisteraars.firePropertyChange("waarde teller", new Integer(waarde), new Integer(waarde+1)); waarde++; displayWaarde();}}); but2.addActionListener(new ActionListener() {public void actionPerformed(ActionEvent e) {luisteraars.firePropertyChange("waarde teller", new Integer(waarde), new Integer(waarde-1)); waarde--;displayWaarde();}}); but3.addActionListener(new ActionListener() {public void actionPerformed(ActionEvent e) {luisteraars.firePropertyChange("waarde teller", new Integer(waarde), new Integer(0)); waarde=0;displayWaarde();}}); } public void displayWaarde() {lab.setText(""+waarde); } } public class Swing23 extends JApplet {Teller teller1,teller2,teller3; Staaf staaf1,staaf2,staaf3; public void init() {Container c=getContentPane(); c.setLayout(new FlowLayout()); teller1=new Teller();c.add(teller1); teller2=new Teller();c.add(teller2); teller3=new Teller();c.add(teller3); staaf1=new Staaf();c.add(staaf1); staaf2=new Staaf();c.add(staaf2); staaf3=new Staaf();c.add(staaf3); teller1.addPropertyChangeListener(staaf1); teller2.addPropertyChangeListener(staaf2); teller3.addPropertyChangeListener(staaf3); } }
De klasse Staaf heeft een routine propertyChange en implementeert hierdoor de interface PropertyChangeListener. Objecten van de klasse Staaf kunnen aldus met de routine addPropertyChangeListener als luisteraar geregistreerd worden bij de klasse Teller.
Wie heeft er een veto voor een verandering van een waarde? In dit voorbeeldje gaan we een stap verder. We hebben een veldje waarin een waarde kan ingevuld worden. Meerdere luisteraars zullen verwittigd worden indien u de waarde verandert. Het verschil met het vorig voorbeeld is dat we nu werken met VetoableChangeListener VetoableChangeSupport vetoableChange fireVetoableChange
i.p.v. i.p.v. i.p.v. i.p.v.
PropertyChangeListener PropertyChangeSupport propertyChange firePropertyChange
Het woordje property werd vervangen door vetoable (het stellen van een veto). Weeral kunnen meerdere luisteraar geregistreerd worden. Ze worden verwittigd indien een property van waarde verandert, maar kunnen nu een veto stellen door het werpen van een exception.
Visuele Gebruikers Omgevingen: Swing 172
171/395
Elke luisteraar moet nu een routine vetoableChange implementeren. Deze routine moet de exception PropertyVetoException kunnen werpen indien de nieuwe waarde van de property niet goed is. Het gevolg hiervan is dat de routine fireVetoableChange (die voor alle geregistreerde objecten de routine vetoableChange zal oproepen), kan onderbroken worden door een exceptie. U moet daarom (verplicht) rond de oproep van de routine fireVetoableChange een try – catch zetten om de exceptie op te vangen. De oproep fireVetoableChange ziet er typisch als volgt uit. try{ luisteraars.fireVetoableChange("waarde teller", new Integer(waarde), new Integer(nieuwewaarde)); // de volgende instructies worden alleen maar uitgevoerd // indien niemand een veto stelt. waarde=nieuwewaarde; field.setText(""+waarde);} catch(PropertyVetoException e) {field.setText("veto !!");
}
De luisteraars worden verwittigd door fireVetoableChange die voor elke luisteraar de routine vetoableChange zal oproepen. Bij een veto worden de instructies achter Deze instructies gaan de oproep van fireVetoableChange niet meer uitgevoerd. juist de nieuwe waarde toekennen aan de veranderlijke die hoort bij de property. (hier is dat waarde). Bij een veto zal de veranderlijke ‘waarde’ dus geen nieuwe waarde krijgen en zal (door de catch) de tekst ‘veto’ in het veldje verschijnen.
Bovenaan staat een veldje TELLER. De waarde van de teller kan gewijzigd worden met behulp van setTeller. De klasse TELLER heeft echter een routine addVetoableChangeListener. De routine setTeller zal alle VetoableChangeListeners verwittigen. Onderaan de toepassing hebben we een veldje ‘geef nieuwe waarde van teller’. Hier kunt u een nieuwe waarde intypen. We hebben drie visuele luisteraars: objecten van een klasse U kunt hier gewoon een waarde intypen. VetoValueController. (die u steeds kunt veranderen) zullen niet aanvaard worden.)
Deze waarde
We hebben ook een niet visuele luisteraar: een object van de klasse Keurder die nooit de waarde 13 (brengt ongeluk) zal toelaten. import javax.swing.*;
Visuele Gebruikers Omgevingen: Swing 173
172/395
import java.awt.*; import java.awt.event.*; import java.beans.*; class VetoValueController extends JPanel implements VetoableChangeListener {int VetoValue=0; JLabel lab=new JLabel("Geblokkeerde waarde:"); JTextField field=new JTextField(5); JTextField inputField=new JTextField(5); public VetoValueController() { add(lab);add(field);add(inputField); field.setEditable(false); setPreferredSize(new Dimension(150,50)); inputField.addActionListener(new ActionListener() {public void actionPerformed(ActionEvent e) { String ingetypt = e.getActionCommand(); VetoValue=Integer.parseInt(ingetypt); field.setText(ingetypt); } }); } public void vetoableChange(PropertyChangeEvent e) throws PropertyVetoException {if(e.getPropertyName().equals("waarde teller")) {int hulp = ((Integer)e.getNewValue()).intValue() ; if(hulp==VetoValue) throw new PropertyVetoException("neen",e); } } } class Keurder implements VetoableChangeListener { public void vetoableChange(PropertyChangeEvent e) throws PropertyVetoException {if(e.getPropertyName().equals("waarde teller")) {int hulp=((Integer)e.getNewValue()).intValue() ; if( hulp ==13)//ongeluk? throw new PropertyVetoException("13 brengt ongeluk",e); } } } class Teller extends JPanel {JTextField field=new JTextField(10); JLabel lab=new JLabel("TELLER :"); int waarde=0; VetoableChangeSupport luisteraars= new VetoableChangeSupport(this); public void addVetoableChangeListener(VetoableChangeListener l) {luisteraars.addVetoableChangeListener(l); } public void removeVetoableChangeListener(VetoableChangeListener l) {luisteraars.removeVetoableChangeListener(l); } public Teller() {setLayout(new FlowLayout()); add(lab);add(field);field.setEditable(false); } public void setTeller(int nieuwewaarde) { // met fireVetoableChange worden alle luisteraars verwittigd. // ze mogen een veto stellen, indien dit zo is worden de // laatste bevelen van de try-catch clausule niet uitgevoerd. try{ luisteraars.fireVetoableChange("waarde teller", new Integer(waarde), new Integer(nieuwewaarde)); // de volgende instructies worden alleen maar uitgevoerd // indien niemand een veto stelt. waarde=nieuwewaarde; field.setText(""+waarde);}
Visuele Gebruikers Omgevingen: Swing 174
173/395
catch(PropertyVetoException e) {field.setText("veto !!"); } } } public class Swing24 extends JApplet {Teller teller1; VetoValueController controller1,controller2,controller3; JLabel boodschap=new JLabel(); JLabel l=new JLabel("geef nieuwe waarde teller: "); JTextField field = new JTextField(10); JPanel pp=new JPanel(); public void init() {Container c=getContentPane(); c.setLayout(new FlowLayout()); teller1=new Teller();c.add(teller1); controller1=new VetoValueController();c.add(controller1); controller2=new VetoValueController();c.add(controller2); controller3=new VetoValueController();c.add(controller3); teller1.addVetoableChangeListener(controller1); teller1.addVetoableChangeListener(controller2); teller1.addVetoableChangeListener(controller3); pp.add(l);pp.add(field);c.add(pp); c.add(boodschap); // zou het volgende lukken? teller1.setTeller(-1); // slechts vanaf nu zal er op 13 worden getest bij teller1 teller1.addVetoableChangeListener(new Keurder()); field.addActionListener(new ActionListener() {public void actionPerformed(ActionEvent e) {String ingetypt = e.getActionCommand(); int waarde=Integer.parseInt(ingetypt); // zou het lukken? teller1.setTeller(waarde);// dit kan al dan niet lukken } }); } }
Visuele Gebruikers Omgevingen: Swing 175
174/395
Threads import java.awt.*; import javax.swing.*; import java.awt.event.*; class MijnFrame extends JFrame {int rij=15; public MijnFrame() {super(); setBounds(5,5,400,500); setVisible(true); //wachten noodzakelijk anders wordt // scherm opgebouwd terwijl threads // lopen en verdwijnen enkele lijnen try {Thread.sleep(3000);} // we wachten 3 seconden catch(InterruptedException e) {System.out.println("thread"+ "interrupted");} new Thread( new Runnable() {public void run() {for(int i=1;i<10;i++) getGraphics().drawString(""+i,50,rij+=15);} }).start(); new Thread( new Runnable() {public void run() {for(int i=1;i<10;i++) getGraphics().drawString(""+i,150,rij+=15);} }).start(); new Thread( new Runnable() {public void run() {for(int i=1;i<10;i++) getGraphics().drawString(""+i,250,rij+=15);} }).start(); addWindowListener(new WindowAdapter() {public void windowClosing(WindowEvent e) {System.exit(0);} }); } } public class Thr01 {public static void main(String arg[]) {new MijnFrame(); } } Er worden drie nieuwe threads opgestart. De code in elke thread wordt gelijktijdig met de andere threads uitgevoerd.
Een thread stelt een nieuw proces voor Stel dat u drie blokken code hebt: - Blok code A - Blok code B
Visuele Gebruikers Omgevingen: Swing 176
175/395
- Blok code C Stel dat u, nadat blok code A werd uitgevoerd, de uitvoering van blok code B en C gelijktijdig moet gebeuren, dan moet u de code van blok B in een thread steken. - Blok code A - Thread t=new thread(new B() ); - t.start(); - Blok code C public class B implements Runnable {public void run() { “code blok B” } } De interface Runnable is erg eenvoudig: Public interface Runnable { public void run();} De code die door het apart proces moet uitgevoerd worden staat dus in een routine run. Dat is de enige voorwaarde. Aan de constructor van de klasse Thread moet steeds een Runnable object doorgegeven worden. Als de routine start wordt uitgevoerd, zal een nieuw proces worden opgestart en zal de routine run, van het object dat doorgegeven werd aan de constructor, uitgevoerd worden. Nadat het proces werd opgestart, gaat de uitvoering van het normale programma dadelijk door. Er wordt niet gewacht. Hierdoor wordt de code van blok B en C gelijktijdig uitgevoerd.
Uitvoer vorig programma Er verschijnen drie kolommen naast elkaar op het scherm. Elke kolom komt overeen met een thread. De eerste thread zal steeds in kolom 50 schrijven, de tweede thread steeds in kolom 150 en de derde kolom steeds in kolom 250. In vorig programma werden geen nieuwe klassen gebruikt die de interface Runnable implementeren, maar werden naamloze innerklassen gebruikt. Deze innerklassen kunnen wel aan de gemeenschappelijke veranderlijken van de omliggende klasse aan. Zo kan de veranderlijke ‘rij’ benaderd worden vanuit de drie processen. Telkens een proces 15 optelt bij de veranderlijke rij, zal het volgend getal lager worden geschreven. Belangrijk: Als processen gelijktijdig lopen weet u niet welke bevelen eerst zullen worden uitgevoerd. Neem nu het bevel getGraphics().drawString(""+i,250,rij+=15);} Dit zijn eigenlijk twee bevelen: rij+=15;//bevel A getGraphics().drawString(""+i,250,rij+=15);}// bevel B Tussen de uitvoering van de eerste instructie en de uitvoering van de tweede instructie, is het mogelijk dat een andere thread ook instructies uitvoert, en bijvoorbeeld de rij eerst ook nog eens verhoogd, of eerst nog eens een getal uitschrijft in een andere kolom (van dezelfde rij).
Visuele Gebruikers Omgevingen: Swing 177
176/395
Elke keer u bovenstaand programma uitvoert, zal de uitvoering anders zijn. Het hangt van het uitbatingssysteem af in welke concrete volgorde de processen worden uitgevoerd. Als u niet opgeeft, hebben threads prioriteit 5. U kunt deze prioriteit wijzigen. (Hiervoor moet u gaan kijken bij de klasse Thread). Het is een hele krachttoer van java om zulke systeemafhankelijke dingen als processen machineonafhankelijk te programmeren. Elk uitbatingssysteem heeft meestal een andere wijze waarop de tijd tussen verschillende processen wordt verdeeld. U moet u inbeelden dat bovenstaand programma uit drie maal 20 instructies bestaat (10x2). U schrijft deze drie sets van instructies in drie kolommen naast elkaar en vervolgens begint u bovenaan elk stapeltje naar beneden toe. U kunt afwisselend instructies van de eerste, tweede of derde stapel uitvoeren. Altijd in een andere volgorde. U kunt bijvoorbeeld eerst 5 instructies van stapel één uitvoeren en dan 2 instructies van stapel twee en dan nog eens drie instructies van stapel drie. …
Andere versies van hetzelfde programma Een expliciete i nnerklasse gebruiken class MijnFrame02 extends JFrame {int rij=15; private class EersteKolom implements Runnable {public void run() {for(int i=1;i<10;i++) getGraphics().drawString(""+i,50,rij+=15); }} public MijnFrame02() {super(); setBounds(5,5,400,500);
setVisible(true);
Thread one=new Thread(new EersteKolom()); one.start(); new Thread( new Runnable() {public void run(){for(int i=1;i<10;i++) getGraphics().drawString(""+i,150,rij+=15); }}).start(); new Thread( new Runnable() {public void run(){for(int i=1;i<10;i++) getGraphics().drawString(""+i,250,rij+=15); }}).start(); addWindowListener(new WindowAdapter() {public void windowClosing(WindowEvent e){System.exit(0);} }); }} public class Thr02 {public static void main(String arg[]){new MijnFrame02(); }} De klasse EersteKolom is een innerklasse. Het blijft noodzakelijk dat het een innerklasse is omdat alleen een innerklasse aan de gemeenschappelijke veranderlijke ‘rij’ aan kan.
De hoofdklasse implementeert zelf de interface Runnable class MijnFrame03 extends JFrame implements Runnable {int rij=15; public void run()
{for(int i=1;i<10;i++) getGraphics().drawString(""+i,50,rij+=15); }
Visuele Gebruikers Omgevingen: Swing 178
177/395
public MijnFrame03() {super(); setBounds(5,5,400,500);
setVisible(true);
Thread one=new Thread(this); one.start(); new Thread( new Runnable() {public void run(){for(int i=1;i<10;i++) getGraphics().drawString(""+i,150,rij+=15);} }).start(); new Thread( new Runnable() {public void run(){for(int i=1;i<10;i++) getGraphics().drawString(""+i,250,rij+=15);} }).start(); addWindowListener(new WindowAdapter() {public void windowClosing(WindowEvent e){System.exit(0);} }); }}
Dit is een speciaal geval. De hoofdklasse implementeert zelf de interface Runnable en heeft zelf dus een routine run. Omdat we aan de bijhorende thread een referentie naar een object Runnable moeten doorgeven, moeten we hier ‘this’ doorgeven aan de constructor van de klasse Thread. Weeral kan deze routine run aan de gemeenschappelijke veranderlijke ‘rij’.
Visuele Gebruikers Omgevingen: Swing 179
178/395
yield: voorrang geven aan anderen
class MijnFrame04 extends JFrame {int rij=15; public MijnFrame04() {super(); setBounds(5,5,400,500); setVisible(true); new Thread( new Runnable() {public void run() {for(int i=1;i<10;i++) {getGraphics().drawString(""+i+"yield",50,rij+=15); Thread.yield(); //Causes the currently executing thread object to //temporarily pause and allow other threads to execute. } }}).start(); new Thread( new Runnable() {public void run() {for(int i=1;i<10;i++) getGraphics().drawString(""+i,150,rij+=15); }}).start(); new Thread( new Runnable() {public void run() {for(int i=1;i<10;i++) getGraphics().drawString(""+i,250,rij+=15); }}).start(); } public class Thr04 {public static void main(String arg[]) {new MijnFrame04(); }}
Visuele Gebruikers Omgevingen: Swing 180
179/395
De eerste thread zal na het uitvoeren van twee bevelen (rij+=15 en drawString) even pauzeren en andere wachtende threads voorlaten. Zolang thread twee en drie lopen, zal thread één dus steeds maar die twee bevelen uitvoeren en anderen voorlaten.
Join: thread wacht totdat een andere specifieke thread gedaan heeft Thread drie zal na de uitvoering van de helft van zijn bevelen wachten totdat thread twee gedaan heeft. Maar thread twee zal ook na de helft van de uitvoering van zijn bevelen wachten totdat thread één gedaan heeft. In bovenstaande schermafdrukken vindt u twee verschillende uitvoeringen van hetzelfde programma. In beide gevallen zal thread één steeds eerst gedaan hebben, gevolgd door thread twee. Thread drie zal steeds als laatste gedaan hebben. We zullen nu expliciete referenties naar de threads moeten bijhouden. Daarom heeft het programma drie veranderlijken van het type thread: one, two, three. ‘one’ stelt de eerste thread voor, ‘two’ de tweede thread, ‘three’ de derde thread. Hierdoor kunnen we, na de helft van de uivoering van thread ‘three’, de instructie ‘two.join()’ uitvoeren. Hierdoor stopt thread ‘three’ totdat thread ‘two’ gedaan heeft. Analoog zal tijdens de uitvoering van thread two de instructie one.join() thread two laten wachten totdat thread ‘one’ gedaan heeft.
Visuele Gebruikers Omgevingen: Swing 181
180/395
class MijnFrame05 extends JFrame {int rij=15; Thread one,two,three; public MijnFrame05() {super(); setBounds(5,5,400,500);
setVisible(true);
one=new Thread( new Runnable() {public void run(){for(int i=1;i<10;i++) {getGraphics().drawString(""+i,50,rij+=15);}}}); one.start(); two=new Thread( new Runnable() {public void run() {for(int i=1;i<10;i++) {getGraphics().drawString(""+i,150,rij+=15); if(i==5) {getGraphics().drawString("join one",165,rij); try{one.join();// waits for thread one to die . getGraphics().drawString("joined one",165,rij); }catch(InterruptedException e) {System.out.println(e);} } } }}); two.start(); three=new Thread( new Runnable() {public void run() {for(int i=1;i<10;i++) {getGraphics().drawString(""+i,250,rij+=15); if(i==5) {getGraphics().drawString("join two",265,rij); try{two.join(); getGraphics().drawString("joined two",265,rij); }catch(InterruptedException e) {System.out.println(e);} } } }}); three.start(); addWindowListener(new WindowAdapter() {public void windowClosing(WindowEvent e){System.exit(0);}}); } } public class Thr05 {public static void main(String arg[]){new MijnFrame05();}} ================================================================
Drie bevelen die na elkaar, on-onderbroken worden uitgevoerd? Soms wil je er zeker van zijn dat enkele instructies in een thread on-onderbroken worden uitgevoerd. We hebben daarom in het volgende programma de drie thread tienmaal de volgende instructies laten uitvoeren: getGraphics().drawString(""+i,50,rij+=15); getGraphics().drawString(""+i,60,rij); getGraphics().drawString(""+i,70,rij);
Visuele Gebruikers Omgevingen: Swing 182
181/395
Dit zijn vier instructies (eenmaal rij+=15 en driemaal drawString). In een eerste versie laten we alles gelijktijdig uitvoeren. Deze vier instructies kunnen dan op een willekeurige tijdstip onderbroken worden door een andere thread die dan op dezelfde rij gaat schrijven of die vlug even de rij verhoogt. De volgende schermafdruk laat zien dat het zeer zelden gebeurt dat de vier instructies van één thread na elkaar worden uitgevoerd.
class MijnFrame06 extends JFrame {int rij=15; Thread one,two,three; public MijnFrame06() {super(); setBounds(5,5,400,500);
setVisible(true);
one=new Thread( new Runnable() {public void run(){for(int i=1;i<10;i++) {getGraphics().drawString(""+i,50,rij+=15); getGraphics().drawString(""+i,60,rij); getGraphics().drawString(""+i,70,rij); } }}); one.start(); two=new Thread( new Runnable() {public void run(){for(int i=1;i<10;i++) {getGraphics().drawString(""+i,150,rij+=15); getGraphics().drawString(""+i,160,rij); getGraphics().drawString(""+i,170,rij); }
Visuele Gebruikers Omgevingen: Swing 183
182/395
}}); two.start(); three=new Thread( new Runnable() {public void run(){for(int i=1;i<10;i++) {getGraphics().drawString(""+i,250,rij+=15); getGraphics().drawString(""+i,260,rij); getGraphics().drawString(""+i,270,rij); } }}); three.start(); addWindowListener(new WindowAdapter() {public void windowClosing(WindowEvent e) {System.exit(0);}}); } } public class Thr06 {public static void main(String arg[]){new MijnFrame06();}}
Kunnen de drie bevelen nu niet steeds na elkaar worden uitgevoerd? En dan pas zou een andere thread aan beurt mogen komen. Blijkbaar kan een andere thread op een willekeurig tijdstip onderbroken worden door een andere thread. Hoe kunnen we dit synchroniseren? Het mechanisme dat in Java wordt gebruikt bestaat uit monitors. Elk object in java kan gemonitored worden zodat geen twee routines die aangegeven zijn met synchronized terzelfdertijd het object kunnen accessen. Van zodra een eerste thread een routine of een deel code, aangegeven met synchronized binnengaat, zal het object waarop gesynchronized wordt gemonitored worden: er wordt op toegezien dat geen enkele andere thread synchronized code voor dit zelfde object zal uitvoeren. De uitvoer hierboven zou het verbeterde resultaat moeten zijn. In
het programma hieronder wordt een object geentweetegelijk gemaakt. Het is een willekeurig object van de klasse Object. (De concrete
Visuele Gebruikers Omgevingen: Swing 184
183/395
klasse heeft geen belang hier, zolang er maar een monitor mee verbonden kan worden.) Van zodra een thread het blok synchronized(geentweetegelijk){} Binnenkomt, wordt er een ‘ingebruik’-vlag geplaatst op het object geentweetegelijk. Bij het verlaten van dit synchronized blok, wordt de vlag van geentweetegelijk terug op vrij gezet.
Als een andere thread dan ook tracht van blok binnen te komen dat ook synchronizeert op hetzelfde object geentweetegelijk, dan wordt eerst nagegaan of de vlag op vrij staat. Indien niet, dan blijft deze andere thread wachten totdat de andere thread gedaan heeft. Dan pas kan hij het blok binnengaan en zullen anderen thread moeten wachten. Omdat de drie threads monitor-ren op hetzelfde object, dwingen dat steeds maar één thread tegelijkertijd de gesynchronizeerde code kan uitvoeren. class MijnFrame07 extends JFrame {int rij=15; Thread one,two,three; Object geentweetegelijk=new Object();//een willekeurig object public MijnFrame07() {super(); setBounds(5,5,400,500); setVisible(true); one=new Thread( new Runnable() {public void run(){for(int i=1;i<10;i++) synchronized(geentweetegelijk) {getGraphics().drawString(""+i,50,rij+=15); getGraphics().drawString(""+i,60,rij); getGraphics().drawString(""+i,70,rij); } }}); one.start(); two=new Thread( new Runnable() {public void run(){for(int i=1;i<10;i++) synchronized(geentweetegelijk) {getGraphics().drawString(""+i,150,rij+=15); getGraphics().drawString(""+i,160,rij); getGraphics().drawString(""+i,170,rij); } }}); two.start(); three=new Thread( new Runnable() {public void run(){for(int i=1;i<10;i++) synchronized(geentweetegelijk) {getGraphics().drawString(""+i,250,rij+=15); getGraphics().drawString(""+i,260,rij); getGraphics().drawString(""+i,270,rij); } }}); three.start(); addWindowListener(new WindowAdapter() {public void windowClosing(WindowEvent e){System.exit(0);}}); } } public class Thr07 {public static void main(String arg[]){new MijnFrame07();}}
Visuele Gebruikers Omgevingen: Swing 185
184/395
Opdracht
Hoe kunnen we bovenstaande uitvoer bekomen? In thread een en drie moeten de vier instructies steeds on-onderbroken worden uitgevoerd. In thread twee moeten alle instructies on-onderbroken worden uitgevoerd. Oplossing: plaats in thread twee heel de for lus in het blok code dat synchroniseert op het object geentweetegelijk. class MijnFrame08 extends JFrame {int rij=15; Thread one,two,three; Object geentweetegelijk=new Object();//een willekeurig object public MijnFrame08() {super(); setBounds(5,5,400,500);
setVisible(true);
one=new Thread( new Runnable() {public void run(){for(int i=1;i<10;i++) synchronized(geentweetegelijk) {getGraphics().drawString(""+i,50,rij+=15); getGraphics().drawString(""+i,60,rij); getGraphics().drawString(""+i,70,rij); } }}); one.start();
Visuele Gebruikers Omgevingen: Swing 186
185/395
two=new Thread( new Runnable() {public void run(){synchronized(geentweetegelijk) {for(int i=1;i<10;i++) {getGraphics().drawString(""+i,150,rij+=15); getGraphics().drawString(""+i,160,rij); getGraphics().drawString(""+i,170,rij); } } }}); two.start(); three=new Thread( new Runnable() {public void run(){for(int i=1;i<10;i++) synchronized(geentweetegelijk) {getGraphics().drawString(""+i,250,rij+=15); getGraphics().drawString(""+i,260,rij); getGraphics().drawString(""+i,270,rij); } }}); three.start(); addWindowListener(new WindowAdapter() {public void windowClosing(WindowEvent e){System.exit(0);}}); } } public class Thr08 {public static void main(String arg[]){new MijnFrame08();}}
Synchronizeren op twee verschillende objecten Eerst geven we weeral een versie waarbij helemaal niet gesynchronizeerd wordt. Veronderstel twee bankrekeningen. Voor elk van de twee bankrekeningen zijn er twee personen die tezelfdertijd op dezelfde rekening een frank willen storten en dadelijk terug afhalen. Het programma zal vier threads hebben. We gaan aldus vier kolommen getallen zien. Het getal dat we zien in de twee eerste kolommen stelt de waarde van de bankrekening één voor. Het getal dat we zien in de twee laatste kolommen stelt de waarde van de bankrekening twee voor. Thread one en two stellen twee personen voor die gelijktijdig tienmaal de volgende bewerking willen doen op bankrekening één: 1 frank storten en dadelijk 1 frank afhalen. Als dit ‘storten en afhalen’ ononderbroken zou zijn, krijgen we als uitvoer voor de eerste twee thread steeds : 10
of
10
10 10 De bankrekening begint bij 0, eerst één frank storten geeft 1, dadelijk 1 frank afhalen geeft terug 0. Als het storten en afhalen on-onderbroken gebeurt krijgen we steeds ‘1 0’ op dezelfde rij van uitvoeren.
Visuele Gebruikers Omgevingen: Swing 187
186/395
Bij niet-gesynchronizeerde uitvoering krijgen we het volgende:
Er zijn dus vier thread aan de slag. De twee eerste thread werken met bankrekening één. We zien bijvoorbeeld: eerste thread stort 1 en laat dit zien, tweede thread forceert een nieuwe rij 0 11 eerste thread haalt 1 frank af en laat dit zien, tweede thread stort één frank en laat zien tweede thread haalt 1 frank af maar voordat dit getoond wordt, zal thread één 1 frank storten (raar resultaat hij haalt 1 frank af maar blijft op 1 staan).
1
2 1
1
Twee andere personen (thread drie en vier) die juist hetzelfde doen , maar nu met een andere gemeenschappelijke rekening. Het eindresultaat is steeds goed (0). De tussenresultaten kunnen beter.
Visuele Gebruikers Omgevingen: Swing 188
187/395
De bankrekening en bijhorende gegeven waarop later gesynchronizeerd zal worden, worden in classe TweeGetallen gestoken. X stelt hierbij de stand van de bankrekening voor. Rij stelt hierbij de rij waarop op het scherm moet geschreven worden voor. Waarom wordt hiervoor een aparte klasse genomen? Later zullen we twee objecten van deze klasse maken en zullen de twee eerste threads synchronizeren op het eerste object en de twee laatste threads op het tweede object (tweede bankrekening). class TweeGetallen {public int x=0,rij=15; } class MijnFrame09 extends JFrame { Thread one,two,three,four; TweeGetallen GetalEen=new TweeGetallen(), GetalTwee=new TweeGetallen(); public MijnFrame09() {super(); setBounds(5,5,400,350);
setVisible(true);
one=new Thread( new Runnable() De eerste persoon gaat van de eerste {public void run() rekening tien maal een frank storten en {for(int i=1;i<10;i++) terug afhalen. {GetalEen.x++; getGraphics().drawString(""+GetalEen.x,50,GetalEen.rij+=15); GetalEen.x--; getGraphics().drawString(""+GetalEen.x,65,GetalEen.rij);}}}); one.start(); two=new Thread( new Runnable() {public void run() {for(int i=1;i<10;i++) {GetalEen.x++; getGraphics().drawString(""+GetalEen.x,100,GetalEen.rij+=15); GetalEen.x--; getGraphics().drawString(""+GetalEen.x,115,GetalEen.rij); } }}); two.start(); three=new Thread( new Runnable() {public void run() {for(int i=1;i<10;i++) {GetalTwee.x++; getGraphics().drawString(""+GetalTwee.x,200,GetalTwee.rij+=15); GetalTwee.x--; getGraphics().drawString(""+GetalTwee.x,215,GetalTwee.rij); } }}); three.start(); four=new Thread( new Runnable() {public void run() {for(int i=1;i<10;i++) {GetalTwee.x++; getGraphics().drawString(""+GetalTwee.x,250,GetalTwee.rij+=15); GetalTwee.x--; getGraphics().drawString(""+GetalTwee.x,265,GetalTwee.rij); } }}); four.start(); addWindowListener(new WindowAdapter() {public void windowClosing(WindowEvent e){System.exit(0);}}); }
Visuele Gebruikers Omgevingen: Swing 189
188/395
} public class Thr09 {public static void main(String arg[]){new MijnFrame09();}} De eerste twee threads zullen gelijktijdig tienmaal de volgende instructies uitvoeren: GetalEen.x++; getGraphics().drawString(""+GetalEen.x,50,GetalEen.rij+=15); GetalEen.x--; getGraphics().drawString(""+GetalEen.x,65,GetalEen.rij); Dit zijn vijf instructies (rij+=15 is ook een instructie). Deze vijf instructies kunnen op elk moment onderbroken worden door de andere thread die even de bankrekening gaat aanpassen of de rij gaat verhogen of even vlug gaat schrijven in zijn kolom. Thread een en twee werken met GetalEen.x als bankrekening en GetalEen.rij als schrijfrij. De twee andere threads werken met GetalTwee.x en GetalTwee.rij. Oplossing: U moet telkens twee threads synchronizeren op hun gemeenschappelijke rekening. Hieronder wordt gesynchronizeerd op GetalEen, maar synchronisatie op GetalTwee werd 'vergeten'. Het resultaat is overduidelijk.
De twee eerste threads geven nu steeds mooi ‘1 0’ als resultaat. Dit komt omdat de vijf instructies niet niet onderbroken mogen worden in een synchronized blok staan. Thread een en twee synchroniseren beiden op de veranderlijke GetalEen. Als thread een de vijf instructies begint uit te voeren, zal een monitor op het object GetalEen worden gezet. Als dan thread twee ook probeert de vijf instructies uit te voeren voordat thread gedaan heeft, zal thread twee even moeten wachten. Als thread een de vijf instructies heeft uitgevoerd, zal de vlag-monitor voor object GetalEen worden afgezet. Hierdoor kan thread even ‘zijn’ vlag planten op object GetalEen en verder gaan.
class TweeGetallen {public int x=0,rij=15; }
Visuele Gebruikers Omgevingen: Swing 190
189/395
class MijnFrame10 extends JFrame { Thread one,two,three,four; TweeGetallen GetalEen=new TweeGetallen(), GetalTwee=new TweeGetallen(); public MijnFrame10() {super(); setBounds(5,5,400,350);
setVisible(true);
one=new Thread( new Runnable() {public void run() {for(int i=1;i<10;i++) synchronized(GetalEen) {GetalEen.x++; getGraphics().drawString(""+GetalEen.x,50,GetalEen.rij+=15); GetalEen.x--; getGraphics().drawString(""+GetalEen.x,65,GetalEen.rij); } }}); one.start(); two=new Thread( new Runnable() {public void run() {for(int i=1;i<10;i++) synchronized(GetalEen) {GetalEen.x++; getGraphics().drawString(""+GetalEen.x,100,GetalEen.rij+=15); GetalEen.x--; getGraphics().drawString(""+GetalEen.x,115,GetalEen.rij); } }}); two.start(); three=new Thread( new Runnable() {public void run() {for(int i=1;i<10;i++) {GetalTwee.x++; getGraphics().drawString(""+GetalTwee.x,200,GetalTwee.rij+=15); GetalTwee.x--; getGraphics().drawString(""+GetalTwee.x,215,GetalTwee.rij); } }}); three.start(); four=new Thread( new Runnable() {public void run() {for(int i=1;i<10;i++) {GetalTwee.x++; getGraphics().drawString(""+GetalTwee.x,250,GetalTwee.rij+=15); GetalTwee.x--; getGraphics().drawString(""+GetalTwee.x,265,GetalTwee.rij); } }}); four.start(); } } public class Thr10 {public static void main(String arg[]){new MijnFrame10();}}
Synchronizeren op alle rekeningen Voor twee concrete rekeningen kunt u nog expliciet de synchronizeren op die twee rekeningen. Visuele Gebruikers Omgevingen: Swing 191
190/395
Stel dat u echter voor alle rekening-objecten wilt zorgen dat de vijf instructies steeds on-onderbroken zullen worden uitgevoerd.
Hoe zorgen we ervoor dat u bij meerdere rekeningen nooit vergeet om een te synchrronizeren op een rekening? Antwoord: plaats de te synchronizeren code bij de data waarop u synchroniseert. De uitvoer zal het volgende zijn en zonder aanpassingen geldt deze uitvoeren voor 100 bankrekeningen, zonder dat u op concrete objecten moet synchronizeren.
De vijf instructies werden nu in de klasse TweeGetallen gestoken in een routine ‘stortenhaalaf’. Als code staat er eigenlijk: this.x++; gg.drawString(""+this.x,kol, this.rij+=15); this.x--; gg.drawString(""+this.x,kol+15, this.rij); We gaan dus algemeen gaan synchroniseren op het object this. Dit stelt tijdens de uitvoering wel een concreet object voor. Het enige dat u nog moet doen is de routine ‘stortenhaalaf’ synchronized declareren. Als dan een thread voor een bepaald object tracht deze routine ‘stortenhaalaf’ uit te voeren, zal een vlag worden gezet op dat object (waar this naar wijst). Als een tweede thread dan tracht om ook die routine uit te voeren voor hetzelfde object, dan moet deze thread wachten totdat de eerste thread gedaan heeft met de uitvoering van de routine en de vlag wegneemt voor dat object.
class TweeGetallen {private int x=0,rij=15; public synchronized void stortenhaalaf(Graphics gg,int kol) {x++; gg.drawString(""+x,kol,rij+=15); x--; gg.drawString(""+x,kol+15,rij); } }
Visuele Gebruikers Omgevingen: Swing 192
191/395
class MijnFrame11 extends JFrame { Thread one,two,three,four; TweeGetallen GetalEen=new TweeGetallen(), GetalTwee=new TweeGetallen(); public MijnFrame11() {super(); setBounds(5,5,400,350);
setVisible(true);
one=new Thread( new Runnable() {public void run() {for(int i=1;i<10;i++) GetalEen.stortenhaalaf(getGraphics(),50); }}); one.start(); two=new Thread( new Runnable() {public void run() {for(int i=1;i<10;i++) GetalEen.stortenhaalaf(getGraphics(),100); }}); two.start(); three=new Thread( new Runnable() {public void run() {for(int i=1;i<10;i++) GetalTwee.stortenhaalaf(getGraphics(),200); }}); three.start(); four=new Thread( new Runnable() {public void run() {for(int i=1;i<10;i++) GetalTwee.stortenhaalaf(getGraphics(),250); }}); four.start(); addWindowListener(new WindowAdapter() {public void windowClosing(WindowEvent e){System.exit(0);}}); } } public class Thr11{public static void main(String arg[]){new MijnFrame11();}}
Telkens je een synchronized routine oproept, zal heel het object worden gemonitored zodat geen enkele andere thread met dat object langs een gesynchronizeerde routine kan werken. Omdat het uitschrijven op scherm nu in de klasse TweeGetallen gebeurt, moeten we de waarde van getGraphics en ook de kolom waar moet geschreven worden doorgeven aan de oproep.
De ene thread wacht totdat de andere thread een teken geeft
Visuele Gebruikers Omgevingen: Swing 193
192/395
We hebben hier vier threads. We hebben maar één getal dat steeds door de vier threads wordt aangepast. De getallen die we in de schermafdruk hierboven zien, zijn waarden van dezelfde veranderlijke (stelt bijvoorbeeld een bankrekening voor). Twee threads (aangeduid op schermafdruk met ++) willen steeds 1 frank op een rekening zetten. Twee andere threads (aangeduid op schermafdruk met --) willen steeds 1 frank van een rekening halen. We weten echter niet welke thread eerst zal beginnen. Het zou kunnen dat de threads die geld afhalen meer in het begin aan bod komen, zodat de rekening negatief wordt. Stel dat dit niet zou mogen? Hoe kunnen we er nu voor zorgen dat de rekening nooit negatief wordt? Dit kan alleen maar indien de threads die geld van de rekening willen halen, zullen moeten wachten totdat de rekening positief is. Er zijn verschillende mogelijkheden om dit te bekomen.
Versie waarbij er geen voorwaarde wordt afgedwongen Hierbij kan de waarde van de rekening dus negatief worden (zie afdruk hierboven). class Getallen {public int x=0,rij=15; } class MijnFrame12 extends JFrame
Visuele Gebruikers Omgevingen: Swing 194
193/395
{Thread one,two,three,four; Getallen Getal=new Getallen(); public MijnFrame12() {super(); setBounds(5,5,400,600);
setVisible(true);
one=new Thread( new Runnable() {public void run() {for(int i=1;i<10;i++) {Getal.x--; getGraphics().drawString("--( "+Getal.x+" )--", 50,Getal.rij+=15); } }}); one.start(); two=new Thread( new Runnable() {public void run() {for(int i=1;i<10;i++) {Getal.x++; getGraphics().drawString("++( "+Getal.x+" )++", 100,Getal.rij+=15); } }}); two.start(); three=new Thread( new Runnable() {public void run() {for(int i=1;i<10;i++) {Getal.x--; getGraphics().drawString("--( "+Getal.x+" )—- " ,200,Getal.rij+=15); } }}); three.start(); four=new Thread( new Runnable() {public void run() {for(int i=1;i<10;i++) {Getal.x++; getGraphics().drawString("++( "+Getal.x+" )++", 250,Getal.rij+=15); } }}); four.start(); addWindowListener(new WindowAdapter() {public void windowClosing(WindowEvent e){System.exit(0);}}); } } public class Thr12 {public static void main(String arg[]){new MijnFrame12();}}
Versie waarbij een thread gaat slapen totdat conditie vervuld is De eenvoudigste wijze om te wachten is de threads die geld willen afhalen een tijdje te laten slapen totdat het bedrag positief is. Dit pollen is wel niet efficiënt. Het zou beter zijn dat de thread die verhoogt verwittigd, wanneer het bedrag positief geworden is. Als een thread even gaat slapen, zal er ‘sleep’ op het scherm worden getoond. Indien thread een of drie als waarde voor Getal.x gelijk aan nul heeft, zal deze thread wachten totdat deze voorwaarde niet meer geldt. Dan wordt de while lus verlaten en zal Getal.x—kunnen uitgevoerd worden.
Visuele Gebruikers Omgevingen: Swing 195
194/395
class Getallen {public int x=0,rij=15; } class MijnFrame13 extends JFrame {Thread one,two,three,four; Getallen Getal=new Getallen(); public MijnFrame13() {super(); setBounds(5,5,400,600);
setVisible(true);
one=new Thread( new Runnable() {public void run() {for(int i=1;i<10;i++) {while(Getal.x<=0) {try{getGraphics().drawString("sleep",50,Getal.rij+=15); Thread.sleep(50); }catch(InterruptedException e) {System.out.println(e);} } Getal.x--; getGraphics().drawString("--( "+Getal.x+" )--", 50,Getal.rij+=15);
Visuele Gebruikers Omgevingen: Swing 196
195/395
} }}); one.start(); two=new Thread( new Runnable() {public void run() {for(int i=1;i<10;i++) {Getal.x++; getGraphics().drawString("++( "+Getal.x+" )++", 100,Getal.rij+=15); }}}); two.start(); three=new Thread( new Runnable() {public void run() {for(int i=1;i<10;i++) {while(Getal.x<=0) {try{getGraphics().drawString("sleep",200,Getal.rij+=15); Thread.sleep(50); }catch(InterruptedException e) {System.out.println(e);} } Getal.x--; getGraphics().drawString("--( "+Getal.x+" )--", 200,Getal.rij+=15); } }}); three.start(); four=new Thread( new Runnable() {public void run() {for(int i=1;i<10;i++) {Getal.x++; getGraphics().drawString("++( "+Getal.x+" )++", 250,Getal.rij+=15); } }}); four.start(); addWindowListener(new WindowAdapter() {public void windowClosing(WindowEvent e){System.exit(0);}}); }} public class Thr13 {public static void main(String arg[]){new MijnFrame13();}} Hierbij zal het proces dus af en toe wakker worden om te kijken of het verder mag gaan. Beter zou zijn dat het proces wakker gemaakt wordt door een ander proces indien de conditie voldaan is.
Visuele Gebruikers Omgevingen: Swing 197
196/395
Zichzelf in een wachttoestand brengen Het is veel efficiënter dat een thread zichzelf in wait zet, wanneer de conditie van een bepaald object niet goed is. Dit gebeurt wel per object waarvoor de conditie niet goed is. In dit programma wordt er gesynchronizeerd op het object Getal. Getal.wait() moet binnen dit synchronized blok uitgevoerd worden. Het bijhorend proces zal dan in een soort slaap toestand terechtkomen (en de monitor-vlag wordt tijdelijk opgeheven) en kan maar wakker gemaakt worden door een ander proces dat ook tijdens de uitvoering van een gesynchroniseerd blok (op dezelfde veranderlijke, hier Getal) de instructie Getal.notify() uitvoert. In het volgende programma, wordt er dus met een synchonized blok code gewerkt. Dit blok zal een monitor op een bepaald object zetten. wait instructies mogen alleen maar uitgevoerd worden vanuit gemonitored blok. De wait en notify instructie behoren tot de klasse Object. U kunt dus threads laten wachten op gelijk welk object totdat een andere thread de conditie voor dat bepaald object goed gezet heeft en dan een notify voor datzelfde object doet.
class Getallen {public int x=0,rij=15; }
Visuele Gebruikers Omgevingen: Swing 198
197/395
class MijnFrame14 extends JFrame {Thread one,two,three,four; Getallen Getal=new Getallen(); public MijnFrame14() {super(); setBounds(5,5,400,600);
setVisible(true);
one=new Thread( new Runnable() {public void run() {for(int i=1;i<10;i++) synchronized(Getal) {if(Getal.x<=0) {try{getGraphics().drawString("wait",50,Getal.rij+=15); Getal.wait(); }catch(InterruptedException e) {System.out.println(e);} } Getal.x--; getGraphics().drawString("--( "+Getal.x+" )--", 50,Getal.rij+=15); } }}); one.start(); two=new Thread( new Runnable() {public void run() {for(int i=1;i<10;i++) synchronized(Getal) {Getal.x++; if(Getal.x>0) { Getal.notify();} getGraphics().drawString("++( "+Getal.x+" )++", 100,Getal.rij+=15); }}}); two.start(); three=new Thread( new Runnable() {public void run() {for(int i=1;i<10;i++) synchronized(Getal) {if(Getal.x<=0) {try{getGraphics().drawString("wait",200,Getal.rij+=15); Getal.wait(); }catch(InterruptedException e) {System.out.println(e);} } Getal.x--; getGraphics().drawString("--( "+Getal.x+" )--", 200,Getal.rij+=15); } }}); three.start(); four=new Thread( new Runnable() {public void run() {for(int i=1;i<10;i++) synchronized(Getal) {Getal.x++; if(Getal.x>0) { Getal.notify();} getGraphics().drawString("++( "+Getal.x+" )++", 250,Getal.rij+=15); } }}); four.start(); addWindowListener(new WindowAdapter() {public void windowClosing(WindowEvent e){System.exit(0);}}); }} public class Thr14 {public static void main(String arg[]){new MijnFrame14();}}
Visuele Gebruikers Omgevingen: Swing 199
198/395
Threads wachten (wait) dus voor een bepaald object (hier Getal). Andere threads kunnen voor dat object de wachtende threads verwittigen dat de conditie vervuld is. Omdat we in bovenstaande klasse een synchronized blok gebruiken zullen alle instructie van dit blok on-onderbroken worden uitgevoerd.
Wait en notify voor alle objecten van een klasse In het volgend programma zit nog een klein probleempje verscholen (dit zat ook al in vorige versies): meerdere threads kunnen nog terzelfdertijd de rij aanpassen. Normaal gezien worden rijen beschreven van boven naar onder. Omdat sommige threads terwijl u aan het schrijven bent, de rij aanpassen,worden rijen die meer naar onder komen soms voor rijen die meer naar boven komen gedrukt. Oplossing: verhogen van rij moet ook in een synchronizede routine komen. class Getallen {public int x=0,rij=15; public synchronized void verhoog() {x++; if(x>0)this.notify(); } public synchronized void verlaag(Graphics gg,int kol) {if( x<=0) {try{gg.drawString("wait",kol, rij+=15); this.wait(); }catch(InterruptedException e) {System.out.println(e);} } x--; } public synchronized int waarde(){return x;} } Weeral synchroniseren we op het algemeen object ‘this’. Zodoende geldt de synchronisatie later voor alle objecten van de klasse. Voor wait en notify krijgen we this.notify() en this.wait() voor het concrete object. class MijnFrame15 extends JFrame {Thread one,two,three,four; Getallen Getal=new Getallen(); public MijnFrame15() {super(); setBounds(5,5,400,600);
setVisible(true);
one=new Thread( new Runnable() {public void run() {for(int i=1;i<10;i++) {Getal.verlaag(getGraphics(),50); getGraphics().drawString("--( "+Getal.waarde()+" )--", 50,Getal.rij+=15); } }}); one.start(); two=new Thread( new Runnable() {public void run() {for(int i=1;i<10;i++) {Getal.verhoog(); getGraphics().drawString("++( "+Getal.waarde()+" )++", 100,Getal.rij+=15);
Visuele Gebruikers Omgevingen: Swing 200
199/395
}}}); two.start(); three=new Thread( new Runnable() {public void run() {for(int i=1;i<10;i++) {Getal.verlaag(getGraphics(),200); getGraphics().drawString("--( "+Getal.waarde()+" )--", 200,Getal.rij+=15); } }}); three.start(); four=new Thread( new Runnable() {public void run() {for(int i=1;i<10;i++) {Getal.verhoog(); getGraphics().drawString("++( "+Getal.waarde()+" )++", 250,Getal.rij+=15); }}}); four.start(); addWindowListener(new WindowAdapter() {public void windowClosing(WindowEvent e){System.exit(0);}}); }} public class Thr15 {public static void main(String arg[]){new MijnFrame15();}}
De klasse wordt nu ook erg overzichtelijk.
Visuele Gebruikers Omgevingen: Swing 201
200/395
Voorbeelden Threads : meerdere tekeningen die tergelijkertijd bewegen
class MijnPanel extends JPanel {Thread one; int t=10,t2=10,aanpassing=1,aanpassing2=1;boolean sw=true; Color coll;int time; public MijnPanel(int tt,Color col,int timee) {t=tt;coll=col;time=timee; setVisible(true); setPreferredSize(new Dimension(100,100)); setSize(100,100); setBorder(BorderFactory.createRaisedBevelBorder()); one=new Thread(new Runnable() {public void run() {for(;;) {try{Thread.sleep(time); }catch(InterruptedException e){e.printStackTrace();} Graphics g=MijnPanel.this.getGraphics(); g.setXORMode(coll); XOR mode is handig. Als we een lijn t+=aanpassing;t2+=aanpassing2; g.setColor(Color.green); tekenen en daarna er terug een lijn g.fillRect(9,9,t,t2); overtekenen, dan zal het snijpunt in de // als we aan de rand komen moeten we van richting veranderen if(t>MijnPanel.this.getWidth()-20)aanpassing=-1; if(t<2)aanpassing=1; if(t2>MijnPanel.this.getHeight()-20)aanpassing2=-1; if(t2<2)aanpassing2=1; } } }); } public void start(){ one.start(); } public void paintComponent(Graphics g) {super.paintComponent(g); if(sw) g.setColor(Color.blue);else g.setColor(Color.red); sw=!sw; g.fillRect(0,0,200,400); } } public class Thr16 extends JFrame
Visuele Gebruikers Omgevingen: Swing 202
201/395
{MijnPanel a,b,c,d,e,f; public Thr16() {Container xc=getContentPane(); xc.setLayout(new GridLayout(3,2)); xc.add(a=new MijnPanel(10,Color.yellow,90)); xc.add(d=new MijnPanel(30,Color.cyan,65)); xc.add(b=new MijnPanel(40,Color.black,80)); xc.add(c=new MijnPanel(60,Color.white,40)); xc.add(e=new MijnPanel(40,Color.blue,55)); xc.add(f=new MijnPanel(70,Color.red,70)); a.start();d.start();b.start();e.start();c.start();f.start(); setSize(630,320); setVisible(true); addWindowListener(new WindowAdapter() {public void windowClosing(WindowEvent ee){System.exit(0);}}); } public static void main(String args[]) {new Thr16(); }} Een woordje uitleg bij de XORmode. Waarvoor dient het? Stel dat de gebruiker een rechthoek tekent met de muis. Telkens hij de muis beweegt, moet de rechthoek mee vergroten en moet de oude rechthoek verdwijnen. Dit gebeurt met de XORmode. U werkt met bijvoorbeeld een witte achtergrond en tekent een zwarte lijn. Bij setXORmode(Color.white) en het terug tekenen van dezelfde lijn op dezelfde plaats, zal de lijn terug weg zijn. (vervangen door wit). Wat ook de achtergrond is: een tekening of zo, als u er een rechthoek op tekent, zult u deze rechthoek steeds zien (kleuren worden geXORed). Tekent u nogeens dezelfde rechthoek, dan zal de oorspronkelijke tekening er onbeschadigd terug staan.
Wanneer zijn threads echt nuttig? Stel dat een programma twee listboxen moet opvullen uitgaande van twee files. Normaal gebeurt het opvullen één na één : eerst de eerste file, dan pas de tweede file. In de eerste versie van het programma gebeurt dit zo. U merkt dat de tweede listbox nog leeg is, omdat de eerste listbox nog niet vol is. (Het lezen uit een file wordt hier gesimuleerd met een klasse Dokument waarmee we met getLine een lijn van het document opvragen. In de routine getLine wordt ervoor gezorgd dat deze routine traag is en dat er steeds een andere lijn wordt gegenereerd.
import java.awt.*; import javax.swing.*; import java.awt.event.*; // zijn thread wel nuttig? Stel dat u een document moet lezen // en dat het genereren van elke lijn 1 seconde duurt. // Wel erg indien u 100 lijnen moet lezen, niet? // De classe Document simuleert dit: class Dokument {int i=0;String tekst; public Dokument(String tekstt){tekst=tekstt;} public String getLine()
Visuele Gebruikers Omgevingen: Swing 203
202/395
{i++; try {Thread.sleep(300);// de gebruiker moet wachten op deze lijn }catch(InterruptedException e){System.out.println(e);} return "......lijn "+i+"......."+tekst; } } class ScrollDoorDocument extends JFrame {DefaultListModel defListMod=new DefaultListModel(), defListMod2=new DefaultListModel(); JList list,list2; Dokument dok,dok2; public ScrollDoorDocument() {Container c=getContentPane(); c.setLayout(new GridLayout(1,2)); list=new JList(defListMod); list2=new JList(defListMod2); setVisible(true);setSize(500,300); c.add(new JScrollPane(list)); c.add(new JScrollPane(list2)); pack(); dok=new Dokument("eerste document"); for(int i=0;i<100;i++) defListMod.addElement( dok.getLine() ); dok2=new Dokument("tweede document"); for(int j=0;j<100;j++) defListMod2.addElement( dok2.getLine() ); // ALS HIER NOG CODE ZOU STAAN MOETEN WE LANG WACHTEN VOORDAT DIE // UITGEVOERD ZOU WORDEN. } } public class Thr17 {static public void main(String []args) {new ScrollDoorDocument().addWindowListener( new WindowAdapter() {public void windowClosing(WindowEvent ee){System.exit(0);} }); }} Door het laden van de twee listboxen in twee aparte threads te laten gebeuren, worden de twee listboxen onafhankelijk van elkaar opgevuld. Ook moet het programma niet wachten totdat de twee listboxen opgevuld zijn om verder te gaan. (Achter de code die de twee listboxen opvuld, kan nog andere kode staan.) Bij servers wordt ook dikwijls met threads gewerkt. Stel dat meerdere gebruikers terzelfdertijd een trage bewerking van de server vragen, dan zou de tweede klant van de server moeten wachten totdat de eerste klant gedaan heeft. Bij server wordt daarom steeds een aparte thread opgestart die de transacties van één klant zal behandelen.
In volgende aangepaste versie, worden de twee listboxen terzelfdertijd opgevuld.
Visuele Gebruikers Omgevingen: Swing 204
203/395
class Dokumentje {int i=0;String tekst; public Dokumentje(String tekstt){tekst=tekstt;} public String getLine() {i++; try {Thread.sleep(300); }catch(InterruptedException e){System.out.println(e);} return "......lijn "+i+"......."+tekst; } } class ScrollDoorDocumentje extends JFrame {DefaultListModel defListMod=new DefaultListModel(), defListMod2=new DefaultListModel(); JList list,list2; Dokumentje dok,dok2; public ScrollDoorDocumentje() {Container c=getContentPane(); c.setLayout(new GridLayout(1,2)); list=new JList(defListMod); list2=new JList(defListMod2); setVisible(true);setSize(500,300); c.add(new JScrollPane(list)); c.add(new JScrollPane(list2)); pack(); new Thread(new Runnable() {public void run() {dok=new Dokumentje("eerste document"); for(int i=0;i<100;i++) defListMod.addElement( dok.getLine() ); }).start();
}
new Thread(new Runnable() {public void run() {dok2=new Dokumentje("tweede document"); for(int j=0;j<100;j++) defListMod2.addElement( dok2.getLine() ); } }).start(); }} public class Thr18 {static public void main(String []args) {new ScrollDoorDocumentje().addWindowListener( new WindowAdapter() {public void windowClosing(WindowEvent ee){System.exit(0);} }); }}
Laatste berichten over threadsave tekenen SWING "URBAN LEGENDS" Developers who work with Swing components often hear about certain ways of doing things that they assume are the right ways to work with Swing. Like "urban legends" that purport to be accounts of actual
Visuele Gebruikers Omgevingen: Swing 205
204/395
events but never really happened, some of these Swing techniques are incorrect. In this tip, you'll learn about a number of these Swing urban legends -- approaches that will hinder the performance of your Swing applications. In some cases, the performance reduction might only be nanoseconds, but if you want the best performance profile, even cutting a handful of nanoseconds adds up over time. Here are three Swing urban legends:
• • •
Create threads for long tasks from the event dispatch thread. Use SwingUtilities for running tasks on the event dispatch thread. Synchronize methods for synchronization.
Create Threads for Long Tasks From the Event Dispatch Thread Here's the situation: you want to spin off a thread from the event queue to do a long task. If your event handler needs to do a long task, you don't want to block the event thread. So you create a new thread for the long task, and call invokeLater() when the task is done to handle the results on the event thread. Here's the typical usage pattern:
public void actionPerformed(ActionEvent e) { Runnable longTask = new Runnable() { public void run() { // Run task to do long stuff ... // long task // Update Swing component when done Runnable awtTask = new Runnable() { public void run() { // Update Swing component } } EventQueue.invokeLater(awtTask); } }; Thread t = new Thread(longTask); t.start(); } This is a common pattern: run long tasks off the event dispatch thread, then update the Swing component after the long task is done. It seems like the right thing to do, but it isn't. If you follow this pattern, you'll run across a problem that could cause your program to behave badly. When a new thread is created, it retains the thread priority of the creating thread. Because the event thread typically runs at a higher level than normal threads, threads created from the event thread inherit the higher priority. Here is a simple program, Threads, that demonstrates the thread priorities:
import java.awt.*; public class Threads { public static void main(String args[]) { System.out.println("Main Thread priority: " + Thread.currentThread().getPriority()); Runnable runner = new Runnable() { public void run() { System.out.println("Event Thread priority: " + Thread.currentThread().getPriority()); } }; EventQueue.invokeLater(runner); } } If you run Threads, you'll see that the main thread has a priority of 5, and the event thread runs at a priority 6.
>> java Threads
Visuele Gebruikers Omgevingen: Swing 206
205/395
Main Thread priority: 5 Event Thread priority: 6 The higher priority for the event thread is desirable. You want your user interfaces to be responsive. But, you don't want to extend that higher priority to non-event processing tasks. So be sure to lower the priority of user-created threads initialized from the event dispatch thread. This means: Change:
Thread t = new Thread(longTask); t.start(); To:
Thread t = new Thread(longTask); t.setPriority(Thread.NORM_PRIORITY); t.start(); Threads created with this new priority will not compete for processing time with the event dispatch thread. If there is something to run on the event dispatch thread, it will win -- not the worker thread. You might consider creating a WorkerThread class for just this purpose. That way you won't have to keep calling setPriority() for all new threads created from the event dispatch thread, or use a thread pool through the following new classes in the java.util.concurrent package:
•
Executors.newCachedThreadPool() For a thread pool with unbound size
•
Executors.newFixedThreadPool(int size) For a thread pool of fixed size > 1
•
Executors.newSingleThreadExecutor() For a thread pool of fixed size = 1
Executor was added to the standard libraries with JDK 5.0. Use SwingUtilities For Running Tasks on the Event Dispatch Thread This isn't really an urban legend, but rather an explanation of the use of the EventQueue class in the first legend. Many people are familiar with the SwingUtilities class for the use of invokeLater() and invokeAndWait(). Where did this EventQueue class come from? The answer is that all these methods in SwingUtilities wrap calls to the same methods of the EventQueue class in the java.awt package. In other words, there is a level of indirection of the method calls when used through SwingUtilities. There is technically nothing wrong with using the methods. It's just that you can avoid the indirection by using the EventQueue methods directly. Note that another method that wraps access to the EventQueue class is isEventDispatchThread(). This is used to check if the current task is running on the event dispatch thread. If the SwingUtilities methods are just wrapper methods, why do they exist? When the Swing components became available with JDK 1.2, Sun released a version that worked with JDK 1.1. All the Swing bits needed to be self-contained. In other words, the Swing classes couldn't use anything that was introduced to JDK 1.2. For that reason, SwingUtilities contains the methods for invokeLater and invokeAndWait. Since those methods simply pass along the method calls to EventQueue, you should call the EventQueue methods directly. Synchronize Methods for Synchronization Another commonly-seen practice that hinders performance involves the use of the synchronized keyword. It is common to synchronize methods to prevent simultaneous execution. In many cases, this approach is fine. When might having synchronized methods be bad and slow down performance? When the class is a subclass of an AWT or Swing component, specifically any subclass of java.awt.Component. What's wrong with synchronizing methods in subclasses of Component? If you are only trying to
Visuele Gebruikers Omgevingen: Swing 207
206/395
synchronize access to your methods, having a synchronized method means you are competing with all the other synchronized methods of Component. This causes your method to be blocked when it shouldn't, and other Component methods to be blocked when they shouldn't. In fact, this could also lead to unexpected deadlocks. Instead of synchronizing at the method level, you can create a lock variable that is shared by the methods that need to be synchronized. Here's an example:
public class Foo extends JComponent { private final Object lock = new Object(); private final char[] chars; public void setMethod(String value) { synchronized(lock) { // save off value as chars chars = value.toCharArray(); } } public String getMethod() { synchronized(lock) { // regenerate saved value from chars String savedValue = new String(chars); return savedValue; } } }
meer details over performantie: http://java.sun.com/developer/community/chat/JavaLive/2005/jl0215.html
Visuele Gebruikers Omgevingen: Swing 208
207/395
Innerspection/Reflection De klasse Object, waarvan elk java object impliciet overerft heeft de methode getClass(). Deze methode geeft een object van de klasse Class. In dit object zit alle informatie van het de klasse die hoort bij het object. Je kunt vragen: - De naam van de klasse o String GetName() - welke variabelen er in de klasse zitten o Field [] getDeclaredFields Het resultaat is een array van Field objecten die informatie van de velden bevat (naam, type) o Field getDeclaredField(String naamveld) Zoek het Field object dat als naam ‘naamveld’ heeft - welke variabelen er overgeërfd worden van superklassen, samen met eigen gedefinieerde veranderlijken o Field [] getFields() - welke methoden de klasse heeft o Method [] getDeclaredMethods() Het resultaat is een array van Method objecten. Deze methoden kunnen later opgeroepen worden (en dit voor een willekeurige onbekende klasse). o Method getDeclaredMethod(String naammethode) Zoekt naar methode met een bepaalde naam - welke methoden de klasse overerfd o Method [] getMethods() - welke constructoren de klasse heeft o Constructor [] getConstructors() Geeft alle constructoren van deze klasse terug o getConstructor(Class [] parameters) constructor opvragen die bepaalde types van parameters heeft. Merk op dat types van parameters worden voorgesteld door een array van Class’es - welke interne klassen er werden gedefiniëerd o Class [] getDeclaredClasses() Het resultaat van deze functie is een array van Class objecten - Van welke klassen er wordt overgeërfd o GetClasses - De interfaces die de klasse implementeert o Class [] getInterfaces() - Nagaan tot welk package de klasse behoort o Package getPackage() Binnenin een willekeurig object kunnen gaan kijken, wordt innerspection of reflection genoemd. Daarnaast zijn er nog twee andere speciale methodes. static Class forName(String className) Returns the Class object associated with the class or interface with
the given string name.
Visuele Gebruikers Omgevingen: Swing 209
208/395
‘forName’ is een beetje een misleidende naam. Het is een soort zoek methode. Het is een static methode. Ze wordt dus opgeroepen als volgt: Class c=Class.forName(“be.khleuven.rega.ti2.beans.Test”); Deze oproep zal de klasse Test.class op schijf gaan zoeken of in de jars die in het classpath staan gaan zoeken. Indien gevonden, zal de bijhorende Class object gemaakt worden. Object newInstance() Creates a new instance of the class represented by this Class object. Hiermee kunnen objecten gemaakt worden van klassen waarvan u op voorhand (voor de uitvoering van het programma de naam niet hoeft te weten). String naamklasse; … laat gebruiker naam van klasse opgeven…. Class c=Class.forName(naamvanklasse); Object o=c.newInstance(); ’o’ bevat na de oproep van newInstance de referentie naar een klasse waarvan de gebruiker de naam heeft opgegeven.
Voorbeeld waarbij de gebruiker willekeurige klassen kan maken In de volgende interactieve toepassing kan de gebruiker onderaan het scherm de naam van een willekeurige klasse ingeven. In de schermafdruk werd ‘java.lang.String’ genomen. U merkt dat u de volledige naam van de klasse moet opgeven. Een object van die klasse zal aangemaakt worden.
U kunt ook eens bijvoorbeeld javax.swing.JFrame intypen. U zult merken dat er een nieuw (leeg) frame bij op uw scherm verschijnt. Het programma maakt immers een object van die klasse aan. Hierbij ziet u echt het visueel resultaat.
Visuele Gebruikers Omgevingen: Swing 210
209/395
De toepassing heeft ook twee lijsten. De linker lijst zal de namen van alle methode van de door de gebruiker opgegeven klasse tonen. De rechter lijst zal de namen van de velden van de door de gebruiker opgegeven klasse tonen. Voor javax.swing.JFrame krijgen we een heleboel methodes en velden:
Het programma gaat ook automatisch alle methodes oproepen die eenvoudig op te roepen zijn: methodes die geen parameters hebben en ook een eenvoudig resultaat hebben: vb een String. Het resultaat van deze oproepen zal verschijnen in het msdos venster.
import import import import
java.lang.reflect.*; java.awt.*; java.awt.event.*; javax.swing.*;
class MijnFrame extends JFrame {JTextField field; JLabel lab; JList list,list2; DefaultListModel dlm=new DefaultListModel(); DefaultListModel dlm2=new DefaultListModel();
Visuele Gebruikers Omgevingen: Swing 211
210/395
public MijnFrame() {setVisible(true); Container c=getContentPane(); lab=new JLabel("geef naam van een klasse om dynamisch te laden"); field=new JTextField(20); field.setText("naam klasse uit huidige dir of gewone Java klasse"); list=new JList(dlm);// methode van klasse list2=new JList(dlm2);//velden van klasse c.setLayout(new BorderLayout()); JPanel pan=new JPanel(); pan.setLayout(new GridLayout(4,1)); pan.add(lab); pan.add(new JLabel("tevens worden routines die een String teruggeven")); pan.add(new JLabel("en die geen parameters hebben opgeroepen")); c.add(pan,BorderLayout.NORTH); c.add(field,BorderLayout.SOUTH); c.add(new JScrollPane(list),BorderLayout.CENTER); c.add(new JScrollPane(list2),BorderLayout.EAST); forname : hiermee kan je pack();setSize(300,150); een willekeurige klasse field.addActionListener(new ActionListener() van schijf halen. {public void actionPerformed(ActionEvent ee) {try{// we laden klasse informatie over de klasse Class klasseVanSchijf = Class.forName(field.getText()); // we maken een object van de gevraagde klasse Object objectVanKlasseVanSchijf= klasseVanSchijf.newInstance(); //default constructor wordt gebruikt Met newInstance maak je een nieuw object van de willekeurige klasse // welke routines heeft de klasse? Method[] reflectedMethods = klasseVanSchijf.getMethods(); // listem:list all methods that were not inherited from Object We lopen alle methoden //(anders krijgen we er teveel) van klasse af en voegen dlm.clear();// we maken lijst leeg toe aan listbox for (int i = 0; i < reflectedMethods.length; i++) {if (reflectedMethods[i].getDeclaringClass() != Class.forName("java.lang.Object")) dlm.addElement(reflectedMethods[i]);}
Object retVal; // callem: call all methods that return Strings and take no args for (int i = 0; i < reflectedMethods.length; i++) {if ((reflectedMethods[i].getReturnType() == Class.forName("java.lang.String")) &&
Visuele Gebruikers Omgevingen: Swing 212
211/395
(reflectedMethods[i].getParameterTypes().length ==0) ) {retVal = // we roepen de methode eindelijk op: reflectedMethods[i].invoke(objectVanKlasseVanSchijf, null); System.out.println("Method: " + reflectedMethods[i] + ", returned: [" + retVal + "]"); } if (reflectedMethods[i].getName().equals("show") &&reflectedMethods[i].getParameterTypes().length == 0 ) {retVal = // we roepen show op: we zien de component reflectedMethods[i].invoke(objectVanKlasseVanSchijf, null); } } // we vragen alle velden van klasse Field[] reflectedFields = klasseVanSchijf.getFields(); dlm2.clear();//lijst op scherm leeg maken Object fieldValue; // getem for instance for (int i = 0; i < reflectedFields.length; i++) { fieldValue = reflectedFields[i].get(objectVanKlasseVanSchijf); dlm2.addElement("Field[" + i + "]: " + reflectedFields[i] + ", Value = [" + fieldValue + "]"); System.out.println("Field[" + i + "]: " + reflectedFields[i] + ", Value = [" + fieldValue + "]"); } } catch (Exception e) {System.out.println(e.getMessage()); } } }); } } class Reflect01 {static public void main(String args[]) {new MijnFrame().addWindowListener(new WindowAdapter() {public void windowClosing(WindowEvent e){System.exit(0);} }); }}
Visuele Gebruikers Omgevingen: Swing 213
212/395
Toelichting bij het programma Laden van de methodes van de klasse in lijst1 for (int i = 0; i < reflectedMethods.length; i++) {if (reflectedMethods[i].getDeclaringClass() != Class.forName("java.lang.Object")) dlm.addElement(reflectedMethods[i]);} De array reflectedMethods bevat alle Method objecten die bij de klasse horen die de gebruiker ingetypt heeft. Maar deze array bevat ook de methode die overgeërfd werden van bijvoorbeeld de klasse Object. De methodes van de klasse Object gaan we liefst niet elke keer gaan tonen. Daarom testen we of de klasse die bij de methode hoort (reflectedMethods[i].getDeclaringClass() ) wel verschillend is van de klasse Object ( Class.forName("java.lang.Object"))). We testen of twee Class objects wel verschillend zijn.
Methoden zoeken die geen argumenten he bben en die als resultaat een String hebben {if ((reflectedMethods[i].getReturnType() == Class.forName("java.lang.String")) && (reflectedMethods[i].getParameterTypes().length ==0) ) getReturnType geeft een Class object terug dat het type van het resultaat van de methode voorsteld. Dit moet gelijk zijn aan het type van java.lang.String (Class.forName("java.lang.String")). GetParameterTypes() geeft een array van Class’es terug van alle parameters. Als de lengte van deze array 0 is, zijn er geen parameters.
Een willekeurige methode oproepen retVal = reflectedMethods[i].invoke(objectVanKlasseVanSchijf, null); Dit is een rare wijze om een methode op te roepen. Methodes worden normaal als volgt opgeroepen: Naamobject ‘punt’ naamroutine ( parameters ) Vb. object.wijzig(“piet”,5);
De naam van het object is daarbij een veranderlijke en de naam van de methode ligt vast. In ons programma wordt zowel de methode als het object waarop deze methode moet worden toegepast, voorgesteld door een veranderlijke. We kunnen maar één veranderlijke voor het punt schrijven. Voor het punt komt aldus reflectedMethods[i]. Dit object stelt de methode voor die we zullen oproepen met behulp van ‘invoke’. We hebben twee parameters:
Visuele Gebruikers Omgevingen: Swing 214
213/395
-
het object waarop de methode moet toegepast worden (hier objectVanKlasseVanSchijf) de array van objecten die de parameters voorstellen die meegegeven moeten worden aan de methode die opgeroepen wordt (hier null).
Waarden van veranderlijken van de willekeurige klasse opvragen fieldValue = reflectedFields[i].get(objectVanKlasseVanSchijf); De veranderlijke waarvan we waarde willen opvragen wordt voorgesteld door reflectedFields[i]. Het object waarvan we de veranderlijke ‘reflectedFields[i]’ willen opvragen noemt, ‘objectVanKlasseVanSchijf’. In een normaal programma vraagt u de waarde van de veranderlijke op door: Waarde = object.veranderlijke Weeral omdat zowel object als veranderlijke door een variabele worden voorgesteld in ons programma en we maar één veranderlijke voor het punt kunnen plaatsen, komen we tot de volgende schrijfwijze: fieldValue = reflectedFields[i].get(objectVanKlasseVanSchijf); De inhoud van de veranderlijke reflectedFields[i] van de veranderlijke objectVanKlasseVanSchijf wordt opgevraagd.
Visuele Gebruikers Omgevingen: Swing 215
214/395
Serialization- Persistentie Soms komt het voor u een object in zijn geheel moet wegschrijven naar schijf om het persistent te maken. Het object wordt dan persistent naar schijf geschreven. Eenvoudig is deze taak niet omdat het object ook referenties naar andere objecten kan bevatten. Die moeten dan ook gesaved worden. We laten twee verschillende aanpakken aan bod komen. De eerste aanpak bestaat erin om zelf alle data van een object naar een bestand in binair formaat te schrijven. De tweede aanpak zal eruit bestaan om het werk over te laten aan java. Door middel van innerspection kan java zelf alle data die moet gesaved worden opzoeken en wegschrijven naar schijf.
Zelf saven van gehele objecten U kunt gelijk welk object zelf saven door alle velden één voor één ervan weg te schrijven naar een DataOutputStream. Deze klasse heeft methodes om integers, boolean, doubles, floats,… enzovoorts ongeconverteerd (in hexadecimaal formaat) weg te schrijven. Als je de DataOutputStream verbindt met een FileOutputStream, dan worden ze uiteindelijk weggeschreven in een bestand. Het is natuurlijk zwaar om op deze wijze de inhoud van alle velden van een willekeurig object naar schrijf te schrijven. Java heeft de mogelijkheid tot innerspection: voor een willekeurig object kan gevraagd worden welke velden erin zitten en van welk type ze zijn. Hierdoor zou het mogelijk moeten zijn om een systeem te bouwen dat automatisch alle velden van een object gaat saven op schijf en daarna terug gaan herstellen. We kunnen slechts basis types zoals int,char wegschrijven naar een DataOutputStream. In het volgend programma schrijven we een getal ‘1111’ weg. Als we een object zoals een String willen wegschrijven, moeten we alle werk zelf doen: eerst de lengte van de tekst wegschrijven (“dit is tekst”.length()”) gevolgd door de tekst zelf (“dit is tekst”). Niet echt handig. Het programma gaat eerst enkele veranderlijken wegschrijven naar bestand en zal ze daarna vervolgens terug gaan lezen. import java.io.*; public class Stream01 { public static void p(String s){System.out.println(s);} public static void main(String args[]) throws IOException {FileOutputStream fos=new FileOutputStream("bytes.bin"); DataOutputStream dop =new DataOutputStream(fos); dop.writeInt(1111); dop.writeBoolean(true); dop.writeInt("dit is tekst".length() );//een string moeten we als rij
Visuele Gebruikers Omgevingen: Swing 216
215/395
dop.writeChars("dit is tekst"); //van characters + lengte saven dop.writeDouble(11.11); dop.close(); FileInputStream fis=new FileInputStream("bytes.bin"); DataInputStream dip =new DataInputStream(fis); int i;boolean b;int aantal;char letters[];double fl; i=dip.readInt();p(" int :"+i); b=dip.readBoolean();p(" boolean :"+b); aantal=dip.readInt();p(" aantal chars :"+aantal);//we lezen lengte rij letters=new char[aantal]; //we lezen rij van characters for(int j=0;j
Visuele Gebruikers Omgevingen: Swing 217
216/395
Serializatie aan java overlaten Java heeft perfect de mogelijkheid om objecten automatisch te saven. Door middel van innerspection (elk object heeft een Class object waarin alle veranderlijken, hun waarde, hun type zitten) kan java elk willekeurig object ontleden en wegschrijven. Wel is er nood aan een soepele wijze om aan te geven welke objecten wel en welke objecten niet automatisch moeten gesaved worden. Men heeft gekozen voor een eenvoudige oplossing: klassen die wel mogen gesaved worden worden aangeduid doordat ze de lege interface Serializable implementeren. Staat dus achter een klasse implements Serializable, dan zal de inhoud van de klasse (en overal waar objecten van die klasse gebruikt worden) mee gesaved worden als we naar schijf overbrengen. (java kan met behulp van de methode getInterfaces aan het Class object van elk object vragen of de interface Serializable vermeld werd voor de klasse van het object..) Serialization. Classen die de interface Serializable implementeren geven aan dat alle velden moeten gesaved worden als het object wordt De methode weggeschreven naar een ObjectOutputStream. writeObject van deze klasse ObjectOutputStream zal indien de klasse de interface Serializable implementeert, alle velden van het object aflopen en saven. Dit saven gebeurt zoveel niveau’s diep als nodig. In onderstaand voorbeeld saven we een object van de klasse TweeRecords die elk twee andere objecten bevatten (Record), die terug een object bevatten (HulpRecord). (drie niveau’s diep) Alles wordt automatisch goed gesaved. De interface Serializable is raar: er staat NIETS in. Het is een lege interface. Je moet dus geen enkele methode definiëren voor die interface. Waarvoor dient ze dan? Ze dient als flag, switch, markeerder om gewoon aan te geven dat alle velden van die klasse mogen gesaved worden. Saven van een object naar een file (persistent maken van het object) wordt erg eenvoudig: u schrijft het object met behulp van writeObject naar een ObjectOutputStream. Dat is alles. U schrijft meestal maar één object weg naar schijf. Dit object kan wel heel veel deelobjecten hebben, of een array van referenties naar andere objecten bevatten. Alles wordt fijn weggeschreven door gewoon het root record weg te schrijven. Terug lezen van een ObjectInputStream is een heel klein beetje ingewikkelder. Dit gebeurt met behulp van de methode readObject. Deze methode geeft natuurlijk steeds iets terug van het algemene type Object. U moet het resultaat van readObject gewoon converteren naar het type TweeRecords (het type van het object dat u weggeschreven hebt).
import java.io.*;
Visuele Gebruikers Omgevingen: Swing 218
217/395
class HulpRecord implements Serializable {int Int; String str; public HulpRecord(int i, String s) {Int=i;str=s;} public void druk() {System.out.println("HulpRecord: "+Int+" "+str);} } class Record implements Serializable {int Int; String str; HulpRecord hulp; public Record(int i, String s) {Int =i; str=s; int j=Int+1; hulp=new HulpRecord(j, str); } public void druk() {System.out.println("Record: "+Int+" "+str); hulp.druk(); } } class TweeRecords implements Serializable {Record een,twee; public TweeRecords(Record e,Record t) {een=e;twee=t;} public void druk(){een.druk();twee.druk();} } public class Stream02 { public static void p(String s){System.out.println(s);} public static void main(String args[]) throws IOException {FileOutputStream fos=new FileOutputStream("bytes.bin"); ObjectOutputStream dop =new ObjectOutputStream(fos); Record rec,rec2; rec=new Record(100,"eerste record"); rec2=new Record(200,"tweede record"); TweeRecords tw=new TweeRecords(rec,rec2); tw.druk(); dop.writeObject(tw); dop.close(); FileInputStream fis=new FileInputStream("bytes.bin"); ObjectInputStream dip =new ObjectInputStream(fis); TweeRecords tw2; try{//terug converteren naar juiste type tw2=(TweeRecords)dip.readObject(); System.out.println("van schijf:"); tw2.druk(); }catch(ClassNotFoundException e){System.out.println(e);} dip.close(); }} /* Record: 100 eerste record HulpRecord: 101 eerste record Record: 200 tweede record
Visuele Gebruikers Omgevingen: Swing 219
218/395
HulpRecord: 201 tweede record van schijf: Record: 100 HulpRecord: Record: 200 HulpRecord:
eerste record 101 eerste record tweede record 201 tweede record
in het bestand: wat is dat allemaal? (ook types worden bijgehouden). ¬í _sr TweeRecords¹Ù‘@_ðkŽ_ _L _eent _LRecord;L _tweeq ~ _xpsr _Recordª×¹‚ˆüdž_ _I _IntL _hulpt LHulpRecord;L _strt _Ljava/lang/String;xp dsr HulpRecord8ú_ €±¾À_ _I _IntL _strq ~ _xp et eerste recordq ~ sq ~ _ Èsq ~ _ Ét tweede recordq ~ */
Selectief delen van object automatisch laten saven. Sommige objecten bevatten tijdelijke tussenresultaten en moeten niet gesaved worden. Je kan op twee wijzen aangeven dat iets niet automatisch moet gesavede worden: -HulpRecord zal nu niet gesaved worden omdat het de interface Serializable niet implementeert. -Een veranderlijke waarvoor het woordje ‘transient’ wordt aangegeven, wordt nooit gesaved. (Hiermee kan je delen van een object selectief weglaten uit het save proces.) pas op: als je een object terug leest van schijf, moet je testen of er geen velden bijzijn die niet hersteld werden (transient velden). In onderstaand voorbeeld moet je bij het uitdrukken nagaan of hulp wel een waarde heeft Alle Swing Componenten zijn automatisch Serializable en kunnen eenvoudig gesaved worden. Je kan dus met één writeObject bevel een ingewikkelde JFrame even saven en later in zijn geheel terug herstellen. --------------------------------------------------------------------------=== import java.io.*; class HulpRecord {int Int; String str; public HulpRecord(int i, String s) {Int=i;str=s;} Visuele Gebruikers Omgevingen: Swing 220
219/395
public void druk(){System.out.println("HulpRecord: "+Int+" "+str);} } class Record implements Serializable {int Int; String str; transient HulpRecord hulp; // wordt niet gesaved door transient public Record(int i, String s) {Int =i; str=s; int j=Int+1; hulp=new HulpRecord(j, str); } public void druk() {System.out.println("Record: "+Int+" "+str); if(hulp!=null) hulp.druk(); } } class TweeRecords implements Serializable {Record een,twee; public TweeRecords(Record e,Record t) {een=e;twee=t;} public void druk(){een.druk();twee.druk();} } public class Stream03 { public static void p(String s){System.out.println(s);} public static void main(String args[]) throws IOException {FileOutputStream fos=new FileOutputStream("bytes.bin"); ObjectOutputStream dop =new ObjectOutputStream(fos); Record rec,rec2; rec=new Record(100,"eerste record"); rec2=new Record(200,"tweede record"); TweeRecords tw=new TweeRecords(rec,rec2); tw.druk(); dop.writeObject(tw); dop.close(); FileInputStream fis=new FileInputStream("bytes.bin"); ObjectInputStream dip =new ObjectInputStream(fis); TweeRecords tw2; try{ tw2=(TweeRecords)dip.readObject(); System.out.println("van schijf:"); tw2.druk(); }catch(ClassNotFoundException e){System.out.println(e);} dip.close(); } } /*
Visuele Gebruikers Omgevingen: Swing 221
220/395
Record: 100 HulpRecord: Record: 200 HulpRecord:
eerste record 101 eerste record tweede record 201 tweede record
van schijf: (geen hulprecord daar dit niet werd gesaved) Record: 100 eerste record Record: 200 tweede record */
Visuele Gebruikers Omgevingen: Swing 222
221/395
OO-2 Een flexibel rekenmachine Als we een ‘Rekenmachine’ inpluggen werken de bewerkingen ‘+’, ‘-‘, ‘*’, en ‘/’ voor gewone getallen.
public class Rekenmachine1b {public static void main(String args[]) {new GUI(new Rekenmachine()); }
}
Als we een ‘RekenmachineLong’ inpluggen, werkt de rekenmachine met getallen tot 100 cijfers. Alleen de bewerking ‘+’ werd voor deze demo geimplementeerd.
public class Rekenmachine1b {public static void main(String args[]) {new GUI(new RekenmachineLong()); }
}
Visuele Gebruikers Omgevingen: Swing 223
222/395
Als we een ‘RekenmachineBinaryLong’ inpluggen, werkt de rekenmachine met binaire getallen tot 100 cijfers. Alleen de bewerking ‘+’ werd voor deze demo geimplementeerd.
public class Rekenmachine1b {public static void main(String args[]) {new GUI(new RekenmachineBinaryLong()); } }
Als we een ‘RekenmachineOctaalLong’ inpluggen, werkt de rekenmachine met binaire getallen tot 100 cijfers. Alleen de bewerking ‘+’ werd voor deze demo geimplementeerd.
public class Rekenmachine1b {public static void main(String args[]) {new GUI(new RekenmachineOctaalLong()); } }
Om dit te bewerkstelligen hebben we de klasse JIntegerTextField wat aangepast. We zullen nu testen dat er geen ‘niet-digits’ worden ingetypt. (we kunnen niet meer testen of het geheel wel een integer is.) import javax.swing.*; import java.awt.event.*;
Visuele Gebruikers Omgevingen: Swing 224
223/395
import java.awt.*;
class JIntegerTextField extends JTextField { public JIntegerTextField() {super(); addKeyListener(new KeyAdapter() {public void keyReleased(KeyEvent e) {if( isGetal()) setBackground(Color.white); else setBackground(Color.orange); } }); } public String getGetal() {if(isGetal()) return getText(); return "0"; } public Dimension getPreferredSize(){return new Dimension(370,30);} public boolean isGetal() {String tekst=getText(); int l=tekst.length(); Visuele Gebruikers Omgevingen: Swing 225
224/395
boolean isGetal=true; // we kijken of alle tekens wel digits zijn for(int j=0;j
class Rekenmachine {String eerstegetal,tweedegetal; char bewerking; public Rekenmachine() {eerstegetal="0";tweedegetal="0";bewerking='+'; } public void setBewerking(char b){bewerking=b;} public void setGetalEen(String een) {eerstegetal=een;} public void setGetalTwee(String twee) {tweedegetal=twee;} public String getResultaat() { int getal1=convertToInt(eerstegetal); int getal2=convertToInt(tweedegetal); if(bewerking=='+')return ""+(getal1+getal2); if(bewerking=='-')return ""+(getal1-getal2); if(bewerking=='/'&&getal2!=0)return ""+(getal1/getal2); if(bewerking=='*')return ""+(getal1*getal2); return "0"; } private int convertToInt(String tekst) {int i=0; try{i=Integer.parseInt(tekst); } catch(Exception ee){return 0;} return i; } } //De klasse Rekenmachine zal de String converteren naar integers en vervolgens //gewoon de bewerkingen in java uitvoeren. // de klasse RekenmachineLong zal de twee String converteren naar array’s van 100 // characters. Vervolgens zullen deze array’s van rechts naar links worden afgelopen. // Elk character zal naar een integer worden omgezet en worden opgeteld bij het // overeenkomstig character van de andere array.
Visuele Gebruikers Omgevingen: Swing 226
225/395
class RekenmachineLong extends Rekenmachine {char getal1[]=new char[100]; char getal2[]=new char[100]; char result[]=new char[100]; String allemaalnullen="0000000000000000000000000"+ "0000000000000000000000000"+ "0000000000000000000000000"+ "0000000000000000000000000"; public int basis(){return 10;} public String getResultaat() {// we implementeren alleen '+' initArrays(); int overdracht=0; for (int i=99;i>=0;i--) { // we converteren '0' naar 0 en ...'9' naar 9 int digit1=getal1[i]-'0'; int digit2=getal2[i]-'0'; int digit3=digit1+digit2+overdracht; overdracht=digit3/basis(); result[i]=(char)((int)'0'+(digit3 - (overdracht*basis()))); } return new String(result); } private void initArrays() { String eerstegetal100= allemaalnullen.substring(0,100 - eerstegetal.length())+eerstegetal; String tweedegetal100= allemaalnullen.substring(0,100 - tweedegetal.length())+tweedegetal; getal1=eerstegetal100.toCharArray(); getal2=tweedegetal100.toCharArray(); } } // het enige verschil in algoritme tussen het rekenen in het tiendelig of het binair //stelsel, is de basis die ofwel 10 ofwel 2 zal zijn.
class RekenmachineBinaryLong extends RekenmachineLong {public int basis(){return 2;} }
class RekenmachineOctaalLong extends RekenmachineLong {public int basis(){return 8;} } De klasse GUI zal kunnen werken met elke Rekenmachine. Bij het aanmaken van een object van deze klasse GUI zal een object van de klasse Rekenmachine worden doorgegeven.
class GUI extends JFrame Visuele Gebruikers Omgevingen: Swing 227
226/395
{ class KeuzeBewerking implements ActionListener {public void actionPerformed(ActionEvent ae) {if(ae.getSource() instanceof JButton) {JButton but=(JButton)ae.getSource(); char bewerking=but.getText().charAt(0); if( bewerking=='+') rekenmachine.setBewerking('+'); if( bewerking=='-') rekenmachine.setBewerking('-'); if( bewerking=='/') rekenmachine.setBewerking('/'); if( bewerking=='*') rekenmachine.setBewerking('*'); rekenmachine.setGetalEen( getal1.getGetal() ); rekenmachine.setGetalTwee( getal2.getGetal() ); if( bewerking=='=') {String resultaat=rekenmachine.getResultaat(); getal3.setText(resultaat); } } } } JIntegerTextField getal1,getal2,getal3; JButton plus,min,maal,deel,is; Container c; Rekenmachine rekenmachine; public GUI(Rekenmachine rekenmachine) {super("rekenmachine"); this.rekenmachine=rekenmachine; c=getContentPane(); c.setLayout(new FlowLayout()); getal1=new JIntegerTextField(); getal2=new JIntegerTextField(); plus =new JButton("+");min =new JButton("-"); deel =new JButton("/");maal =new JButton("*"); is =new JButton("="); getal3=new JIntegerTextField(); c.add(getal1);c.add(plus);c.add(min);c.add(deel);c.add(maal); c.add(getal2);c.add(is); c.add(getal3); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); KeuzeBewerking keuzebewerking=new KeuzeBewerking(); plus.addActionListener(keuzebewerking); min.addActionListener(keuzebewerking); maal.addActionListener(keuzebewerking); deel.addActionListener(keuzebewerking); is.addActionListener(keuzebewerking); setSize(400,250); setVisible(true); } } public class Rekenmachine1b {public static void main(String args[]) {new GUI(new RekenmachineOctaalLong()); } }
Visuele Gebruikers Omgevingen: Swing 228
227/395
Strategy: Karel het objectje
In dit programma zijn er twee met de muis versleepbare mannetjes. Je kan een mannetje selecteren door er met de muis op te klikken. Je kan het vervolgens verslepen. Het geselecteerd mannetje kan je ook een ander hoofd geven: een rond of een vierkant hoofd. In dat geval wordt er een object dat de interface ‘Head’ implementeert aangemaakt en ingeplugd in de persoon p1 of p2.
Visuele Gebruikers Omgevingen: Swing 229
228/395
import javax.swing.*; import javax.swing.border.*; import java.awt.*; import java.awt.event.*; import java.io.*;
Visuele Gebruikers Omgevingen: Swing 230
229/395
/* ***************** MYPANNEL CLASS ******************** */
class MyPanel extends JPanel { int posx,posy; int px, py; Person p1=new Person(200,100); Person p2=new Person(300,100); public MyPanel() {setDoubleBuffered(false); setOpaque(false); addMouseMotionListener (new MouseMotionAdapter() {public void mouseDragged(MouseEvent e) { Graphics g=getGraphics(); posx=e.getX(); posy=e.getY(); if(p1.isMouseOnObject(px, py)) {p1.clear(g); p1.moveBy(posx - px, posy -py); } if(p2.isMouseOnObject(px, py)) {p2.clear(g); p2.moveBy(posx - px, posy -py); } px = posx; py = posy; paint(g);}}); addMouseListener (new MouseAdapter() {public void mousePressed(MouseEvent e) {px=e.getX(); py=e.getY(); if (p1.isMouseOnObject(px, py)) {p1.setSelected(true); p2.setSelected(false);} if (p2.isMouseOnObject(px, py)) {p2.setSelected(true); p1.setSelected(false);} }}); } public void paint(Graphics g) {p1.draw(g); p2.draw(g); } public void changeHeadOfSelectedPerson(Head head) {if(p1.isSelected())p1.setHead(head); if(p2.isSelected())p2.setHead(head); } } /* ******************* HEAD-INTERFACE ******************** */
interface Head {public void draw(int x,int y, Graphics g); } Visuele Gebruikers Omgevingen: Swing 231
230/395
class SquareHead implements Head {public void draw(int x,int y, Graphics g) { g.setColor(Color.yellow); g.fillRect(x+30,y,20,20); g.setColor(Color.black); g.drawRect(x+30,y,20,20);} }
class RoundHead implements Head {public void draw(int x,int y, Graphics g) { g.setColor(Color.yellow); g.fillOval(x+30,y,20,20); g.setColor(Color.black); g.drawOval(x+30,y,20,20);} } /* ******************* PersonClass ******************** */
class Person {
Head head; int x,y; boolean selected; public Person (int parx, int pary) { x=parx; y=pary; selected=false; setHead(new RoundHead()); } public void setSelected(boolean selected){this.selected=selected;} public boolean isSelected(){return selected;} public void setHead(Head head) {this.head=head;} public void draw(Graphics g) { g.setColor(Color.black); g.drawLine(x+6,y+30,x+20,y+30); g.drawLine(x+60,y+30,x+74,y+30); g.drawLine(x+30,y+60,x+30,y+75); g.drawLine(x+50,y+60,x+50,y+75); g.drawRect(x+45,y+75,10,5); g.drawRect(x+1,y+25,5,10); g.drawRect(x+25,y+75,10,5); g.drawRect(x+74,y+25,5,10); g.drawRect(x+20,y+20,40,40); if(head != null) head.draw(x,y,g); } public void clear(Graphics g) { g.setColor(Color.white); g.fillRect(x,y,80,90); } public boolean isMouseOnObject(int xm, int ym) {return x+20 < xm && xm < x+60 && y+20 < ym && ym < y+60;}
Visuele Gebruikers Omgevingen: Swing 232
231/395
public void moveBy(int deltax, int deltay) {x += deltax; y += deltay;} }; /* ******************* JFRAME-PROCEDURE ******************** */
public class KarelHetObjectje extends JFrame { MyPanel Carlpanel =new MyPanel(); JButton roundbutton =new JButton("Round"); JButton squarebutton=new JButton("Square"); JLabel txttype =new JLabel("Carl's Head :"); public KarelHetObjectje() { super("Carl the Object HeadPlug-in"); setSize(500,310); Container c=getContentPane(); c.setBackground(Color.white); c.setLayout(null); c.add(roundbutton); c.add(txttype); c.add(squarebutton); c.add(Carlpanel); txttype.setBounds(10,10,100,30); roundbutton.setBounds(10,50,80,30); squarebutton.setBounds(10,90,80,30); Carlpanel.setBounds(100,10,380,260); Carlpanel.setBorder(new LineBorder(Color.blue)); roundbutton.addActionListener(new ActionListener() {public void actionPerformed(ActionEvent e) { if (e.getSource()==roundbutton) {Carlpanel.changeHeadOfSelectedPerson(new RoundHead()); repaint();} }}); squarebutton.addActionListener(new ActionListener() {public void actionPerformed(ActionEvent e) { if (e.getSource()==squarebutton) {Carlpanel.changeHeadOfSelectedPerson(new SquareHead()); repaint();}}}); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setVisible(true); } /* ******************* MAIN-PROCEDURE ******************** */ public static void main( String[] args ) {new KarelHetObjectje( ); }}
Visuele Gebruikers Omgevingen: Swing 233
232/395
Een tekenprogramma
In dit tekenprogramma kan je de kleur waarmee wordt getekend, inpluggen. Je kan ook de dikte van de tekenpen opgeven. De JFrame maakt gebruikt van een GridBagLayout. Hiermee wordt de verschillende onderdelen van het venster proportioneel mee vergroot.
Visuele Gebruikers Omgevingen: Swing 234
233/395
import javax.swing.*; import javax.swing.border.*; import javax.swing.JOptionPane.*; import java.awt.*; import java.awt.event.*; /* ***************** DRAWPANNEL CLASS ******************** */
class MyPanel extends JPanel { int x=0, y=0, oldx, oldy; static int size=1; static ColorChooser cc; static Color bkcol=Color.white;
Visuele Gebruikers Omgevingen: Swing 235
234/395
public MyPanel(Color c) { bkcol=c; setBackground(bkcol); addMouseListener (new MouseAdapter() {public void mousePressed(MouseEvent e) { oldx=x; oldy=y; x=e.getX(); y=e.getY(); Graphics g=getGraphics(); if (e.isMetaDown()) g.setColor(bkcol); else g.setColor(cc.giveColour()); if (size >3) g.fillOval(x, y, size, size); }}); addMouseMotionListener (new MouseMotionAdapter() {public void mouseDragged(MouseEvent e) { oldx=x; oldy=y; x=e.getX(); y=e.getY(); Graphics g=getGraphics(); if (e.isMetaDown()) g.setColor(bkcol); else g.setColor(cc.giveColour()); if (size >3) g.fillOval(x, y, size, size); else g.drawLine(oldx, oldy, x, y); }});} public void pluginColorChooser(ColorChooser c) {cc=c;} public void setSize(int a) {size=a;} } /* ******************* COLOR-INTERFACE ******************** */
interface ColorChooser {public Color giveColour();}
class blackColor implements ColorChooser {public Color giveColour(){return Color.black;}
}
class greenColor implements ColorChooser {public Color giveColour(){return Color.green;}
}
class redColor implements ColorChooser {public Color giveColour(){ return Color.red;}
}
class blueColor implements ColorChooser {public Color giveColour(){ return Color.blue;}
}
Visuele Gebruikers Omgevingen: Swing 236
235/395
class magicColor implements ColorChooser { static int rx=0,gx=100,bx=200; boolean rxbl=false,gxbl=false,bxbl=false; private static Color magic = new Color(rx,0,0,150); public Color giveColour() { magic = new Color((rx%255),(gx%255),(bx%255),255); if (rx>=0 && !rxbl) rx=rx+1; if (rx<=254 && rxbl) rx=rx-2; if (rx==254) rxbl=true; if (rx==0) rxbl=false; if (gx>=0 && !gxbl) gx=gx+2; if (gx<=254 && gxbl) gx=gx-1; if (gx==254) gxbl=true; if (gx==0) gxbl=false; if (bx>=0 && !bxbl) bx=bx+2; if (bx<=254 && bxbl) bx=bx-2; if (bx==254) bxbl=true; if (bx==0) bxbl=false; return magic; } } /* ******************* DIALOG-PROCEDURE ******************** */
public class Painter extends JFrame { JComboBox kleurbox =new JComboBox(new String[] {" Black"," Green"," Red"," Blue"," Magic"}); JComboBox sizebox =new JComboBox(new String[] {" Small"," Medium"," Large"," XLarge", " Super"}); MyPanel DRAWPANNEL =new MyPanel(Color.white); JButton exitknop =new JButton("Exit"); JButton clearknop =new JButton("Clear"); JButton infoknop =new JButton("Info"); JLabel txtcolor =new JLabel("Color :"); JLabel txtsize =new JLabel("Size :"); JMenuBar bar =new JMenuBar(); JMenu menu1 =new JMenu("File"); JMenu menu1it1 =new JMenu("New"); JMenu menu1it2 =new JMenu("Save"); JMenuItem menu1it11 =new JMenuItem("Black"); JMenuItem menu1it12 =new JMenuItem("White"); public Painter() { super(); setSize(450,250); setTitle("Java Paint"); MyPanel.pluginColorChooser(new blackColor()); final JPanel c= new JPanel(new BorderLayout()); Visuele Gebruikers Omgevingen: Swing 237
236/395
c.setLayout(new GridBagLayout()); c.setBorder(new EmptyBorder(new Insets(5,5,5,5))); getContentPane().add(BorderLayout.CENTER, c); GridBagConstraints gb = new GridBagConstraints(); gb.weighty=1.0; gb.weightx=1.0; /* ******************* DRAWPANNEL ******************** */ gb.gridx=0; gb.gridy=0; gb.ipadx=260; gb.gridheight=5; gb.anchor=GridBagConstraints.WEST; gb.fill=GridBagConstraints.VERTICAL; c.add(DRAWPANNEL,gb); /* ****************** TEKST-COLOR ******************* */ gb.gridx=1; gb.ipadx=5; gb.ipady=5; gb.gridheight=1; gb.fill=GridBagConstraints.NONE; c.add(txtcolor,gb); /* ****************** TEKST-SIZE ******************** */ gb.gridy=1; gb.ipadx=5; gb.ipady=5; gb.gridheight=1; gb.fill=GridBagConstraints.NONE; c.add(txtsize,gb); /* ******************* COLORBUTTON ********************* */ gb.gridx=2; gb.gridy=0; gb.ipadx=5; gb.ipady=5; gb.anchor=GridBagConstraints.EAST; gb.fill=GridBagConstraints.HORIZONTAL; c.add(kleurbox,gb); kleurbox.setMaximumRowCount(3); /* ******************* SIZEBUTTON ********************* */ gb.gridy=1; gb.ipadx=5; gb.ipady=5; c.add(sizebox,gb); sizebox.setMaximumRowCount(3); /* ******************* EXITBUTTON ********************** */ gb.gridy=2; c.add(exitknop,gb); /* ******************* CLEARBUTTON ********************** */ gb.gridy=3; c.add(clearknop,gb); /* ******************* INFOBUTTON ********************** */ gb.gridy=4; c.add(infoknop,gb); /* ******************* LISTENERS ********************* */ kleurbox.addItemListener (new ItemListener() {public void itemStateChanged(ItemEvent ee) {if (kleurbox.getSelectedIndex()==0) DRAWPANEL.pluginColorChooser(new blackColor()); if (kleurbox.getSelectedIndex()==1) DRAWPANEL.pluginColorChooser(new greenColor()); if (kleurbox.getSelectedIndex()==2) DRAWPANEL.pluginColorChooser(new redColor()); if (kleurbox.getSelectedIndex()==3) DRAWPANEL.pluginColorChooser(new blueColor());
Visuele Gebruikers Omgevingen: Swing 238
237/395
if (kleurbox.getSelectedIndex()==4) DRAWPANEL.pluginColorChooser(new magicColor()); }}); exitknop.addActionListener(new ActionListener() {public void actionPerformed(ActionEvent e) {if (e.getSource()==exitknop) System.exit(0);}}); clearknop.addActionListener(new ActionListener() {public void actionPerformed(ActionEvent e) {if (e.getSource()==clearknop) c.repaint();}}); infoknop.addActionListener(new ActionListener() {public void actionPerformed(ActionEvent e) {if (e.getSource()==infoknop) { JOptionPane.showMessageDialog(null, "JPaint by Mike Ptacek\[email protected]","Info", JOptionPane.INFORMATION_MESSAGE); }}}); sizebox.addItemListener (new ItemListener() {public void itemStateChanged(ItemEvent ee) {DRAWPANEL.setSize((sizebox.getSelectedIndex()+1)*3); if (sizebox.getSelectedIndex()==4) DRAWPANEL.setSize(35); }}); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setVisible(true); } /* ******************* MAIN-PROCEDURE ******************** */ public static void main( String[] args ) {new Painter( );}}
Visuele Gebruikers Omgevingen: Swing 239
238/395
JTree import import import import
javax.swing.*; javax.swing.tree.*; java.awt.*; java.io.*;
class Test1 extends JFrame {JTree tree; public Test1(){Container c=getContentPane(); c.setLayout(new FlowLayout()); Object [] diep={new File("bestand.txt"),"drieC"}; Object [] drie={"drieA","drieB",diep}; Object [] top={"een","twee",drie}; tree=new JTree(top); c.add(new JScrollPane(tree)); setSize(300,200); setVisible(true); } } public class Tree1 { static public void main(String args[]) {new Test1(); }}
JTree zal een boom van objecten (DefaultMutableTreeNode) maken met als afgebeelde tekst de toString waarde van elk object. Voor een array van objecten wordt wel een erg gekke String waarde gegenereerd. Intern wordt hiervoor (in een DefaultMutableTreeNode object) als userobject heel het File object bijgehouden. Visueel wordt
String "twee" Object [ ] String String Object [ ] File Al deze objecten zijn van het type DefaultMutableTreeNode en bevatten een "userobject" (String, File, Object [ ],...). Tevens bevat de klasse DefaultMutableTreeNode methodes om 'parent' en 'children' van elke node op te vragen. Alsook routines om tijdens de uitvoering knooppunten toe te voegen.
Visuele Gebruikers Omgevingen: Swing 240
239/395
Tree2 import import import import import
javax.swing.*; javax.swing.tree.*; java.awt.*; java.io.*; java.util.*;
class Test2 extends JFrame {JTree tree; public Test2(){Container c=getContentPane();
We erven over van Vector, zodat we een nieuwe toString kunnen
Vector diep=new Vector() {public String toString(){return "DIEP NIVEAU";}}; diep.addElement(new File("bestand.txt")); diep.addElement("drieC"); Vector drie=new Vector() {public String toString(){return "NIVEAU DRIE";}}; drie.addElement("drieA"); drie.addElement("drieB"); drie.addElement(diep); Vector top=new Vector() {public String toString(){return "top";} }; top.addElement("een"); top.addElement("twee"); top.addElement(drie); tree=new JTree(top); tree.setRootVisible(true); c.add(new JScrollPane(tree)); setSize(300,200); setVisible(true); //als we na het openen van niveau drie nog elementen // toevoegen, worden deze niet getoond. // we zullen later expliciet moeten hertekenen for(int i=0;i<10;i++) try{Thread.sleep(1000); drie.addElement("drie"+i); }catch(InterruptedException e) {System.out.println(e);} }} public class Tree2
{ static public void main(String args[]) {new Test2(); }}
Visuele Gebruikers Omgevingen: Swingwel 241een goede 240/395 Nu hebben de knooppunten naam. Dit komt dankzij overerving van Vector, zodat we een nieuwe toString kunnen
Tree3 import import import import import import
javax.swing.*; javax.swing.tree.*; java.awt.*; java.io.*; java.util.*; javax.swing.event.*;
In plaats van alle waarden op te sommen, kan je ook gewoon een Model opgeven dat een child of een root zal genereren. Met 'getChildCount' geven we op hoeveel kinderen elk parent knooppunt heeft . Met 'isLeaf' geven we aan of het een
class MyTreeModel implements TreeModel { Waarden public Object getChild(Object parent, int index) worden //Returns child of parent at index index dynamisch //in parent's child array. {int parInt=((Integer)parent).intValue(); gegenereer if(parInt==0)//root d.(Wanneer return new Integer(10+index); nodig). else return new Integer(parInt*10+index); } public int getChildCount(Object parent) // Returns the number of children of parent. {return 10; } public int getIndexOfChild(Object parent, Object child) // Returns the index of child in parent. {return 0; } public Object getRoot() // Returns the root of the tree. {return new Integer(0); } public boolean isLeaf(Object node) // Returns true if node is a leaf. {int value=((Integer)node).intValue(); if( value==0 || value%10==0) return false; return true; } public void removeTreeModelListener(TreeModelListener l) {} //Removes listener previously added with addTreeModelListener(). public void addTreeModelListener(TreeModelListener l){} //Adds listener for TreeModelEvent posted after tree changes. public void valueForPathChanged(TreePath path, Object newValue){} // Messaged when the user has altered the value for the item //identified by path to newValue. } class Test3 extends JFrame Voorlopig niet gebruikt. {JTree tree; public Test3(){Container c=getContentPane(); tree=new JTree(new MyTreeModel()); tree.setRootVisible(true); c.add(new JScrollPane(tree)); setSize(300,200); setVisible(true); } } public class Tree3 { static public void main(String args[]) {new Test3(); }}
Visuele Gebruikers Omgevingen: Swing 242
241/395
We gaan nu file gaan bekijken, maar in plaats van Tree4 (vb JFC Java in a nutshell O'Reilly) eerst alle files van c: te lezen en dan pas te tonen, gaan we slechts de files lezen als de gebruiker deze import javax.swing.*; directory opent. Hiervoor kunnen we ook import javax.swing.tree.*; TreeModel gebruiken. Nu zal getChild een File import java.awt.*; import java.io.*; object genereren. TreeModel heeft heel wat import java.util.*; routines en we moeten ze allemaal implementeren import javax.swing.event.*; zonder ons af te vragen waar ze zullen gebruikt d class MyTreeModel4 implements TreeModel {
public Object getChild(Object parent, int index) //Returns child of parent at index index in parent's child array. {String [] children=((File)parent).list(); if((children==null) || (index>=children.length))return null; return new File((File)parent, children[index]); } public int getChildCount(Object parent) We gaan na hoeveel files er // Returns the number of children of parent. in de directory staan. {String [] children=((File)parent).list(); if(children==null) return 0; return children.length; } public int getIndexOfChild(Object parent, Object child) // Returns the index of child in parent. {String [] children=((File)parent).list(); if(children==null) return -1; String childname=((File)child).getName(); Alleen files for(int i=0;i
Visuele Gebruikers Omgevingen: Swing 243
242/395
Visuele Gebruikers Omgevingen: Swing 244
243/395
Tree5 import javax.swing.*; import javax.swing.tree.*; public class Tree5 extends JTree { public Tree5() { DefaultMutableTreeNode rootNode = new DefaultMutableTreeNode(); DefaultMutableTreeNode apolloNode =new DefaultMutableTreeNode("Apollo"); rootNode.add(apolloNode); DefaultMutableTreeNode skylabNode =new DefaultMutableTreeNode("Skylab"); rootNode.add(skylabNode); DefaultMutableTreeNode n =new DefaultMutableTreeNode("11");apolloNode.add(n); n.add(new DefaultMutableTreeNode("Neil Armstrong")); n.add(new DefaultMutableTreeNode("Buzz Aldrin")); n.add(new DefaultMutableTreeNode("Michael Collins")); n = new DefaultMutableTreeNode("12");apolloNode.add(n); n.add(new DefaultMutableTreeNode("Pete Conrad")); n.add(new DefaultMutableTreeNode("Alan Bean")); n.add(new DefaultMutableTreeNode("Richard Gordon")); n = new DefaultMutableTreeNode("13");apolloNode.add(n); n.add(new DefaultMutableTreeNode("James Lovell")); n.add(new DefaultMutableTreeNode("Fred Haise")); n.add(new DefaultMutableTreeNode("Jack Swigert")); n = new DefaultMutableTreeNode("14");apolloNode.add(n); n.add(new DefaultMutableTreeNode("Alan Shephard")); n.add(new DefaultMutableTreeNode("Edgar Mitchell")); n.add(new DefaultMutableTreeNode("Stuart Roosa")); n = new DefaultMutableTreeNode("15");apolloNode.add(n); n.add(new DefaultMutableTreeNode("Dave Scott")); n.add(new DefaultMutableTreeNode("Jim Irwin")); n.add(new DefaultMutableTreeNode("Al Worden")); n = new DefaultMutableTreeNode("16");apolloNode.add(n); n.add(new DefaultMutableTreeNode("John Young")); n.add(new DefaultMutableTreeNode("Charlie Duke")); n.add(new DefaultMutableTreeNode("Ken Mattingly")); n = new DefaultMutableTreeNode("17");apolloNode.add(n); n.add(new DefaultMutableTreeNode("Eugene Cernan")); n.add(new DefaultMutableTreeNode("Harrison Schmidt")); n.add(new DefaultMutableTreeNode("Ron Evans")); n = new DefaultMutableTreeNode("2");skylabNode.add(n); n.add(new DefaultMutableTreeNode("Pete Conrad")); n.add(new DefaultMutableTreeNode("Joseph Kerwin")); n.add(new DefaultMutableTreeNode("Paul Weitz")); n = new DefaultMutableTreeNode("3");skylabNode.add(n);
Visuele Gebruikers Omgevingen: Swing 245
244/395
n.add(new DefaultMutableTreeNode("Alan Bean")); n.add(new DefaultMutableTreeNode("Owen Garriott")); n.add(new DefaultMutableTreeNode("Jack Lousma")); n = new DefaultMutableTreeNode("4");skylabNode.add(n); n.add(new DefaultMutableTreeNode("Gerald Carr")); n.add(new DefaultMutableTreeNode("Edward Gibson")); n.add(new DefaultMutableTreeNode("William Pogue")); this.setModel(new DefaultTreeModel(rootNode)); } public static void main(String[] args) { JFrame f = new JFrame("Tree5"); Tree5 t = new Tree5(); t.putClientProperty("JTree.lineStyle", "Angled"); t.expandRow(0); f.getContentPane().add(new JScrollPane(t)); f.setSize(300, 300); f.setVisible(true); } } In dit voorbeeld maken we een boom van DefaultMutableTreeNode objecten. We hangen eerst alle nodes aan elkaar. Vervolgens geven de rootnode door aan een treemodel en maken we de tree. Aan een tree kunnen we alleen maar klassen die de volgende interfacen implementeren toevoegen: TreeNode: • public Enumeration children() • public boolean getAllowsChildren() • public TreeNode getChildAt(int index) • public int getChildCount() • public int getIndex(TreeNode child) • public TreeNode getParent() • public boolean isLeaf()
Een TreeNode hoeft niet visueel te zijn. Als u gewoon een boom van gegevens wilt bijhouden in uw programma, kunt u ook TreeNode gebruiken.
DefaultMutableTreeNode implementeert deze interface. MutableTreeNode (implementeert ook TreeNode +:) • public void insert(MutableTreeNode child, int index) • public void remove(int index) • public void remove(MutableTreeNode node) • public void removeFromParent() • public void setParent(MutableTreeNode parent) • public void setUserObject(Object userObject)
Een stapje verder: u kunt dynamisch objecten toevoegen en verwijderen. Let op de setUserObject: elk MutableTreeNode object kan hierdoor een userobject
DefaultMutableTreeNode implementeert deze interface. DefaultMutableTreeNode heeft zeer veel methodes om heel de boom af te lopen. Bekijk maar eens.
Visuele Gebruikers Omgevingen: Swing 246
245/395
Tree6 import javax.swing.*; import javax.swing.tree.*; import java.util.*; public class Tree6 { public static void main(String[] args) { JFrame f = new JFrame("Tree 6:Enumerations"); DefaultMutableTreeNode rootNode = new DefaultMutableTreeNode("Root"); for (int i = 0; i < 2; i++) { DefaultMutableTreeNode a = new DefaultMutableTreeNode("" + i); for (int j = 0; j < 2; j++) { DefaultMutableTreeNode b = new DefaultMutableTreeNode("" + i + "_" + j); for (int k = 0; k < 2; k++) { b.add(new DefaultMutableTreeNode(""+i+"_"+j +"_" +k)); } a.add(b); } rootNode.add(a); } JTree t = new JTree(rootNode); t.putClientProperty("JTree.lineStyle", "Angled"); f.getContentPane().add(new JScrollPane(t)); f.setSize(300, 300); Geeft een iterator terug om het f.setVisible(true); knooppunt af te lopen op een
speciale wijze. // Now show various enumerations printEnum("Preorder", rootNode.preorderEnumeration()); printEnum("Postorder", rootNode.postorderEnumeration()); printEnum("Breadth First", rootNode.breadthFirstEnumeration()); } public static void printEnum(String title, Enumeration e) { System.out.println("===============\n" + title); while (e.hasMoreElements()) { Iterator om boom System.out.print(e.nextElement() + " "); af te lopen. } System.out.println("\n"); } }
=============== Preorder Root 0 0_0 0_0_0 0_0_1 0_1 0_1_0 0_1_1 1 1_0 1_0_0 1_0_1 1_1 1_1_0 1_1_1 =============== Postorder 0_0_0 0_0_1 0_0 0_1_0 0_1_1 0_1 0 1_0_0 1_0_1 1_0 1_1_0 1_1_1 1_1 1 Root =============== Breadth First Root 0 1 0_0 0_1 1_0 1_1 0_0_0 0_0_1 0_1_0 0_1_1 1_0_0 1_0_1 1_1_0 1_1_1
Visuele Gebruikers Omgevingen: Swing 247
246/395
U merkt: DefaultMutableTreeNode heeft handige iterators om heel de boom (of een deel ervan) af te lopen.
Tree6B import import import import
javax.swing.*; javax.swing.tree.*; java.util.*; java.io.*;
public class Tree6B { public static void main(String[] args) { JFrame f = new JFrame("Tree 6B"); DefaultMutableTreeNode rootNode = new DefaultMutableTreeNode("Root"); for (int i = 0; i < 2; i++) { DefaultMutableTreeNode a = new DefaultMutableTreeNode("" + i); for (int j = 0; j < 2; j++) { DefaultMutableTreeNode b =new DefaultMutableTreeNode(""+i+"_"+j); for (int k = 0; k < 2; k++) b.add(new DefaultMutableTreeNode(""+i+"_"+ j + "_" + k)); a.add(b); } rootNode.add(a); } JTree tree = new JTree(rootNode); f.getContentPane().add(new JScrollPane(tree)); f.setSize(300, 300);f.setVisible(true); // de data staat los van de tree en kan los ervan gemanipuleerd //worden. Dit treemodel wordt automatisch opgebouwd // en bevat objecten voor alle elementen van onze tree DefaultTreeModel data = (DefaultTreeModel)tree.getModel();
bevat alle gegevens van de boom. // we voegen aan de root een File toe: DefaultMutableTreeNode root = (DefaultMutableTreeNode)data.getRoot(); data.insertNodeInto(new DefaultMutableTreeNode( new File("foert.txt")), root,0); data.reload(root); // we lopen alles eens af vertrekkend van het datamodel print(root); } static void print(DefaultMutableTreeNode n) {// de klasse DefaultMutableTreeNode bevat alles nodig // om heel het datamodel af te lopen. (De klasse DefaultTreeModel // bevat ook routines hiervoor. Je kan dus kiezen. //Ik kies nu eens // DefaultMutableTreeNode om alles af te lopen en //niet DefaultTreeModel System.out.println(n); DefaultMutableTreeNode x=n.getNextNode(); We lopen alleen de bladeren van de boom af. while(x!=null) {System.out.println(x); x=x.getNextLeaf();
Visuele Gebruikers Omgevingen: Swing 248
247/395
} /* andere handige functies: getNextNode, getNextLeaf, getNextSibling, getPreviousNode, getPreviousLeaf, getPreviousSibling, getRoot isLeaf, getChildCount, getDepth, getLeafCount, getSiblingCount, getUserObject : speciaal om het omkapselde object te vragen setUserObject : om het geassocieerde object te wijzigen add(MutableTreeNode) insert(MutableTreeNode,plaats)*/ } }
Een File object werd achteraf aan het datamodel toegevoegd
Root foert.txt 0_0_0 0_0_1 0_1_0 0_1_1 1_0_0 1_0_1 1_1_0 1_1_1
Tree7A import javax.swing.*;
Wijzigbaar ! Pas wel op. U typt een String in en als het oorspronkelijke object geen String was, zal het toch vervangen worden door een String.
import import import import import
javax.swing.tree.*; java.awt.*; java.io.*; java.util.*; javax.swing.event.*;
class Test7A extends JFrame {JTree tree; public Test7A() {Container c=getContentPane(); Vector diep=new Vector() {public String toString() {return "DIEP NIVEAU";}}; diep.addElement(new File("bestand.txt"));
Visuele Gebruikers Omgevingen: Swing 249
248/395
diep.addElement("drieC"); Vector drie=new Vector() {public String toString() {return "NIVEAU DRIE";}}; drie.addElement("drieA");drie.addElement("drieB"); drie.addElement(diep); Vector top=new Vector() {public String toString(){return "top";} }; top.addElement("een");top.addElement("twee"); top.addElement(drie); tree=new JTree(top); tree.setRootVisible(true); c.add(new JScrollPane(tree));setSize(300,200);setVisible(true); // JTree zal voor elk element van de vector een // DefaultMutableTreeNode // object aanmaken en dit linken aan de andere nodes. tree.setEditable(true);//!! PROBEER EENS, HET WERKT tree.addTreeSelectionListener(new TreeSelectionListener() { public void valueChanged(TreeSelectionEvent evt) { // welk 'path' heeft de gebruiker geselecteerd? // een 'path' bestaat uit een aaneenschakeling van alle // 'voorgaande' knooppunten. // Dit zijn allemaal DefaultMutableTreeNode objecten TreePath selectTreePath=tree.getSelectionPath(); System.out.println("_____________________________________"); System.out.println("Selected:"+selectTreePath ); //toString wordt opgeroepen // probeer eens meer dan één element te selecteren //(shift,ctrl) System.out.println("aantal geselecteerd:" +tree.getSelectionCount()); if (tree.getSelectionCount() >1) {TreePath [] alles=tree.getSelectionPaths(); for(int i=0;i< alles.length;i++) System.out.println(""+i+":"+alles[i]); } } }); } } public class Tree7A { static public void main(String args[]) {new Test7A(); }} /* handige functies van JTree: --------------------------setRootVisible(true/false) boolean isSelectionEmpty() setSelectionPaths(TreePath[]) collapsePath(TreePath) expandPath(TreePath) TreePath getClosestPathForLocation(int x,int y) TreePath getPathForLocation(int x,int y) boolean hasBeenExpanded(TreePath) boolean isCollapsed(TreePath) boolean isPathEditable(TreePath) boolean isPathSelected(TreePath) boolean isVisible(TreePath) makeVisible(TreePath)
Visuele Gebruikers Omgevingen: Swing 250
249/395
*/
Als we deze twee elementen selecteren, wordt de volgende uitvoer
_____________________________________ Selected:[root, NIVEAU DRIE, DIEP NIVEAU, bestand.txt] aantal geselecteerd:2 0:[root, NIVEAU DRIE, DIEP NIVEAU, bestand.txt] 1:[root, NIVEAU DRIE, DIEP NIVEAU, drieC] Belangrijk is dat de selectie door de gebruiker wordt weergegeven door een TreePath object. Dit is een aaneenschakeling van alle knooppunten die leiden tot het geselecteerd element. Het bevat dus meerdere objecten en deze meerdere objecten kunnen we uit het TreePath object halen. Een TreeSelectionListener gaat kunnen reageren op selecties van de gebruiker.
Tree7B import javax.swing.*; import javax.swing.tree.*; import java.awt.*; import java.io.*; import java.util.*; import javax.swing.event.*; class Test7B extends JFrame {JTree tree; public Test7B() {Container c=getContentPane(); Vector diep=new Vector() {public String toString(){return "DIEP NIVEAU";}}; diep.addElement(new File("bestand.txt")); diep.addElement("drieC"); Vector drie=new Vector() {public String toString(){return "NIVEAU DRIE";}}; drie.addElement("drieA");drie.addElement("drieB"); drie.addElement(diep); Vector top=new Vector() {public String toString(){return "top";} }; top.addElement("een");top.addElement("twee"); top.addElement(drie); tree=new JTree(top); tree.setRootVisible(true);
Visuele Gebruikers Omgevingen: Swing 251
250/395
c.add(new JScrollPane(tree));setSize(300,200);setVisible(true); tree.addTreeSelectionListener(new TreeSelectionListener() { public void valueChanged(TreeSelectionEvent evt) { TreePath [] alles=tree.getSelectionPaths(); for(int i=0;i< alles.length;i++) {TreePath selectedPath=alles[i]; System.out.println("-------------------------------------"); System.out.print(""+i+":"+selectedPath); // een TreePath bestaat uit alle knooppunten die tot het //geselecteerde onderdeel leiden: // we kunnen alle onderdelen (Objecten) eruit halen: Object [] partsSelectTreePath=selectedPath.getPath(); for(int j=0;j<partsSelectTreePath.length;j++) {System.out.println(); System.out.print("\tpart"+j+": " +partsSelectTreePath[j]+"\t ["); if(partsSelectTreePath[j] instanceof DefaultMutableTreeNode) {// er zit een DefaultMutableTreeNode rond ons node object Object uo = ((DefaultMutableTreeNode) partsSelectTreePath[j]).getUserObject(); if (uo != null) // root heeft geen userobject System.out.print(uo.getClass());//We laten type zien System.out.print("]"); } } System.out.println(); System.out.println("Dadelijk laatste deel geklikt TreePath:***" +selectedPath.getLastPathComponent() +"***(DefaultMutableTreeNode)"); }}}); }} public class Tree7B { static public void main(String args[]) {new Test7B(); }}
geselecteer d TreePath element
Met getLastPathComponent verkrijg je dadelijk het geselecteerde f l bl d l
------------------------------------0:[root, NIVEAU DRIE, DIEP NIVEAU, bestand.txt] Geeft klasse van part0: root [class java.lang.String] part1: NIVEAU DRIE [class Test7B$2] userobject van part2: DIEP NIVEAU [class Test7B$1] element van boom part3: bestand.txt [class java.io.File] weer. Meestal Dadelijk laatste deel geklikt TreePath: *** bestand.txt *** String, maar ook (DefaultMutableTreeNode) File naargelang wat ------------------------------------je er zelf instopt 1:[root, NIVEAU DRIE, DIEP NIVEAU, drieC] part0: root [class java.lang.String] Visuele Gebruikers Omgevingen: Swing 252
251/395
part1: NIVEAU DRIE [class Test7B$2] part2: DIEP NIVEAU [class Test7B$1] part3: drieC [class java.lang.String] Dadelijk laatste deel geklikt TreePath: *** drieC *** (DefaultMutableTreeNode) Alle onderdelen van het geselecteerde TreePath zijn DefaultMutableTreeNode elementen.
Tree8 : icoontjes, font, kleuren aanpassen import import import import
javax.swing.*; javax.swing.tree.*; javax.swing.event.*; java.awt.*;
public class Tree8{ public static void main(String[] args) { JFrame f = new JFrame("Tree Selection Example"); Tree5 tree = new Tree5();//Apollo, Skylab tree.putClientProperty("JTree.lineStyle", "Angled"); tree.expandRow(0); // Een DefaultTreeCellRenderer is een JLabel // waarvan we verschillende dingen kunnen aanpassen TreeCellRenderer cellrenderer=tree.getCellRenderer(); if (cellrenderer instanceof DefaultTreeCellRenderer) {DefaultTreeCellRenderer renderer = (DefaultTreeCellRenderer)cellrenderer; renderer.setLeafIcon(new ImageIcon("Leaf.gif")); renderer.setClosedIcon(new ImageIcon("Closed.gif")); renderer.setOpenIcon(new ImageIcon("Open.gif")); renderer.setBackgroundNonSelectionColor(Color.white); renderer.setBackgroundSelectionColor(Color.red); renderer.setTextNonSelectionColor(Color.blue); renderer.setTextSelectionColor(Color.orange); renderer.setFont(new Font("Monospaced",Font.ITALIC,11)); renderer.setBorderSelectionColor(Color.green); //tree.setBackground(Color.yellow);//achtergrond tree } f.getContentPane().add(new JScrollPane(tree)); f.setSize(300, 300); f.setVisible(true); } } In dit voorbeeld werken we met de boom van Tree5 voorbeeld (Apollo, Skylab). Dit voorbeeld komt uit een boek van Topley. We gaan de layout gaan aanpassen. Dit doen we door het bestaande CellRenderer object aan te passen. (We kunnen ook een nieuw CellRenderer object inpluggen.) Dit
Visuele Gebruikers Omgevingen: Swing 253
252/395
object staat in voor de presentatie van alle knooppunten en bladeren van de boom. Zoals u merkt merkt kunt u icoontjes en kleuren aanpassen op een eenvoudige wijze.
Tree9 : zelf een TreeCellRenderer klasse maken.
X : GeëXpandeerd SX : Selected, eXpanded
SL : Selected Leaf
Vooraan hebben we het rij nummer gezet (gewoon het nummer van de visuele rij in de boom). Bij openen van een tak worden alle nummers van latere rijen aangepast L : Leaf
import import import import
javax.swing.*; javax.swing.tree.*; javax.swing.event.*; java.awt.*;
class MyTreeCellRenderer implements TreeCellRenderer {public Component getTreeCellRendererComponent (JTree tree, // de boom Object value, // meestal een DefaultMutableTreeNode boolean selected,//geselecteerd? boolean expanded,// opengeklapt? boolean leaf, // is het een eindpunt? int row, // op welke rij komt dit component boolean hasFocus) { DefaultMutableTreeNode node = (DefaultMutableTreeNode)value; Object obj = node.getUserObject();// hier een String, maar kan //gelijk wat zijn zoals een File String status=new String(""+row+" "); if(selected) status+="S"; if(expanded) status+="X"; if(leaf) status+="L"; return new JLabel(status+" "+ obj); }} public class Tree9{ public static void main(String[] args) { JFrame f = new JFrame("Tree Selection Example"); Tree5 tree = new Tree5();//Apollo, Skylab tree.putClientProperty("JTree.lineStyle", "Angled"); tree.expandRow(0); Een nieuwe tree.setCellRenderer(new MyTreeCellRenderer()); CellRenderer wordt f.getContentPane().add(new JScrollPane(tree));
ingeplugd. Visuele Gebruikers Omgevingen: Swing 254
253/395
f.setSize(300, 300); f.setVisible(true); }} Weeral werken we met de vorige boom Tree5 (Apollo, Skylab). Een eigen TreeCellRenderer werkt eigenlijk heel eenvoudig. We moeten één routine opgeven : getTreeCellRendererComponent. Deze routine geeft een Component terug: het component dat wordt getoond. Meestal wordt hiervoor een JLabel genomen omdat je hiermee zowel tekst als het bijhorend icoontje kunt weergeven. De routine wordt opgeroepen voor elk element van het visueel deel van de boom en heeft als parameters: JTree tree, // de boom Object value, // meestal een DefaultMutableTreeNode boolean selected,//geselecteerd? boolean expanded,// opengeklapt? boolean leaf, // is het een eindpunt? int row, // op welke rij komt dit component boolean hasFocus) In 'value' zit het element van de boom waarvoor we een Component moeten genereren. Dit is meestal een DefaultMutableTreeNode object. Naargelang dit element geselecteerd of geëxpandeerd of een eindpunt is, kunnen we andere inhoud aan het te genereren component geven. In dit voorbeeld veranderen we gewoon de tekst van de JLabel die we genereren.
Tree9B : crazy layout : soms icon, soms een textfield, soms Label import import import import
javax.swing.*; javax.swing.tree.*; javax.swing.event.*; java.awt.*;
class MyTreeCellRendererB implements TreeCellRenderer {public Component getTreeCellRendererComponent (JTree tree, // de boom Object value, // meestal een DefaultMutableTreeNode boolean selected,//geselecteerd? boolean expanded,// opengeklapt? boolean leaf, // is het een eindpunt? int row, // op welke rij komt dit component boolean hasFocus) { DefaultMutableTreeNode node = (DefaultMutableTreeNode)value; Object obj = node.getUserObject();// hier een String String status=new String(""+row+" "+obj+" "); status+=selected?"SELECTED ":" "; status+=expanded?"EXPANDED ":" "; status+=leaf?"LEAF ":" "; JLabel label =new JLabel(); label.setFont(new Font("Monospaced",Font.PLAIN,14)); label.setText(status); if(leaf && row <7) label.setIcon(new ImageIcon("leaf.icon")); if(selected) {label.setFont(new Font("Monospaced",Font.ITALIC,14)); label.setBorder( BorderFactory.createLineBorder(Color.green)); }
Visuele Gebruikers Omgevingen: Swing 255
254/395
if(row>12){JButton but=new JButton(status); return but; } if(!leaf) return label; else if(!selected)return new JTextField(status); else {JTextField field=new JTextField(status); field.setBorder( BorderFactory.createLineBorder(Color.red)); return field; } }} public class Tree9B{ public static void main(String[] args) { JFrame f = new JFrame("Tree Selection Example"); Tree5 tree = new Tree5();//Apollo, Skylab tree.putClientProperty("JTree.lineStyle", "Angled"); tree.expandRow(0); tree.setCellRenderer(new MyTreeCellRendererB()); tree.setEditable(true); f.getContentPane().add(new JScrollPane(tree)); f.setSize(300, 300); f.setVisible(true); }}
JLabel
JTextField
geselecteerd : ander font (italic)+ kader
Indien rij > 12 : JB tt
Tree9C import import import import import
javax.swing.*; javax.swing.tree.*; javax.swing.event.*; java.awt.*; java.io.*;
Visuele Gebruikers Omgevingen: Swing 256
255/395
public class Tree9C{ public static void main(String[] args) { JFrame f = new JFrame("Tree Selection Example"); Tree5 tree = new Tree5();//Apollo, Skylab tree.putClientProperty("JTree.lineStyle", "Angled"); tree.expandRow(0); // het volgende is voldoende om de waarden van de knooppunten // te wijzigen: // pas wel op: we hebben als userobject van één object een // File object genomen (zie Tree5). De String waarde hiervan // zien we in de boom. Als u deze wijzigt, typt u een String in // en zal het userobject worden gewijzigd naar een String. tree.setEditable(true);
// we kunnen een JCheckBox, JComboBox of een JTextField // doorgeven aan een DefaultCellEditor. // Deze DefaultCellEditor is ook te gebruiken bij JTable's. JComboBox combo =new JComboBox(new Object[] {"Mercury","Gemini", "Apollo","Skylab", new File("Bestand2.txt")}); // Als u "Bestand2.txt neemt, dan kiest u voor een 'File' object // de andere opties zijn gewoon Strings. // Denk eraan: knooppunten van een JTree kunnen uit gelijk welk // object samengesteld zijn. DefaultCellEditor editor=new DefaultCellEditor(combo); tree.setCellEditor(editor); f.getContentPane().add(new JScrollPane(tree)); f.setSize(300, 300); f.setVisible(true); }
}
Visuele Gebruikers Omgevingen: Swing 257
256/395
Default hebben we als CellEditor een JTextField. We kunnen dit bijvoorbeeld vervangen door een JComboBox door deze aan de constructor van de CellEditor mee te geven. Probleem is wel dat deze CellEditor zal gebruikt worden voor alle knooppunten van de boom. U moet dus werkelijk alle waarden van eindbladen en knooppunten opgeven. Dit kan een probleem zijn. Dezelfde CellEditor kan ook wel gebruikt worden bij JTable. U moet vooral onthouden dat er een apart object is (CellEditor) dat instaat voor het veranderen van de waarde van elk element van de boom. Desnoods kunt u overerven van CellEditor of kunt u een eigen klasse maken die deze interface implementeert.
Tree9D : alleen eindbladen editeerbaar maken import import import import import import import
javax.swing.*; javax.swing.tree.*; javax.swing.event.*; java.awt.*; java.io.*; java.util.*; java.awt.event.*;
public class Tree9D{ public static void main(String[] args) { JFrame f = new JFrame("Tree Selection Example"); final Tree5 tree = new Tree5();//Apollo, Skylab tree.putClientProperty("JTree.lineStyle", "Angled"); tree.expandRow(0); tree.setEditable(true); JComboBox combo =new JComboBox( new Object[]{"Peeters","Janssens", "Amstrong","Geenen", new File("Bestand2.txt")}); DefaultCellEditor editor=new DefaultCellEditor(combo) {public boolean isCellEditable(EventObject evt) {if (evt instanceof MouseEvent) {TreePath path=tree.getPathForLocation(((MouseEvent)evt).getX(), ((MouseEvent)evt).getY()); DefaultMutableTreeNode o = (DefaultMutableTreeNode)path.getLastPathComponent(); return o.isLeaf(); } return false; }}; tree.setCellEditor(editor); f.getContentPane().add(new JScrollPane(tree)); f.setSize(300, 300); f.setVisible(true); }} We erven hier over van DefaultCellEditor. Let op hoe dat we toch nog aan de constructor van DefaultCellEditor het combo object doorgeven. We herdefinieren de functie isCellEditable. Deze heeft als parameter de event waarmee we het element selecteren. Wij moeten nu uitgaande van dit event bepalen of deze cell al dan niet editeerbaar is door al dan niet true terug te geven als resultaat van de functie. Hoe halen we uit de muisklik het TreePath object waarop geklikt werd? We kunnen uit het MouseEvent de X en Y positie halen van waar er geklikt werd. Dan kunnen we met de 'getPathForLocation aan de boom vragen welk TreePath bij deze coordinaten hoort. Het laatste DefaultMutableTreeNode element uit dit TreePath is de TreeNode waarop we geklikt hebben. Als dit een Leaf is, besluiten dat de gebruiker dit element mag editeren. Voor knooppunten zal altijd false teruggegeven worden. U mag hier zoveel op klikken als u wenst, er gebeurt niets.
Visuele Gebruikers Omgevingen: Swing 258
257/395
Tree99 :stamboom
Telkens u een knooppunt opent, komen er dynamisch twee knooppunten bij met als naam 'Vader van' en 'Moeder van'
import import import import import import import
java.awt.*; java.awt.event.*; java.util.*; javax.swing.*; javax.swing.border.*; javax.swing.event.*; javax.swing.tree.*;
class IconCellRenderer extends JLabel implements TreeCellRenderer { protected boolean m_selected; public IconCellRenderer() {super(); setOpaque(false);} public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean expanded, boolean leaf, int row, boolean hasFocus) {// we halen userobject uit value DefaultMutableTreeNode node = (DefaultMutableTreeNode)value; IconData obj = (IconData)node.getUserObject(); setText(obj.toString());//zetten tekst(uit userobject)in label if (expanded) setIcon(obj.getExpandedIcon()); else setIcon(obj.getIcon()); setFont(tree.getFont());//mooier setForeground(sel ? Color.red : Color.blue); setBackground(sel ? Color.yellow : Color.white); m_selected = sel; return this;} public void paintComponent(Graphics g) {Color bColor = getBackground(); Icon icon = getIcon(); g.setColor(bColor); int offset = 0; if(icon != null && getText() != null) offset = (icon.getIconWidth() + getIconTextGap()); g.fillRect(offset,0,getWidth()-1-offset,getHeight() - 1); if (m_selected) { g.setColor(Color.green); g.drawRect(offset, 0, getWidth()-1-offset, getHeight()-1); } super.paintComponent(g);} }
class IconData {
protected Icon m_icon;
Visuele Gebruikers Omgevingen: Swing 259
258/395
protected Icon m_expandedIcon; protected Object m_data; public IconData(Icon icon, Object data) { m_icon = icon;m_expandedIcon = null;m_data = data;} public IconData(Icon icon, Icon expandedIcon, Object data) { m_icon = icon;m_expandedIcon = expandedIcon; m_data = data;} public Icon getIcon() { return m_icon; } public Icon getExpandedIcon() { return m_expandedIcon!=null ? m_expandedIcon : m_icon;} public Object getObject() { return m_data; } public String toString() { return m_data.toString(); } }
public class Tree99 extends JFrame {
public static ImageIcon ICON_SELF = new ImageIcon("myself.gif"); public static ImageIcon ICON_MALE = new ImageIcon("male.gif"); public static ImageIcon ICON_FEMALE =new ImageIcon("female.gif"); protected JTree m_tree; protected DefaultTreeModel m_model; protected IconCellRenderer m_renderer; protected IconCellEditor m_editor; public Tree99() {super("Ancestor Tree"); setSize(500, 400); DefaultMutableTreeNode top = new DefaultMutableTreeNode( new IconData(ICON_SELF, "Myself")); addAncestors(top);// we voegen steeds twee voorouders toe m_model = new DefaultTreeModel(top); m_tree = new JTree(m_model); m_tree.getSelectionModel().setSelectionMode( TreeSelectionModel.SINGLE_TREE_SELECTION); m_tree.setShowsRootHandles(true); m_tree.setEditable(true); m_renderer = new IconCellRenderer(); m_tree.setCellRenderer(m_renderer); m_editor=new IconCellEditor(m_tree); m_tree.setCellEditor(m_editor); m_tree.setInvokesStopCellEditing(true); m_tree.addMouseListener(new TreeExpander()); JScrollPane s = new JScrollPane(); s.getViewport().add(m_tree); getContentPane().add(s, BorderLayout.CENTER); WindowListener wndCloser = new WindowAdapter() { public void windowClosing(WindowEvent e){ System.exit(0);}}; addWindowListener(wndCloser); setVisible(true); } public boolean addAncestors(DefaultMutableTreeNode node) { if (node.getChildCount() > 0) return false; Object obj = node.getUserObject(); node.add(new DefaultMutableTreeNode( new IconData( ICON_MALE, "Vader van: "+obj.toString()) )); node.add(new DefaultMutableTreeNode( new IconData( ICON_FEMALE, "Moeder van: "+obj.toString()) )); return true;} public static void main(String argv[]) { new Tree99(); }
class TreeExpander extends MouseAdapter { public void mouseClicked(MouseEvent e) {if (e.getClickCount() == 2) // dubbel geklikt? {// we zoeken het pad waarop we geklikt hebben TreePath selPath=m_tree.getPathForLocation(e.getX(),e.getY()); if (selPath == null) return;// niet op een TreePath geklikt DefaultMutableTreeNode node =
Visuele Gebruikers Omgevingen: Swing 260
259/395
(DefaultMutableTreeNode) (selPath.getLastPathComponent()); if (node!=null && addAncestors(node)) {m_tree.expandPath(selPath);//klappen het geselecteerde open m_tree.repaint(); } } }}}
class IconCellEditor extends JLabel implements TreeCellEditor, ActionListener {protected protected protected protected protected protected
JTree m_tree = null; JTextField m_editor = null; IconData m_item = null; int m_lastRow = -1; long m_lastClick = 0; Vector m_listeners = null;
public IconCellEditor(JTree tree) {super(); m_tree = tree; m_listeners = new Vector(); } public Component getTreeCellEditorComponent(JTree tree, Object value, boolean isSelected, boolean expanded, boolean leaf, int row) {DefaultMutableTreeNode node = (DefaultMutableTreeNode)value; IconData obj = (IconData)node.getUserObject(); IconData idata = (IconData)obj; m_item = idata; // Reserve some more space... setText(idata.toString()+" "); // tekst van edit label setIcon(idata.m_icon);//tijdens edit moet er ook een icoon zijn return this; } // BELANGRIJK : de default celleditor maakt er een string van van // vervangt het oorspronkelijk userobject door een string // dit mag niet gebeuren omdat icon wordt bijgehouden in userobject. public Object getCellEditorValue() {if (m_item != null && m_editor != null) m_item.m_data = m_editor.getText(); return m_item; }// we geven hier geen String terug zoals de //standaard CellEditor public boolean isCellEditable(EventObject evt) {if (evt instanceof MouseEvent) {MouseEvent mEvt = (MouseEvent)evt; if (mEvt.getClickCount() == 1) {int row = m_tree.getRowForLocation(mEvt.getX(), mEvt.getY()); if (row != m_lastRow) {m_lastRow = row; m_lastClick = System.currentTimeMillis(); return false; } else if (System.currentTimeMillis()-m_lastClick > 1000) {m_lastRow = -1; m_lastClick = 0;prepareEditor(); mEvt.consume(); return true; } else return false; } } return false; } protected void prepareEditor() {if (m_item == null) return; String str = m_item.toString(); m_editor = new JTextField(str); m_editor.addActionListener(this); m_editor.selectAll(); m_editor.setFont(m_tree.getFont());add(m_editor); revalidate(); TreePath path = m_tree.getPathForRow(m_lastRow); m_tree.startEditingAtPath(path); } protected void removeEditor()
Visuele Gebruikers Omgevingen: Swing 261
260/395
{if (m_editor != null) {remove(m_editor); m_editor.setVisible(false); m_editor = null; m_item = null; } } public void doLayout() {super.doLayout(); if (m_editor != null) {int offset = getIconTextGap(); if (getIcon() != null) offset += getIcon().getIconWidth(); Dimension cSize = getSize(); m_editor.setBounds(offset, 0, cSize.width – offset,cSize.height); } } public boolean shouldSelectCell(EventObject evt) { return true; } public boolean stopCellEditing() {if (m_item != null) m_item.m_data = m_editor.getText(); ChangeEvent e = new ChangeEvent(this); for (int k=0; k<m_listeners.size(); k++) {CellEditorListener l = (CellEditorListener)m_listeners. elementAt(k); l.editingStopped(e); } removeEditor(); return true; } public void cancelCellEditing() {ChangeEvent e = new ChangeEvent(this); for (int k=0; k<m_listeners.size(); k++) {CellEditorListener l = (CellEditorListener)m_listeners. elementAt(k); l.editingCanceled(e); } removeEditor(); } public void addCellEditorListener(CellEditorListener l) {m_listeners.addElement(l); } public void removeCellEditorListener(CellEditorListener l) {m_listeners.removeElement(l); } public void actionPerformed(ActionEvent e) {stopCellEditing(); m_tree.stopEditing(); } } We hebben nu zowel een eigen CellRenderer als een eigen CellEditor. Waarom is de code ingewikkeld? Bij editeren, vervangen we de oorspronkelijke objecten niet door Strings.
Visuele Gebruikers Omgevingen: Swing 262
261/395
belang Componenten = lego-blokjes om programma’s te maken
• Een succes-verhaal: office werkt uitgebreid met componenten voor tekenen, ...die ook in andere programma’s gebruikt worden (componenten van ‘word’ worden ook in ‘powerpoint’ en ‘excel’ gebruikt). • Verhoogt de productiviteit van programmatie enorm.
Componenten • Er zijn componenten om met databanken te werken,... • Voor internettoepassingen worden nu ook componenten gebruikt : java server faces, asp.net (webcontrols)
Even een demo • Sun levert een eenvoudig programma (BeanBox) om het concept van ‘componenten’ te illustreren. • Je kan ermee componenten naar een ontwerpscherm slepen en ze onderling door acties met elkaar verbinden. • De toepassing werkt dadelijk
262/395
1
Het ontwerpscherm • Links staan de componenten om te slepen. • Rechts hun ‘eigenschappen’ (properties)
We slepen een ‘explicit button’ naar ontwerpscherm • We hebben hier vier properties: background, foreground kleur, label, font
De achtergrondkleur instellen • Als je op het ontwerpscherm klikt, verschijnen rechts zijn properties: background, foreground, name. • We veranderen eens de achtergrond
Op de juiste plaats zetten • Je kan de componenten gewoon verslepen en vergroten
Kan gewoon vergroot worden met de muis
263/395
2
Een tweede button • We voegen een tweede button toe, en veranderen de tekst van die button
We voegen ‘juggler’ component toe • De ‘juggler’ component heeft methodes: – -stopJuggling, startJuggling
• Het heeft properties: snelheid van jongleren • De buttons hebben ook events: – ‘button push’ (actionperformed): het gewoon klikken op een button
Een ‘juggler’ • Vervolgens moeten we het event ‘klikken op de button’ verbinden met het oproepen van een methode van het juggler object
De beanbox genereert zelf code • In tegenstelling tot de meeste andere tools die met componenten werken, zal de beanbox code genereren. U verbindt met klikken op de componenten en kiezen van routines die opgeroepen worden wat er moet gebeuren. De beanbox zal de gegenereerde code compileren en dadelijk laten werken.
264/395
3
We kiezen eerst het event • Visual Age van IBM genereert ook code. • Tools van Borland en Sun genereren lege functies die zullen opgeroepen worden wanneer het event zich voordoet. De programmeur moet die lege functies opvullen.
Andere mogelijke events
• We selecteren eerst de button en vervolgens het event waarvoor we actie willen.
Als we op de "Stop the Animation" button klikken (actionPerformed) zullen we een methode van een ander (door ons te kiezen) object oproepen.
Versleep de rode lijn naar een ander object • Je kan nu met de muis aanduiden van welk ander object er een methode zal moeten opgeroepen worden.
Target the selector Line: kies het Juggler component.
265/395
4
Kies methode die in de gegenereerde code wordt opgeroepen. • Kies ‘stopJuggling’
Code generatie • Er wordt code gegenereerd om bij het event ‘klikken op button’ de methode ‘stopJuggling’ van het juggler object op te roepen.
We kiezen de methode die we zullen oproepen als we op "Stop the Animation" klikken. U moet steeds methodes zonder parameters kiezen. (met deze eenvoudige toolkit, kunnen we geen parameters opgeven.) Hoe kent de toolkit deze methodes: gewoon door introspectie: van gelijk welk object kan je zijn methodes opvragen.
Analoog voor de ander button • De toepassing werkt dadelijk. • U kunt steeds jongleren op en af zetten, de snelheid regelen. (property aanpassen).
Dynamisch genereert de toolkit een listenerklasse (ActionListener) en zal deze toevoegen met addActionListener aan de button. Hierdoor zal bij het klikken op de button effectief de methode stopJuggling opgeroepen worden.
Hoe componenten in java verpakken? • Gewoon een zip file die hernoemd wordt in jar file. • In een subdirectory meta-inf staat een bestand ‘manifest.mf’ met daarin de naam van de klasse die de component voorstelt.
266/395
5
De jar bekijken • Met winzip kan je de inhoud van de jar bekijken. • Deze jar bevat naast de juggler klasse ook alle andere benodigde bestanden zoals gif bestanden.
meta-inf/manifest.mf • In het manifest.mf bestand staat de naam van de klasse die de bean vormt. • De naam van deze klasse zal bij de componenten getoond worden. Er wordt aangegeven dat er slechts één klasse (Juggler.class) als bean in tools zoals beanbox mag geladen worden. De rest zijn aldus ondersteunende klassen.
JBuilder 6
Properties bij jbuilder
267/395
6
Events bij jbuilder
Tool: visual cafe
tientallen javabeans, voor databanken, multimedia,...
properties per component; Hier zie je voor de Button enkele gridbagcontraints.
• Door rechts te klikken op een willekeurig component, krijg je een popop menu, waar je 'add interaction' kan kiezen.
interactie tussen visuele en niet visuele component (data van JTable)
GridBagLayo ut
Een gok-toepassing • Eerst eens met beanbox • Vanboven: een gewone knop, bij klikken worden de twee objecten eronder (Willekeurig-object) aangestuurd.
268/395
7
De werkende toepassing • De range van de twee Willekeurig objecten wordt op 20 en 100 gezet. Ze zullen willekeurige getallen binnen deze range genereren
• Willekeurig1 genereert ‘6’. Willekeurig2 genereert ’98’. • Gokker1,2,3 wedden op Willekeurig1. • Gokker 3,4,5 wedden op Willeurig2. • Gokker 3 wedt dus op beide willekeurig objecten
Gokker objecten • Gokker objecten kunnen zich registreren als ‘luisteraar’ bij één of meerdere Willekeurig objecten. • Een Gokker object kan met een methode setDoel tot 100 getallen ingeven waarvan hij hoopt dat ze zullen gegenereerd worden door een willekeurig object
Waarop wedt wie? • • • • •
Gokker 1 : 1, 2 Gokker 2: 10 11 12 13 13 Gokker 3: 13 Gokker 4: 50 5051 51 Gokker 5: 60 61
• Elke keer als we op ‘do gok’ klikken, genereren de twee Willekeurig objecten twee getallen. De gokkers worden dynamisch verwittigd. Als ze winnen verhoogd het getal achter hun naam
269/395
8
Componenten: een dynamisch systeem • Het ligt op voorhand niet vast welke gokker op welk willekeurig object zal gokken. Tijdens uitvoering kan men met de beanbox nieuwe gokker-objecten aanmaken, alsook nieuwe willekeurig objecten en ze met elkaar verbinden op zelf gekozen wijzen. • Het is dus een soort dynamisch lego-blokken systeem.
In JBuilder : zelf blauwe code toevoegen void jButton1_actionPerformed(ActionEvent e) { willekeurig1.werp(); willekeurig2.werp(); } void willekeurig1_gokGebeurd(GokEvent e) { gokker1.gokGebeurd(e); gokker2.gokGebeurd(e); gokker3.gokGebeurd(e); } void willekeurig2_gokGebeurd(GokEvent e) { gokker3.gokGebeurd(e); gokker4.gokGebeurd(e); gokker5.gokGebeurd(e); }
Dezelfde componenten nu met JBuilder 6 • Weeral worden dynamisch objecten gesleept op scherm en verbonden met elkaar
Kode verklaart zichzelf • Klikken op button1: werp oproepen voor willekeurig1 en 2 • GokGebeurd voor willekeurig1: gokker1,2 en 3 verwittigen door gokGebeurd op te roepen
270/395
9
Eerste stap : een event uitdenken • De klasse Willekeurig moet andere objecten verwittigen: de data wordt meegegeven in een Event object import java.awt.*; import java.util.*; public class GokEvent extends EventObject {private int gok; public GokEvent(Object Source,int gok) { super(Source); this.gok=gok; } public int getGok(){return gok;} }
Verplicht overerven
In deze klasse stockeren we de waarde van het gekozen willekeurig getal. Dit getal wordt met dit object doorgegeven aan alle luisteraars.
Tweede stap: een ‘verwittig contract’ vastleggen Welke routine zal een verwittig object oproepen van iedereen die wil verwittigd worden? Dit leggen we vast in een interface contract. Dit moet verplicht de overerven van EventListener. In de interface zet de volledig vrij te kiezen namen van routines die opgeroepen worden. Verplicht is wel dat elk van deze routines als parameter een object moet hebben dat overerft van EventObject (GokEvent voldoet hieraan). package be.khleuven.rega.ti2.beans.events.visual.gokker; public interface GokListener extends java.util.EventListener{ public void gokGebeurd(GokEvent gok); }
Klasse Gokker
Voorwaarde 3
• Als deze klasse wilt verwittigd worden, moet het de afgesproken ‘verwittig contract’ interface GokListener implementeren.
• Alle javabeans moeten de interface Serializable implementeren. Dit is gelukkig eenvoudig: het is een lege interface.
• Hiervoor moet het aldus de methode ‘gokGebeurd’ definiëren. Deze methode zal immers opgeroepen worden door willekeurig objecten.
• Waarom? Dynamische systemen die met componenten werken, geven de mogelijkheid om een complex netwerk van objecten op te bouwen. Als elk van deze objecten de interface Serializable implementeerd kan dit netwerk eenvoudig gesaved en terug herladen worden. Dit is belangrijk voor tools.
271/395
10
Properties: eenvoudig : voorzie een set en get methode
Voorwaarde 4 • Elke javabean klasse moet een ‘default constructor’ hebben. Dit is een constructor zonder parameters. Dit is nodig om de tools toelaten om een object aan te maken gewoon door te klikken en te slepen naar een ontwerpscherm. In dat geval wordt de default constructor opgeroepen.
• Gelukkig zijn properties in java eenvoudig te maken: je moet gewoon een set en een get methode voorzien. • Vb: de properties ‘naam’ heeft de bijhorende methodes void setNaam(String s) en String getNaam(). Dit is dan een String property. • Let op de ‘n’ die een ‘N’ wordt.
Verwittig
Klasse Gokker package be.khleuven.rega.ti2.beans.events.visual.gokker; import javax.swing.*; import java.awt.*; public class Gokker extends JPanel implements GokListener,java.io.Serializable { private String naam; private int aantalgewonnen; private int doel[];private int aantaldoelen; public void setDoel(int ndoel) {if(aantaldoelen<100)doel[aantaldoelen++] = ndoel; public int getDoel() {if(aantaldoelen>0)return doel[aantaldoelen-1]; return 0; }
}
public void gokGebeurd(GokEvent ge) { boolean gewonnen=false; contract int gok=ge.getGok(); for (int i = 0; i < aantaldoelen& ! gewonnen; i++) { if( doel[i]==gok) gewonnen=true; } if(gewonnen) { aantalgewonnen++; repaint(); System.out.println("gokker " + naam + ": " + gok); } } Niet wijzigbare public int getAantalgewonnen() { property return aantalgewonnen; } public void print(){ System.out.println(naam+" heeft "+aantalgewonnen +" keer gewonnen"); }
Property ‘doel’
272/395
11
Willekeurig public void paint(Graphics g) {super.paint(g); g.drawString(naam+" "+aantalgewonnen,10,20); for(int i=0;i
• De klasse moet een lijst (vector) bijhouden waarin de referentie van alle objecten die moeten verwittigd worden, worden bijgehouden.
public void setNaam(String naam){this.naam=naam;} public String getNaam(){return naam;}
Property ‘naam’
public Gokker() {naam=""; aantalgewonnen=0; aantaldoelen=0; doel=new int[100]; } }
• De methode addGokListener en removeGokListener moeten worden toegevoegd. (het event noemt dan ‘gok’)
Default constructor
De klasse Willekeurig • De routines moeten echt met add en remove beginnen en eindigen op Listener. Anders herkennen de tools het event niet automatisch. • De parameter moet overerven van EventListener (anders herkennen de tools weeral niet deze registratieroutines).
package be.khleuven.rega.ti2.beans.events.visual.gokker; import java.util.Random; import java.util.Vector; import java.io.Serializable; import javax.swing.*; import java.awt.*; import java.awt.event.*; public class Willekeurig extends JPanel implements Serializable { private int gok; private Vector list; private Random r=new Random(); private int range; public void werp() { gok=r.nextInt(range);// willekeurig getal fireGokEvent(gok); repaint(); }
273/395
12
public
public
public
public public public
void addGokListener(GokListener l) {if(!list.contains(l)) list.addElement(l);} void removeGokListener(GokListener l) {if(list.contains(l)) list.removeElement(l);} void fireGokEvent(int gok) { for(int i=0;i< list.size();i++) {GokListener gl= (GokListener) list.elementAt(i); gl.gokGebeurd(new GokEvent(this,gok)); } Property ‘range’ } void setRange(int range){ this.range=range;} int getRange(){return range;} Willekeurig() { list = new Vector(); setSize(100,100); setVisible(true); Default constructor
Laatste stap: jar file • Omdat een component kan gebruik maken van hulpklassen, moet u wel aangeven welke classe de hoofdklasse is. Dit gebeurt in een manifest file. • Maak een bestand meta-inf/manifest.mf
} public void paint(Graphics g) { super.paint(g); g.drawString(""+gok,10,getHeight()/2); } }
meta-inf/manifest.mf Name: be.khleuven.rega.ti2.beans.events.visual.gokker.Willekeurig.class Java-Bean: True Name: be.khleuven.rega.ti2.beans. events.visual.gokker.Gokker.class Java-Bean: True
winzip • Vervolgens zipt u alle classfiles + metainf/manifest.mf • Rename naar .jar en klaar
Steeds lege lijn achter zetten
• Tools hebben de mogelijkheid om jar files met javabeans erin te laden en te laten werken.
274/395
13
Componenten gemaakt uit componenten
Draaiknop component
• We maken eerst een draaiknop: als we erop klikken, draait hij. We maken er een javabean van. • Vervolgens maken we met deze component een andere component: een brandkast samengesteld uit een geheime code en drie draaiknoppen.
Data voor event public class DraaiEvent extends EventObject {private int waarde; public DraaiEvent(Object Source,int waarde) { super(Source); this.waarde=waarde; } public int getWaarde(){return waarde;} }
public interface DraaiListener extends java.util.EventListener { public void gedraaid(DraaiEvent draaiEvent); }
275/395
14
package be.khleuven.vgo.draaiknop; import java.awt.*; import javax.swing.*; import javax.swing.border.*; import java.awt.event.*; import java.util.*; public class Draaiknop extends JPanel implements java.io.Serializable { private Vector list; private int waarde=1; MediaTracker tracker; private Image img[]=new Image[12]; public void draai() { waarde=waarde+1; if (waarde>12)waarde=1; fireDraaiEvent(waarde); repaint(); } public void addDraaiListener(DraaiListener l) { if (!list.contains(l)) list.addElement(l); } public void removeDraaiListener(DraaiListener l) { if (list.contains(l)) list.removeElement(l); }
public void setWaarde(int waarde){this.waarde=waarde;repaint();} public int getWaarde(){return waarde;} public void fireDraaiEvent(int waarde) {for(int i=0;i<list.size();i++) {DraaiListener dl=(DraaiListener) list.elementAt(i); dl.gedraaid(new DraaiEvent(this,waarde)); } } public Draaiknop() {list = new Vector(); Class tk=this.getClass(); img[0]=new ImageIcon(tk.getResource("knop1.jpg")).getImage(); img[1]=new ImageIcon(tk.getResource("knop2.jpg")).getImage(); img[2]=new ImageIcon(tk.getResource("knop3.jpg")).getImage(); img[3]=new ImageIcon(tk.getResource("knop4.jpg")).getImage(); img[4]=new ImageIcon(tk.getResource("knop5.jpg")).getImage(); img[5]=new ImageIcon(tk.getResource("knop6.jpg")).getImage(); img[6]=new ImageIcon(tk.getResource("knop7.jpg")).getImage(); img[7]=new ImageIcon(tk.getResource("knop8.jpg")).getImage(); img[8]=new ImageIcon(tk.getResource("knop9.jpg")).getImage(); img[9]=new ImageIcon(tk.getResource("knop10.jpg")).getImage(); img[10]=new ImageIcon(tk.getResource("knop11.jpg")).getImage(); img[11]=new ImageIcon(tk.getResource("knop12.jpg")).getImage();
De component Brandkast setSize(50,50); setPreferredSize(new Dimension(50,50)); setBorder(new EtchedBorder()); addMouseListener(new MouseAdapter() {public void mousePressed(MouseEvent me) {draai();} }); setVisible(true); } public void paint(Graphics g) {super.paint(g); g.drawImage(img[waarde-1],0,0,this); g.drawString(""+waarde,getWidth()/2-5,getHeight()/2+3); } }
• De brandkast component omvat drie draaiknoppen. De brandkast heeft een geheime kode. Als de gebruiker de code raadt, wordt de achtergrond groen gekleurd. De brandkast gaat open. • De brandkast is een component.
276/395
15
package be.khleuven.vgo.draaiknop; import java.awt.event.*; import java.util.EventObject; public class BrandkastEvent extends EventObject {private int waarde1; private int waarde2; private int waarde3; public BrandkastEvent(Object Source,int waarde1,int waarde2, int waarde3) { super(Source); this.waarde1=waarde1;this.waarde2=waarde2;this.waarde3=waarde3; } public int getWaarde1(){return waarde1;} public int getWaarde2(){return waarde2;} public int getWaarde3(){return waarde3;} }
package be.khleuven.vgo.draaiknop; public interface BrandkastListener extends java.util.EventListener { public void gedraaid(BrandkastEvent draaiEvent); public void geopend(BrandkastEvent draaiEvent); }
package be.khleuven.vgo.draaiknop; import java.awt.*; import javax.swing.*; import javax.swing.border.*; import java.awt.event.*; import java.util.*; public class Brandkast extends JPanel implements java.io.Serializable { private Vector list; private int waarde1=1,waarde2=1,waarde3=1; private int geheim1,geheim2,geheim3; private Draaiknop k1,k2,k3; public void addBrandkastListener( BrandkastListener l) { if (!list.contains(l)) list.addElement(l); } public void removeBrandkastListener(BrandkastListener l) { if (list.contains(l)) list.removeElement(l); } public void setWaarde1(int waarde) {this.waarde1=waarde;k1.setWaarde(waarde);repaint();}
277/395
16
public void setWaarde2(int waarde) {this.waarde2=waarde;k2.setWaarde(waarde);repaint();} public void setWaarde3(int waarde) {this.waarde3=waarde;k3.setWaarde(waarde);repaint();} public int getWaarde1(){return waarde1;} public int getWaarde2(){return waarde2;} public int getWaarde3(){return waarde3;} public void setGeheim1(int g){this.geheim1=g;repaint();} public void setGeheim2(int g){this.geheim2=g;repaint();} public void setGeheim3(int g){this.geheim3=g;repaint();} public int getGeheim1(){return geheim1;} public int getGeheim2(){return geheim2;} public int getGeheim3(){return geheim3;}
public void fireBrandkastEvent(int waarde1, int waarde2,int waarde3) {setBackground(Color.red); if(geheim1==waarde1&&geheim2==waarde2&&geheim3==waarde3) setBackground(Color.green); for(int i=0;i<list.size();i++) {BrandkastListener dl= (BrandkastListener) list.elementAt(i); dl.gedraaid( new BrandkastEvent(this,waarde1,waarde2,waarde3)); if(geheim1==waarde1&&geheim2==waarde2&&geheim3==waarde3) {dl.geopend( new BrandkastEvent(this,waarde1,waarde2,waarde3)); setBackground(Color.green); } } Iedereen verwittigen } indien geopend of public Brandkast(){this(1,1,1);}
gedraaid
public Brandkast(int geheim1,int geheim2,int geheim3) {list = new Vector(); this.geheim1=geheim1;this.geheim2=geheim2; this.geheim3=geheim3; setSize(200,70); setPreferredSize(new Dimension(200,70)); setBorder(new EtchedBorder()); setBackground(Color.red); k1=new Draaiknop();k2=new Draaiknop();k3=new Draaiknop(); k1.addDraaiListener(new DraaiListener() {public void gedraaid(DraaiEvent me) {waarde1=me.getWaarde(); fireBrandkastEvent(waarde1,waarde2,waarde3);}}); k2.addDraaiListener(new DraaiListener() {public void gedraaid(DraaiEvent me) {waarde2=me.getWaarde(); fireBrandkastEvent(waarde1,waarde2,waarde3);}}); k3.addDraaiListener(new DraaiListener() {public void gedraaid(DraaiEvent me) {waarde3=me.getWaarde(); fireBrandkastEvent(waarde1,waarde2,waarde3);}}); fireBrandkastEvent(geheim1,geheim2,geheim3); add(k1);add(k2);add(k3); setVisible(true); } }
Manifest.mf Name: be.khleuven.vgo.draaiknop.DraaiknopJar.class Java-Bean: True Name: be.khleuven.vgo.draaiknop.BrandkastJar.class Java-Bean: True Achter elke javabean declaratie moet een lege lijn komen. We hebben de volgende directory structuur: meta-inf manifest.mf be khleuven vgo draaiknop Classes Jpg’s Het geheel steken we in een zip file die we herbenoemen naar een jar. Klaar.
278/395
17
• De beanbox dedecteert de nieuwe javabeans. We selecteren een Draaiknop en een Brandkast.
Een laatste toepassing
1 brandkast 1 draaiknop
• We gaan met beanbox even een kleine toepassing maken waarbij we de componenten draaiknop en brandkast gebruiken. • Telkens we de brandkast openen door zijn code te raden, wordt de extra draaiknop verwittigd en draait hij 1 positie verder
279/395
18
We stellen properties in
We verbinden event ‘geopend’ met ‘draai’ methode
We slepen naar de draaiknop:
• Er verschijnt een lijstje van routine die kunnen opgeroepen worden, we kiezen ‘draai’:
• Telkens de brandkast geopend wordt, zal de draaiknop rechts ervan verwittigd worden en zal de ‘draai’ routine worden opgeroepen. Deze draaiknop geeft aldus weer hoeveel maal de brandkast geopend werd.
280/395
19
De kracht van xml Het gedrag van uw programma aanpassen met een teksteditor
• XML: het lijkt magisch. Er wordt veel over gepraat. Maar eigenlijk is het niet moeilijk. U kunt ermee gegevens op een hiërarchische wijze structureren. • De grootste uitdaging van xml is niet zijn syntax, maar uw fantasie. U moet xml in toepassingen herkennen.
Xml: hiërarchische gegevens : gelijk welke hiërarchische gegevens
<slot> <paragraaf> je krijgt aldus een informatie boom waarbij de omsluitende tags aangeven wat ze omsluiten <paragraaf> de gedefinieerde taal mag recursief zijn: je kan bijvoorbeeld in de tag 'midden' opgeven dat hierin terug een 'verhaal' tag mag komen.
281/395
1
Ander hiërarchisch voorbeeld
Een lijst initialiseren vanuit xml
Peeters piet Mertens marc Jespers jan
Delen van uw programma naar xml bestand overbrengen • Configuratiebestanden zijn tegenwoordig meestal in xml – Opties van uw programma – highscores
Kleuren, meerdere talen: gebruik xml
282/395
2
Een dynamisch menu <menu>
Abstracter en abstracter • Je kan zelfs een volledige opbouw van een scherm in een xml bestand steken. (Je kan erin steken wat je wilt. Uw eigen fantasie is de enige beperking.) Wel moet je eerst een klein xml-taaltje uitvinden.
Je weet op voorhand niet welke opties er op uw menu gaan staan: • In dit geval gaat het programma bovenstaande xml file lezen, en dynamisch een menu gegeneren. Als actie gekoppeld aan het menu, wordt een methode opgeroepen die bij een opgegeven klasse hoort.
Scherm layout in xml (zie xaml verderop)
283/395
3
Gegevens uitwisselen tussen toepassingen
Xml als onafhankelijke gegevensdrager
• Als je gegevens naar een andere toepassing of naar een andere gebruiker wilt sturen, kunt u deze gegevens structuren met behulp van xml.
• Er zijn tools (vb WebToDate van DataBacker) die toelaten de inhoud van een grote website op te geven. Deze inhoud wordt dan in een xml bestand bijgehouden.
Vanuit java xml maken
• Een ander deel van het programma gaat dan dit xml bestand lezen en volgens een keuze layout een echte website genereren.
De klasse Element
• Er zijn drie belangrijke klassen in het jdom package: – Element – Attribute – Document Enkele constructoren en functies om ‘content toe te voegen’
284/395
4
Alle sub-elementen onder een element opvragen
vb1
Inhoud of sub-elementen weglaten
// xml uitvoer naar bestand a.xml // gebruik van geneste Elementen // jdom : eenvoudig te gebruiken package om xml te bewerken (www.jdom.org) import java.io.*; import org.jdom.*; import org.jdom.output.*; public class vb1 {public static void main(String args[]) {//maken van xml jdom object in geheugen Element a = new Element("A");// Element b = new Element("B");// //tekst komt tussen de omsluitende tags b.addContent("b_inhoud");// b_inhoud
285/395
5
Element b2 = new Element("BB");//
Element c = new Element("C");//
//een element komt onder een ander element a.addContent(b);// b_inhoud a.addContent(b2);// // b_inhoud //
// // b_inhoud //
a.addContent(b3);// // b_inhoud //
// // b_inhoud //
Document doc=new Document(a);
a.xml werd gecreëerd
//overbrengen naar externe file XMLOutputter uit = new XMLOutputter(); uit.setIndent(" "); uit.setNewlines(true); //output heeft als tweede parameter een OutputStream of een Writer FileOutputStream bestand=null; try{bestand= new FileOutputStream("a.xml");} catch(FileNotFoundException ee){ee.printStackTrace();} try{ uit.output(doc , bestand ); } catch(IOException e){e.printStackTrace();} } }
Mag gelijk welke OutputStream of Writer zijn
b_inhoud
286/395
6
Nodige voorwaarden
De klasse Attribute
• Surf naar www.jdom.org, download jdom.jar waarin de nodige klassen zitten. • Om te compileren: set classpath=.;jdom.jar als jdom.jar in zelfde directory staat als te compileren bestand.
import java.io.*; import org.jdom.*; import org.jdom.output.*; public class vb4 {public static void main(String args[]) {//maken van xml jdom object in geheugen Element a = new Element("A"); // a.setAttribute("a1","a1 waarde");// a.setAttribute("a2","a2 waarde");// Element b = new Element("B"); // b.setAttribute("b1","b1 waarde");// b.setAttribute("b2","b2 waarde");// b.addContent("b_inhoud");// b_inhoud
a.addContent(b); // // b_inhoud // Document doc=new Document(a); //overbrengen naar externe file XMLOutputter uit = new XMLOutputter(); uit.setIndent(" "); uit.setNewlines(true); // output heeft als tweede parameter een OutputStream of een Writer FileOutputStream bestand=null; try{bestand= new FileOutputStream("a.xml");} catch(FileNotFoundException ee){ee.printStackTrace();} try{ uit.output(doc , bestand ); } catch(IOException e){e.printStackTrace();} } } /* a.xml: b_inhoud */
287/395
7
Intern worden Attribute objecten gebruikt
De klasse Document
a.setAttribute("a1","a1 waarde");
• Is gelijkwaardig aan: A.setAttribute(new Attribute(“a1”,”a1 waarde”) );
Een xml bestand lezen Pizza.xml
Dit is een ‘mixed content’ xml bestand. De tekst ‘napolitain’ staat niet tussen tags (De tag pizza heeft zowel gewone tekst ‘napolitain’ als subelementen).
import import import import import
java.io.*; org.jdom.*; org.jdom.output.*; org.jdom.input.*; java.util.*;
public class jdom_3 {public static void main(String args[]) {try {SAXBuilder builder = new SAXBuilder(false); // false:geen validatie om na te gaan of er fouten zijn // we halen het document in het geheugen Document doc = builder.build( "pizza.xml"); // nu kunnen we het ontleden Element pizza=doc.getRootElement();
288/395
8
Attributen lezen System.out.println("korst=“ +pizza.getAttribute("korst").getValue()); //-----------------------------------------------------------// korst=hard //----------------------------------------------------------System.out.println("pikant=“ +pizza.getAttribute("pikant").getValue()); //-----------------------------------------------------------// pikant=ja //------------------------------------------------------------
Is ook goed: pizza.getAttributeValue(“pikant”);
Een element opvragen // eerste beleg? System.out.println("eerste beleg:“ +pizza.getChild("beleg").getText()); //----------------------------------------------// eerste beleg:veel kaas //-----------------------------------------------
Als je op voorhand niet weet hoeveel attributen er zijn: alles aflopen //alle attributen van pizza List attributen=pizza.getAttributes(); ListIterator it=attributen.listIterator(); while (it.hasNext()) { Attribute attr=(Attribute)it.next(); System.out.println("Attribute:“ +attr.getName()+" waarde:"+attr.getValue()); } //----------------------------------------------------// Attribute:korst waarde:hard // Attribute:pikant waarde:ja //-----------------------------------------------------
Geneste oproepen pizza.getChild("beleg") .getChild(“subbeleg) .getChild(“subsubbeleg).getText());
Je kan in één bevel ineens de tekst van drie niveaus diep opvragen.
Is ook goed: pizza.getChildText("beleg");
289/395
9
Als je niet weet hoeveel elementen: alles aflopen, wel op type testen List alles=pizza.getContent(); ListIterator it3=alles.listIterator(); while (it3.hasNext()) { System.out.println(".......................;;;"); Object el=it3.next();
‘napolitain’ is van type Text, niet van type Element
if(el instanceof Text) System.out.println(((Text)el).getText() );
if(el instanceof Element) System.out.println(((Element)el).getName()+":“ +((Element)el).getText() ); }
Resultaat //-----------------------------------------------------------// .......................;;; // // napolitain // // .......................;;; // beleg:veel kaas // .......................;;; // deel pizza: type:org.jdom.Text // // .......................;;; // beleg:veel vlees // .......................;;; // //-----------------------------------------------------------} catch(JDOMException e){e.printStackTrace();} } }
Andere klassen • Best steek je in een Element andere Elementen • Andere mogelijkheden: – Text – Comment •
– CDATA • ...