GUI bileşenleri: JFrame, JLabel, JTextField, JButton ve layout'lar
Swing ile pencere kurmak: bileşenler, içerik paneli ve FlowLayout, BorderLayout, GridLayout. Sınavda kod okuma ve 'hangi layout' soruları.
Bu hafta grafik arayüzün (graphical user interface, GUI) parçalarını kuruyoruz: pencere, yazı, metin kutusu, buton ve bunları yerleştiren layout’lar. Tıklamaya tepki vermek (event handling) gelecek haftanın konusu. Sınavda bu konu genelde kod okuma olarak gelir: “bu pencerede ne görünür”, “hangi layout”, “bu satır neden derlenmiyor”. Ezberlemen gereken çok şey yok; birkaç varsayılanı ve layout’ların davranışını bilmen yeterli.
Altın kurallar:
- Her şey String.
JTextField.getText()String döner; sayı istiyorsanInteger.parseInt. - JPanel varsayılanı FlowLayout, JFrame’in içerik paneli BorderLayout.
- BorderLayout’ta her bölgeye tek bileşen. Konumsuz
addCENTER’a gider, öncekini ezer. setVisible(true)en son. Kapatınca programın bitmesi içinEXIT_ON_CLOSE.
İPUCU · Swing mi JavaFX mi? Deitel’in kitabı Swing ile anlatır (yeni baskılarda JavaFX bölümleri de var). Bu sayfa Swing kullanıyor; JDK ile birlikte gelir. Hocan JavaFX gösteriyorsa kavramlar birebir aynı:
Stage≈ JFrame,Scene≈ içerik paneli,Label/TextField/Button, layout yerineHBox,VBox,GridPane,BorderPane.
Swing’in yapısı
GUI’deki her parça bir bileşendir (component). Bileşenleri tutan bileşenlere kap (container) denir. JFrame en dıştaki pencere, JPanel ise içine bileşen koyduğun ve pencerenin içine yerleştirdiğin bir kap.
| Sınıf | Ne işe yarar? | Sık metotlar |
|---|---|---|
JFrame | Başlıklı, kapatılabilir pencere | setSize, pack, setVisible, setLayout, add, setDefaultCloseOperation |
JLabel | Değiştirilemez yazı (ya da ikon) | setText, getText |
JTextField | Tek satırlık metin girişi | getText, setText, setEditable |
JPasswordField | Yıldızlı şifre girişi | getPassword (char[] döner) |
JTextArea | Çok satırlı metin | append, getText |
JButton | Tıklanabilir buton | setText, addActionListener (6. hafta) |
JCheckBox, JRadioButton | Seçim kutuları | isSelected |
JPanel | Bileşen grubu tutan kap | add, setLayout |
Kalıtım zinciri sınavda “is-a” sorusu olarak çıkabilir:
java.awt.Component
└─ java.awt.Container
├─ java.awt.Window ─ java.awt.Frame ─ javax.swing.JFrame
└─ javax.swing.JComponent
├─ JLabel
├─ JPanel
├─ JTextComponent ─ JTextField ─ JPasswordField
└─ AbstractButton ─ JButton
└─ JToggleButton ─ JCheckBox, JRadioButton
JButton jb = new JButton("OK");
System.out.println(jb instanceof JComponent);
System.out.println(jb instanceof Container);
System.out.println(jb instanceof Component);
Object lbl = new JLabel();
System.out.println(lbl instanceof AbstractButton);
System.out.println(new JCheckBox() instanceof AbstractButton);
true
true
true
false
true
İlk pencere
Pencere yazmanın standart yolu JFrame’den türeyen bir sınıf açmak ve bileşenleri constructor’da kurmaktır. Bileşenleri alan olarak tutarsan event handler’lar (6. hafta) onlara erişebilir.
import java.awt.*;
import javax.swing.*;
public class LoginFrame extends JFrame {
private final JLabel userLabel = new JLabel("User:");
private final JTextField userField = new JTextField(12);
private final JLabel passLabel = new JLabel("Password:");
private final JPasswordField passField = new JPasswordField(12);
private final JButton loginButton = new JButton("Login");
public LoginFrame() {
super("Login"); // pencere başlığı
setLayout(new FlowLayout()); // varsayılan BorderLayout'u değiştir
add(userLabel);
add(userField);
add(passLabel);
add(passField);
add(loginButton);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(260, 140);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> new LoginFrame().setVisible(true));
}
}
SwingUtilities.invokeLater pencereyi Swing’in kendi iş parçacığında (event dispatch thread) kurar; kitaplardaki doğru kalıp budur. Sınavda new LoginFrame().setVisible(true); yazman da kabul görür.
TUZAK · kapanınca program bitmiyor. JFrame’in varsayılan kapanış davranışı
HIDE_ON_CLOSE: pencere görünmez olur ama JVM çalışmaya devam eder.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE)yazmayı unutma.
Metin kutusundan değer okumak
Kullanıcının yazdığı her şey String olarak gelir. Sayısal işlem için dönüştürmen gerekir; dönüştürülemeyen metin NumberFormatException fırlatır.
JTextField field = new JTextField(10);
System.out.println("[" + field.getText() + "] cols=" + field.getColumns());
field.setText("Deniz Yilmaz Uzun Bir Isim");
System.out.println(field.getText().length()); // 10 sütun bir sınır değil
JTextField num = new JTextField("12");
int n = Integer.parseInt(num.getText());
System.out.println(n * 2);
num.setText("12a");
try {
Integer.parseInt(num.getText());
} catch (NumberFormatException e) {
System.out.println("NumberFormatException: " + e.getMessage());
}
[] cols=10
26
24
NumberFormatException: For input string: "12a"
Layout yöneticileri
Swing’de bileşenleri piksel piksel konumlandırmazsın. Her kabın bir layout yöneticisi (layout manager) vardır; bileşenlerin nereye ve hangi boyutta oturacağına o karar verir. Pencere büyüyüp küçüldüğünde yerleşimi yeniden hesaplar.
| Layout | Yerleşim | Varsayılan olduğu yer | Ekleme |
|---|---|---|---|
FlowLayout | Soldan sağa sıra, satır dolunca alta; bileşen kendi tercih ettiği boyutta | JPanel | add(c) |
BorderLayout | 5 bölge: NORTH, SOUTH, EAST, WEST, CENTER; CENTER kalan alanı doldurur | JFrame içerik paneli | add(c, BorderLayout.SOUTH) |
GridLayout | Eşit boyutlu hücrelerden ızgara, soldan sağa yukarıdan aşağı | yok | add(c) |
JPanel panel = new JPanel();
System.out.println(panel.getLayout().getClass().getSimpleName());
JRootPane root = new JRootPane(); // JFrame'in içerik panelini kuran yapı
System.out.println(root.getContentPane().getLayout() instanceof BorderLayout);
FlowLayout
true
FlowLayout
En basiti. Bileşenler eklenme sırasıyla yan yana dizilir, varsayılan olarak ortalanır. new FlowLayout(FlowLayout.LEFT) sola yaslar. Pencere daralırsa sığmayan bileşen alt satıra kayar. Formlar için pratik ama düzen pencere boyutuna göre değiştiği için hassas tasarımda yetersiz.
BorderLayout
Pencereyi beş bölgeye ayırır. Kuzey ve güney tam genişlik alır, doğu ve batı kalan yüksekliği, merkez geri kalan her şeyi. Bölge vermezsen add(c) merkeze ekler.
JPanel bp = new JPanel(new BorderLayout());
JButton b1 = new JButton("One"), b2 = new JButton("Two");
bp.add(b1, BorderLayout.CENTER);
bp.add(b2, BorderLayout.CENTER); // b1'in yerine geçer
BorderLayout bl = (BorderLayout) bp.getLayout();
System.out.println(bp.getComponentCount());
System.out.println(((JButton) bl.getLayoutComponent(BorderLayout.CENTER)).getText());
2
Two
Bir bölgeye birden fazla bileşen koymanın yolu araya panel sokmak:
public EditorFrame() {
super("Editor");
add(new JLabel("Title bar text"), BorderLayout.NORTH);
add(new JTextArea(10, 30), BorderLayout.CENTER);
JPanel buttons = new JPanel(); // FlowLayout
buttons.add(new JButton("Save"));
buttons.add(new JButton("Cancel"));
add(buttons, BorderLayout.SOUTH); // iki buton tek bölgede
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
pack(); // tercih edilen boyuta göre küçült
}
pack() pencereyi bileşenlerin tercih ettiği boyuta göre ayarlar; setSize yerine kullanılabilir.
GridLayout
new GridLayout(satır, sütun) ya da boşluklu new GridLayout(satır, sütun, hgap, vgap). Bütün hücreler eşit boyutlu, bileşenler hücreyi tamamen doldurur. 300 x 100 piksellik bir panelde 2 x 3 ızgara:
JPanel gp = new JPanel(new GridLayout(2, 3));
for (int i = 1; i <= 6; i++) gp.add(new JButton("B" + i));
gp.setSize(300, 100);
gp.doLayout();
for (Component c : gp.getComponents()) {
Rectangle r = c.getBounds();
System.out.println(((JButton) c).getText() + " x=" + r.x + " y=" + r.y
+ " w=" + r.width + " h=" + r.height);
}
B1 x=0 y=0 w=100 h=50
B2 x=100 y=0 w=100 h=50
B3 x=200 y=0 w=100 h=50
B4 x=0 y=50 w=100 h=50
B5 x=100 y=50 w=100 h=50
B6 x=200 y=50 w=100 h=50
TUZAK · önce satır.
GridLayout(2, 3)2 satır 3 sütun demek, tersi değil. Matrislerdeki gibi düşün: satır × sütun.
İç içe paneller
Gerçek ekranlar tek bir layout’la çıkmaz. Yöntem: ekranı bölgelere ayır, her bölgeye kendi layout’u olan bir JPanel koy. Örneğin bir hesap makinesi: pencere BorderLayout, NORTH’ta sonuç için JTextField, CENTER’da GridLayout(4, 3, 5, 5) ile tuş paneli.
setLayout(new GridLayout(4, 3, 5, 5));
String[] keys = {"1","2","3","4","5","6","7","8","9","*","0","#"};
for (String k : keys) add(new JButton(k));
Sık yapılan hatalar
| Yanlış düşünce | Doğrusu |
|---|---|
int x = field.getText(); | String döner: Integer.parseInt(field.getText()) |
JTextField(10) en fazla 10 karakter alır | 10 sütunluk genişlik; sınır değil |
| JFrame’e üç buton add ettim, üçü görünür | Varsayılan BorderLayout: hepsi CENTER’a, sadece son görünür |
| JPanel de BorderLayout ile gelir | JPanel varsayılanı FlowLayout |
GridLayout(2, 3) 2 sütun 3 satır | 2 satır, 3 sütun |
| Pencere kapandı, program bitti | EXIT_ON_CLOSE yazmadıysan çalışmaya devam eder |
setVisible(true) en başta olabilir | Bileşenleri ekledikten sonra, en son çağır |
String s = pw.getPassword(); | char[] döner |
GUI açmak için main gerekmez | Program yine main ile başlar, pencereyi orada yaratırsın |
Kendini test et
Sınavdaki gibi İngilizce sorular. Önce kâğıt kalemle çöz, sonra cevabı aç.
What is the default layout manager of (a) a JPanel and (b) the content pane of a JFrame?
Cevabı göster
new JPanel() FlowLayout ile gelir: bileşenler yan yana, satır dolunca alta. JFrame'in içerik paneli (content pane) BorderLayout ile gelir: add(c) konum belirtmezsen CENTER'a koyar. Bu ikisini karıştırmak, "neden sadece son butonum görünüyor" sorusunun kaynağıdır.
What is printed?
JPanel bp = new JPanel(new BorderLayout());
JButton b1 = new JButton("One"), b2 = new JButton("Two");
bp.add(b1, BorderLayout.CENTER);
bp.add(b2, BorderLayout.CENTER);
BorderLayout bl = (BorderLayout) bp.getLayout();
System.out.println(bp.getComponentCount());
System.out.println(((JButton) bl.getLayoutComponent(BorderLayout.CENTER)).getText());Cevabı göster
Panel iki bileşeni de içinde tutar (count 2). Ama BorderLayout'un her bölgesinde tek bileşen olur; aynı bölgeye ikinci ekleme birincinin yerini alır. Ekranda sadece "Two" görünür. Bir bölgeye birden fazla şey koymak istiyorsan önce onları bir JPanel'e koy, paneli bölgeye ekle.
A JPanel uses new GridLayout(2, 3) and buttons B1 to B6 are added in order. The panel is 300 x 100 pixels. Where is B4?
Cevabı göster
GridLayout(satır, sütun): önce satır sayısı. Hücreler eşit boyutlu: 300 / 3 = 100 genişlik, 100 / 2 = 50 yükseklik. Doldurma soldan sağa, yukarıdan aşağı: B1 B2 B3 ilk satır, B4 ikinci satırın başı.
Find the compile error.
JTextField f = new JTextField("7");
int x = f.getText();Cevabı göster
javac: incompatible types: String cannot be converted to int. Metin kutusundan gelen her şey String'dir. Sayı için int x = Integer.parseInt(f.getText()); yaz.
What is printed?
JTextField num = new JTextField("12");
System.out.println(Integer.parseInt(num.getText()) * 2);
num.setText("12a");
try {
Integer.parseInt(num.getText());
} catch (NumberFormatException e) {
System.out.println("NumberFormatException: " + e.getMessage());
}Cevabı göster
"12" sayıya çevrilir, 24 basılır. "12a" sayı değil, parseInt NumberFormatException fırlatır; mesajı For input string: "12a". GUI'de kullanıcı her şeyi yazabilir, parse'ı try/catch içine al (9. hafta).
JTextField field = new JTextField(10); What does 10 mean?
- The field holds at most 10 characters
- The initial text is "10"
- The preferred width is about 10 columns
- The field is 10 pixels wide
Cevabı göster
int parametreli constructor sütun sayısını (columns) verir: kutunun tercih edilen genişliği yaklaşık 10 karakterlik. Uzunluk sınırı değildir; 26 karakterlik metin de yazılabilir (getText().length() 26 döner). Metin vermek için new JTextField("10").
Find the compile error.
JPasswordField p = new JPasswordField();
String s = p.getPassword();Cevabı göster
javac: incompatible types: char[] cannot be converted to String. Şifre güvenlik için String değil char dizisi olarak verilir (String bellekte değişmeden kalır, diziyi sıfırlayabilirsin). Gerekirse new String(p.getPassword()).
What is printed?
JButton jb = new JButton("OK");
System.out.println(jb instanceof JComponent);
System.out.println(jb instanceof Component);
Object lbl = new JLabel();
System.out.println(lbl instanceof AbstractButton);
System.out.println(new JCheckBox() instanceof AbstractButton);Cevabı göster
Swing hiyerarşisi: Component ← Container ← JComponent ← AbstractButton ← JButton / JToggleButton ← JCheckBox. JButton hem JComponent hem Component'tir. JLabel AbstractButton'dan türemez: false. JCheckBox bir AbstractButton'dır. (Değişken JLabel tipinde olsaydı instanceof AbstractButton derlenmezdi: iki tip aynı çizgide değil.)
Find the problem: the window below appears empty and the program keeps running after it is closed.
public class App {
public static void main(String[] args) {
JFrame f = new JFrame("Demo");
f.setVisible(true);
f.add(new JLabel("Hello"));
f.setSize(200, 100);
}
}Cevabı göster
Bileşenleri ekle, boyutu ver, en son setVisible(true) çağır; görünür pencereye sonradan eklenen bileşen yeniden çizilene kadar görünmeyebilir. Varsayılan kapanış davranışı HIDE_ON_CLOSE: pencere gizlenir ama program bitmez. f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); ekle. Derleme hatası yok, ikisi de mantık hatası.
Which layout would you use for a calculator keypad with 4 rows of 3 equal buttons, and how do you create it with 5 pixel gaps?
Cevabı göster
Eşit boyutlu hücreler gereken her ızgara GridLayout'tur. Parametreler: satır, sütun, yatay boşluk, dikey boşluk. FlowLayout pencere daralınca butonları kaydırır, BorderLayout'ta sadece 5 bölge var.
Write a class TemperatureFrame extending JFrame that shows, in a FlowLayout, a label "Celsius:", a 6-column text field, a "Convert" button and a result label "F: ?". The window title is "C to F" and closing it ends the program. (No event handling yet.)
Cevabı göster
Bileşenleri alan olarak tut ki 6. haftada listener içinden erişebilesin. JFrame varsayılanı BorderLayout olduğu için FlowLayout'u açıkça ayarla:
import java.awt.FlowLayout;
import javax.swing.*;public class TemperatureFrame extends JFrame {
private final JTextField celsiusField = new JTextField(6);
private final JButton convertButton = new JButton("Convert");
private final JLabel resultLabel = new JLabel("F: ?"); public TemperatureFrame() {
super("C to F"); // pencere başlığı
setLayout(new FlowLayout());
add(new JLabel("Celsius:"));
add(celsiusField);
add(convertButton);
add(resultLabel);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(300, 100);
} public static void main(String[] args) {
new TemperatureFrame().setVisible(true);
}
}Doğrulama: Bileşen davranışları pencere açmadan headless modda çalıştırıldı (java -Djava.awt.headless=true verify/SEN1002/W05.java). JFrame varsayılanları (HIDE_ON_CLOSE, BorderLayout, CENTER ezme) pencere görünür yapılmadan kontrol edildi (java verify/SEN1002/W05b.java). Pencere örnekleri javac ile derlendi (verify/SEN1002/W05Gui.java). Derleme hataları: verify/SEN1002/err/E05a-c.java.