Author Archives: Phở Code

Python – Cơ sở dữ liệu SQLite

Trong phần này chúng ta sẽ làm việc với cơ sở dữ liệu SQLite.

Python cung cấp sẵn module sqlite3 hỗ trợ kết nối và thao tác với CSDL SQLite.

C:\User\PhoCode>python
Python 3.5.1 (v3.5.1:37a07cee5969, Dec 6 2015, 01:38:48) [MSC v.1900 32 bit (Intel)] 
on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import sqlite3
>>> sqlite3.version
'2.6.0'
>>> sqlite3.sqlite_version
'3.8.11'

Chúng ta có thể kiểm tra một số thông tin về module sqlite3.

Trước khi bắt đầu chúng ta cần có một CSDL để test.

C:\User\PhoCode>sqlite3 test.db
SQLite version 3.7.13 2012-06-11 02:05:22
Enter ".help" for instructions
Enter SQL statements terminated with a ";"

Chúng ta tạo CSDL test.db.

Xem phiên bản SQLite

Trong ví dụ dưới đây chúng ta xem phiên bản CSDL SQLite.

import sqlite3 as lite
import sys
import os
con = None

try:
    path = os.path.dirname(__file__) + "\\test.db"
    con = lite.connect(path)
    
    cur = con.cursor()    
    cur.execute('SELECT SQLITE_VERSION()')
    
    data = cur.fetchone()
    
    print ("SQLite version: %s" % data)         
    
except lite.Error as e:
    
    print ("Error %s:" % e.args[0])
    sys.exit(1)
    
finally:
    
    if con:
        con.close()

Chúng ta kết nối với CSDL test.db và thực thi câu truy vấn lấy thông tin về phiên bản SQLite đang sử dụng.

import sqlite3 as lite

Đầu tiên chúng ta import module sqlite3.

con = None

Biến con là biến lưu trữ đối tượng Connection khi chúng ta kết nối CSDL.

path = os.path.dirname(__file__) + "\\test.db"
con = lite.connect(path)

Để kết nối đến CSDL thì chúng ta dùng phương thức connect(), phương thức này trả về một đối tượng Connection.

cur = con.cursor()    
cur.execute('SELECT SQLITE_VERSION()')

Sau khi đã có đối tượng Connection, chúng ta lấy một đối tượng Cursor, đối tượng này làm nhiệm vụ duyệt qua các bản ghi trong tập dữ liệu được lấy về và thực thi các câu truy vấn. Để thực thi một câu truy vấn thì chúng ta dùng phương thức execute().

data = cur.fetchone()

Phương thức fetchone() lấy về dòng đầu tiên của bảng dữ liệu trả về.

print ("SQLite version: %s" % data)

Chúng ta in dòng dữ liệu đó ra màn hình.

finally:
    
    if con:
        con.close() 

Sau khi đã hoàn tất công việc thì chúng ta đóng kết nối tới CSDL với phương thức close().

SQLite version: 3.8.11

Trong ví dụ dưới đây, chúng ta cũng lấy phiên bản SQLite nhưng sử dụng từ khóa with.

import sqlite3 as lite
import sys
import os

path = os.path.dirname(__file__) + "\\test.db"
con = lite.connect(path)

with con:
    
    cur = con.cursor()    
    cur.execute('SELECT SQLITE_VERSION()')
    
    data = cur.fetchone()
    
    print ("SQLite version: %s" % data)

Dùng từ khóa with có tác dụng làm cho code dễ chịu hơn và các câu lệnh SQL có liên quan đến việc cập nhật dữ liệu như INSERT, UPDATE, DELETE… sẽ tự động được thực thi (nếu không thì bạn phải gọi thêm phương thức commit() thì mới thực sự thực thi câu truy vấn lên CSDL).

with con:

Python sẽ tự động xử lý exception và tự động ngắt kết nối CSDL khi không dùng nữa nếu có từ khóa with.

INSERT

Chúng ta sẽ thực thi câu truy vấn INSERT.

import sqlite3 as lite
import sys
import os
path = os.path.dirname(__file__) + "\\test.db"
con = lite.connect(path)

with con:
    
    cur = con.cursor()    
    cur.execute("CREATE TABLE Cars(Id INT, Name TEXT, Price INT)")
    cur.execute("INSERT INTO Cars VALUES(1,'Audi',52642)")
    cur.execute("INSERT INTO Cars VALUES(2,'Mercedes',57127)")
    cur.execute("INSERT INTO Cars VALUES(3,'Skoda',9000)")
    cur.execute("INSERT INTO Cars VALUES(4,'Volvo',29000)")
    cur.execute("INSERT INTO Cars VALUES(5,'Bentley',350000)")
    cur.execute("INSERT INTO Cars VALUES(6,'Citroen',21000)")
    cur.execute("INSERT INTO Cars VALUES(7,'Hummer',41400)")
    cur.execute("INSERT INTO Cars VALUES(8,'Volkswagen',21600)")

Đoạn code trên tạo bảng Cars và insert 8 dòng dữ liệu vào bảng này.

cur.execute("CREATE TABLE Cars(Id INT, Name TEXT, Price INT)")

Bảng Cars sẽ có 3 cột là Id, NamePrice.

cur.execute("INSERT INTO Cars VALUES(1,'Audi',52642)")
cur.execute("INSERT INTO Cars VALUES(2,'Mercedes',57127)")

Chúng ta chỉ cần dùng phương thức execute() để thực thi các câu lệnh SQL. Khi dùng từ khóa with thì các câu lệnh này sẽ được thực thi ngay trên CSDL.

sqlite> .mode column  
sqlite> .headers on
sqlite> SELECT * FROM Cars;
Id          Name        Price     
----------  ----------  ----------
1           Audi        52642     
2           Mercedes    57127     
3           Skoda       9000      
4           Volvo       29000     
5           Bentley     350000    
6           Citroen     21000     
7           Hummer      41400     
8           Volkswagen  21600 

Các câu lệnh như UPDATE, DELETE… bạn cũng làm tương tự.

Phương thức executemany()

Phương thức executemany() tiện lợi hơn bằng cách thực thi nhiều câu lệnh cùng một lúc.

import sqlite3 as lite
import sys
import os
cars = (
    (1, 'Audi', 52642),
    (2, 'Mercedes', 57127),
    (3, 'Skoda', 9000),
    (4, 'Volvo', 29000),
    (5, 'Bentley', 350000),
    (6, 'Hummer', 41400),
    (7, 'Volkswagen', 21600)
)


path = os.path.dirname(__file__) + "\\test.db"
con = lite.connect(path)

with con:
    
    cur = con.cursor()    
    
    cur.execute("DROP TABLE IF EXISTS Cars")
    cur.execute("CREATE TABLE Cars(Id INT, Name TEXT, Price INT)")
    cur.executemany("INSERT INTO Cars VALUES(?, ?, ?)", cars)

Chúng ta xóa bảng và tạo lại bảng Cars.

cur.execute("DROP TABLE IF EXISTS Cars")
cur.execute("CREATE TABLE Cars(Id INT, Name TEXT, Price INT)")

Đầu tiên chúng ta kiểm tra xem bảng Cars đã tồn tại chưa, nếu rồi thì xóa bảng đó và tạo lại bảng mới.

cur.executemany("INSERT INTO Cars VALUES(?, ?, ?)", cars)

Chúng ta insert 8 dòng dữ liệu vào bảng bằng một phương thức duy nhất là executemany(), tham số đầu tiên là một câu lệnh SQL có tham số là các dấu ?, tham số thứ 2 là một tuple chứa nhiều tuple khác là các dữ liệu cần truyền vào.

SELECT

import sqlite3 as lite
import sys
import os

path = os.path.dirname(__file__) + "\\test.db"
con = lite.connect(path)

with con:    
    
    cur = con.cursor()    
    cur.execute("SELECT * FROM Cars")

    rows = cur.fetchall()

    for row in rows:
        print (row)

Chúng ta sẽ lấy các bản ghi từ bảng Cars.

cur.execute("SELECT * FROM Cars")

Việc này cũng rất đơn giản, chỉ cần dùng phương thức execute() với câu SQL tương ứng.

rows = cur.fetchall()

Phương thức fetchall() sẽ trả về một tuple chứa các tuple là các dòng dữ liệu trong bảng.

for row in rows:
    print (row)

Chúng ta in các tuple đó ra màn hình.

(1, u'Audi', 52642)
(2, u'Mercedes', 57127)
(3, u'Skoda', 9000)
(4, u'Volvo', 29000)
(5, u'Bentley', 350000)
(6, u'Citroen', 21000)
(7, u'Hummer', 41400)
(8, u'Volkswagen', 21600)

Bạn cũng có thể in từng dòng một nếu muốn.

import sqlite3 as lite
import sys
import os

path = os.path.dirname(__file__) + "\\test.db"
con = lite.connect(path)

with con:
    
    cur = con.cursor()    
    cur.execute("SELECT * FROM Cars")

    while True:
      
        row = cur.fetchone()
        
        if row == None:
            break
            
        print (row[0], row[1], row[2])

Chúng ta lấy từng dòng và in chúng ra màn hình.

while True:

Chúng ta dùng một vòng lặp để lặp qua từng dòng dữ liệu trong bảng. Vòng lặp kết thúc khi đối tượng Cursor đã đọc hết dữ liệu trong bảng.

row = cur.fetchone()

if row == None:
    break

Đối tượng Cursor có chứa một con trỏ chỉ đến các dòng trong bảng. Phương thức fetchone() sẽ đẩy con trỏ này lên một dòng và trả về dữ liệu của dòng đó, nếu con trỏ chỉ qua dòng cuối cùng thì sẽ trả về một đối tượng None.

print (row[0], row[1], row[2])

Dữ liệu trả về là một tuple nên bạn có thể truy xuất từng phần tử trong tuple bằng cặp dấu [].

1 Audi 52642
2 Mercedes 57127
3 Skoda 9000
4 Volvo 29000
5 Bentley 350000
6 Citroen 21000
7 Hummer 41400
8 Volkswagen 21600

Lấy phần tử thông qua tên cột

Như các ví dụ trên, dữ liệu trả về là một tuple chứa các tuple, nhưng bạn có thể quy định dữ liệu trả về dạng Dictionary, bằng cách đó bạn có thể truy cập vào các cột thông qua tên cột chứ không cần dùng chỉ số nữa.

import sqlite3 as lite
import os

path = os.path.dirname(__file__) + "\\test.db"
con = lite.connect(path)   

with con:
    
    con.row_factory = lite.Row
       
    cur = con.cursor() 
    cur.execute("SELECT * FROM Cars")

    rows = cur.fetchall()

    for row in rows:
        print ("%s %s %s" % (row["Id"], row["Name"], row["Price"]))

Trong ví dụ này chúng ta sẽ lấy dữ liệu về dạng Dictionary.

con.row_factory = lite.Row

Để dữ liệu trả về là Dictionary thì chúng ta thiết lập thuộc tính row_factory trong đối tượng Connectionsqlite3.Row.

for row in rows:
    print ("%s %s %s" % (row["Id"], row["Name"], row["Price"]))

Dữ liệu trả về kiểu Dictionary và bạn có thể truy xuất dữ liệu của các ô thông qua tên cột.

Truyền tham số vào câu truy vấn

Truyền tham số vào câu truy vấn giúp tăng tốc độ thực thi câu truy vấn và đảm bảo an toàn cho ứng dụng khỏi kiểu tấn công SQL Injection.

import sqlite3 as lite
import sys
import os
uId = 1
uPrice = 62300 

path = os.path.dirname(__file__) + "\\test.db"
con = lite.connect(path) 

with con:

    cur = con.cursor()    

    cur.execute("UPDATE Cars SET Price=? WHERE Id=?", (uPrice, uId))        
    con.commit()
    
    print ("Number of rows updated: %d" % cur.rowcount)

Chúng ta thực thi câu lệnh UPDATE và dùng tham số trong câu lệnh SQL.

cur.execute("UPDATE Cars SET Price=? WHERE Id=?", (uPrice, uId)) 

Các tham số trong câu truy vấn được đại diện bằng dấu ?.

print ("Number of rows updated: %d" % cur.rowcount)

Ngoài ra trong đối tượng Cursor có thuộc tính rowcount chứa số lượng các dòng dữ liệu vừa được cập nhật.

Number of rows updated: 1

Java Swing – Mô hình MVC

Trong phần này chúng ta sẽ tìm hiểu về mô hình mà các component trong Java Swing sử dụng.

Bộ thư viện Swing được thiết kế dựa theo mô hình MVC (Model View Controller) cho phép thao tác với dữ liệu một cách tiện lợi và nhanh chóng.

Mô hình MVC

Mô hình MVC mà chúng ta thường thấy chia ứng dụng làm 3 phần: mô hình (Model), View và Controller. Trong đó model đại diện cho dữ liệu trong ứng dụng, view làm nhiệm vụ định dạng hiển thị cho dữ liệu và cuối cùng Controller xử lý sự kiện, có thể là sự kiện từ người dùng hoặc cũng chính nó tự sinh ra sự kiện. Nguyên gốc của mô hình MVC xuất phát từ ý tưởng chia tầng dữ liệu và tầng giao diện bằng một tầng ở giữa là Controller.

Thư viện Swing thì khác một tí, cả 2 phần View và Controller được gom lại làm một và được đặt tên là UI.

Trong Swing, tất cả mọi component đều có mô hình (model) của riêng nó, các mô hình lại được chia làm 2 loại là mô hình trạng thái và mô hình dữ liệu. Mô hình trạng thái lưu trữ trạng thái của component, ví dụ mô hình lưu giữ trạng thái của một button là đang được click hay đang được thả. Mô hình dữ liệu lưu trữ dữ liệu của component, chẳng hạn như một label lưu dữ liệu về text mà nó đang hiển thị.

Bản thân các component trong Swing đều được kế thừa từ một lớp model của riêng chúng, chẳng hạn như JButton kế thừa từ JAbstractButton, bên trong JAbstractButton có một thuộc tính là một đối tượng ButtonModel (bạn có thể tìm đọc trong bộ source của Java Swing), đối tượng này cung cấp các phương thức để lấy dữ liệu, thường thì nếu chúng ta muốn lấy dữ liệu thì chúng ta phải lấy đối tượng model này, nhưng lớp JButton thì lại có các phương thức lấy dữ liệu trực tiếp từ model luôn, nên bạn có thể lấy dữ liệu trực tiếp từ JButton mà không cần phải lấy đối tượng model trước.

public int getValue() { 
    return getModel().getValue(); 
}

Ví dụ như phương thức getValue() ở trên (đây là đoạn code phương thức getValue() của một component trong mã nguồn Java Swing), chúng ta lấy dữ liệu trực tiếp luôn chứ không cần phải lấy một đối tượng component nào đó rồi mới lấy dữ liệu. Hơn nữa việc thao tác trực tiếp với component là không an toàn.

Khi một component được tạo thì mặc định một đối tượng model sẽ được tạo, chẳng hạn như với JButton thì lớp đại diện cho model của nó là DefaultButtonModel.

public JButton(String text, Icon icon) {
  // Create the model
  setModel(new DefaultButtonModel());

  // initialize
  init(text, icon);
}

Ở trên là đoạn code khởi tạo JButton trong bộ mã nguồn của Java Swing.

Model của JButton

Mô hình được tạo sẵn cho JButton còn được dùng cho một số component phổ biến khác như check box, radio box, menu item… Loại mô hình của các component này là mô hình trạng thái.

import java.awt.Container;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;

import javax.swing.AbstractAction;
import javax.swing.DefaultButtonModel;
import javax.swing.GroupLayout;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

public class ButtonModel extends JFrame {
 
    private JButton okbtn;
    private JLabel enabledLbl;
    private JLabel pressedLbl;
    private JCheckBox cb;
 
    public ButtonModel()
    {
        initUI();
    }
 
    private void initUI()
    {
        Container pane = getContentPane();
        GroupLayout gl = new GroupLayout(pane);
        pane.setLayout(gl);

        okbtn = new JButton("OK");
        okbtn.addChangeListener(new DisabledChangeListener());
        cb = new JCheckBox();
        cb.setAction(new CheckBoxAction());

        enabledLbl = new JLabel("Enabled: true");
        pressedLbl = new JLabel("Pressed: false");
 
        gl.setAutoCreateContainerGaps(true);
        gl.setAutoCreateGaps(true);

        gl.setHorizontalGroup(gl.createParallelGroup()
            .addGroup(gl.createSequentialGroup()
                .addComponent(okbtn)
                .addGap(80)
                .addComponent(cb))
            .addGroup(gl.createParallelGroup()
                .addComponent(enabledLbl)
                .addComponent(pressedLbl)) 
        );

        gl.setVerticalGroup(gl.createSequentialGroup()
            .addGroup(gl.createParallelGroup()
                .addComponent(okbtn)
                .addComponent(cb))
                .addGap(40)
            .addGroup(gl.createSequentialGroup()
                .addComponent(enabledLbl)
                .addComponent(pressedLbl))
        );

        pack();

        setTitle("ButtonModel");
        setLocationRelativeTo(null);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
    }
 
    private class DisabledChangeListener implements ChangeListener {

        @Override
        public void stateChanged(ChangeEvent e) {

            DefaultButtonModel model = (DefaultButtonModel) okbtn.getModel();

            if (model.isEnabled()) {
                enabledLbl.setText("Enabled: true");
            } else {
                enabledLbl.setText("Enabled: false");
            }

            if (model.isPressed()) {
                pressedLbl.setText("Pressed: true");
            } else { 
                pressedLbl.setText("Pressed: false");
            }
        }
    }
 
    private class CheckBoxAction extends AbstractAction {
 
        public CheckBoxAction() {
           super("Disabled");
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            if (okbtn.isEnabled()) {
                okbtn.setEnabled(false);
            } else {
                okbtn.setEnabled(true);
            }
        }
    }

    public static void main(String[] args) {

        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                ButtonModel ex = new ButtonModel();
                ex.setVisible(true);
            }
        });
    }
}

Chúng ta tạo một button, một checkbox và 3 label, các label này hiển thị dữ liệu về trạng thái của button như có đang được click hay không, đang bật hay đang bị vô hiệu hóa…

okbtn.addChangeListener(new DisabledChangeListener());

Chúng ta dùng lớp ChangeListener để lắng nghe sự thay đổi trạng thái của button.

DefaultButtonModel model = (DefaultButtonModel) okbtn.getModel();

Để lấy đối tượng model thì chúng ta dùng phương thức getModel().

if (model.isEnabled()) {
    enabledLbl.setText("Enabled: true");
} else {
    enabledLbl.setText("Enabled: false");
}

Chúng ta dùng phương thức isEnabled() để biết button đang dùng được hay đang bị vô hiệu hóa và cập nhật lên label.

if (okbtn.isEnabled()) {
    okbtn.setEnabled(false);
} else {
    okbtn.setEnabled(true);
}

Check box sẽ bật/tắt button nhưng ở đây chúng ta không dùng model mà dùng phương thức setEnabled() để thay đổi trực tiếp luôn.

public void setEnabled(boolean b) {
    if (!b && model.isRollover()) {
        model.setRollover(false);
    } 
    super.setEnabled(b);
    model.setEnabled(b);
}

Đoạn code trên là mã nguồn của phương thức JCheckBox.setEnable(), như bạn thấy thực chất khi gọi phương thức này JCheckBox tự động lấy model của nó và thiết lập thông tin từ model đó.

Capture

Tùy chỉnh Model

Trong phần này chúng ta làm lại những gì đã làm ở trên, chỉ khác là chúng ta sẽ sử dụng model do chúng ta tự thiết kế.

import java.awt.Container;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import javax.swing.AbstractAction;
import javax.swing.ButtonModel;
import javax.swing.DefaultButtonModel;
import javax.swing.GroupLayout;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import static javax.swing.JFrame.EXIT_ON_CLOSE;
import javax.swing.JLabel;


public class ButtonModel2 extends JFrame {

    private JButton okbtn;
    private JLabel enabledLbl;
    private JLabel pressedLbl;
    private JCheckBox cb;

    public ButtonModel2() {

        initUI();
    }
    
    private void initUI() {
        
        Container pane = getContentPane();
        GroupLayout gl = new GroupLayout(pane);
        pane.setLayout(gl);       
        
        okbtn = new JButton("OK");
        cb = new JCheckBox();
        cb.setAction(new CheckBoxAction());

        enabledLbl = new JLabel("Enabled: true");
        pressedLbl = new JLabel("Pressed: false");

        OkButtonModel model = new OkButtonModel();
        okbtn.setModel(model);        

        gl.setAutoCreateContainerGaps(true);
        gl.setAutoCreateGaps(true);

        gl.setHorizontalGroup(gl.createParallelGroup()
                .addGroup(gl.createSequentialGroup()
                        .addComponent(okbtn)
                        .addGap(80)
                        .addComponent(cb))
                .addGroup(gl.createParallelGroup()
                        .addComponent(enabledLbl)
                        .addComponent(pressedLbl))
        );

        gl.setVerticalGroup(gl.createSequentialGroup()
                .addGroup(gl.createParallelGroup()
                        .addComponent(okbtn)
                        .addComponent(cb))
                .addGap(40)
                .addGroup(gl.createSequentialGroup()
                        .addComponent(enabledLbl)
                        .addComponent(pressedLbl))
        );

        pack();

        setTitle("MVC");
        setLocationRelativeTo(null);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
    }
    
    private class OkButtonModel extends DefaultButtonModel {

        @Override
        public void setEnabled(boolean b) {
            if (b) {
                enabledLbl.setText("Enabled: true");
            } else {
                enabledLbl.setText("Enabled: false");
            }

            super.setEnabled(b);
        }

        @Override
        public void setPressed(boolean b) {
            if (b) {
                pressedLbl.setText("Pressed: true");
            } else {
                pressedLbl.setText("Pressed: false");
            }

            super.setPressed(b);
        }
    }
    
    private class CheckBoxAction extends AbstractAction {
        
        public CheckBoxAction() {
            super("Disabled");
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            if (okbtn.isEnabled()) {
                okbtn.setEnabled(false);
            } else {
                okbtn.setEnabled(true);
            }
        }
    }    

    public static void main(String[] args) {
        
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                ButtonModel2 ex = new ButtonModel2();
                ex.setVisible(true);
            }
        });
    }
}

Chúng ta thiết kế lại giao diện như trong ví dụ trước, chỉ khác là chúng ta không dùng đến các listener nữa mà sử dụng đối tượng model kế thừa do chúng ta tự thiết kế.

ButtonModel model = new OkButtonModel();
okbtn.setModel(model); 

Để thiết lập model thì chúng ta sử dụng phương thức setModel().

private class OkButtonModel extends DefaultButtonModel {
...
}

Chúng ta tạo model button riêng và override một số phương thức cần thiết.

@Override
public void setEnabled(boolean b) {
    if (b) {
        enabledLbl.setText("Enabled: true");
    } else {
        enabledLbl.setText("Enabled: false");
    }

    super.setEnabled(b);
}

Chúng ta override phương thức setEnabled() và cập nhật lại text trên label, sau đó chúng ta phải gọi phương thức setEnabled() của lớp cha để cập nhật sự thay đổi.

Component có 2 model

Có một số component có tới 2 model, JList là một ví dụ, 2 model của JListListModel à ListSelectionModel. Trong đó ListModel là một model dữ liệu, model này lưu trữ danh sách các item trong list, cũng như tính chất của các item đó, ListSelectionModel là một model trạng thái, lưu trữ trạng thái của các item, chẳng hạn như item thứ mấy đang được chọn bởi người dùng…

import java.awt.Container;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.DefaultListModel;
import javax.swing.GroupLayout;
import static javax.swing.GroupLayout.Alignment.CENTER;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JScrollPane;
import javax.swing.ListSelectionModel;

public class ListModels extends JFrame {

    private DefaultListModel model;
    private JList list;
    private JButton remallbtn;
    private JButton addbtn;
    private JButton renbtn;
    private JButton delbtn;

    public ListModels() {

        initUI();
    }

    private void createList() {

        model = new DefaultListModel();
        model.addElement("Amelie");
        model.addElement("Aguirre, der Zorn Gottes");
        model.addElement("Fargo");
        model.addElement("Exorcist");
        model.addElement("Schindler's list");

        list = new JList(model);
        list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);

        list.addMouseListener(new MouseAdapter() {

            @Override
            public void mouseClicked(MouseEvent e) {

                if (e.getClickCount() == 2) {
                    int index = list.locationToIndex(e.getPoint());
                    Object item = model.getElementAt(index);
                    String text = JOptionPane.showInputDialog("Rename item", item);
                    String newitem = null;
                    if (text != null) {
                        newitem = text.trim();
                    } else {
                        return;
                    }

                    if (!newitem.isEmpty()) {
                        model.remove(index);
                        model.add(index, newitem);
                        ListSelectionModel selmodel = list.getSelectionModel();
                        selmodel.setLeadSelectionIndex(index);
                    }
                }
            }
        });
    }

    private void createButtons() {

        remallbtn = new JButton("Remove All");
        addbtn = new JButton("Add");
        renbtn = new JButton("Rename");
        delbtn = new JButton("Delete");

        addbtn.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {

                String text = JOptionPane.showInputDialog("Add a new item");
                String item = null;

                if (text != null) {
                    item = text.trim();
                } else {
                    return;
                }

                if (!item.isEmpty()) {
                    model.addElement(item);
                }
            }
        });

        delbtn.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent event) {

                ListSelectionModel selmodel = list.getSelectionModel();
                int index = selmodel.getMinSelectionIndex();
                if (index >= 0) {
                    model.remove(index);
                }
            }

        });

        renbtn.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {

                ListSelectionModel selmodel = list.getSelectionModel();
                int index = selmodel.getMinSelectionIndex();
                if (index == -1) {
                    return;
                }

                Object item = model.getElementAt(index);
                String text = JOptionPane.showInputDialog("Rename item", item);
                String newitem = null;

                if (text != null) {
                    newitem = text.trim();
                } else {
                    return;
                }

                if (!newitem.isEmpty()) {
                    model.remove(index);
                    model.add(index, newitem);
                }
            }
        });

        remallbtn.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                model.clear();
            }
        });

    }

    private void initUI() {

        createList();
        createButtons();
        JScrollPane scrollpane = new JScrollPane(list);

        Container pane = getContentPane();
        GroupLayout gl = new GroupLayout(pane);
        pane.setLayout(gl);

        gl.setAutoCreateContainerGaps(true);
        gl.setAutoCreateGaps(true);

        gl.setHorizontalGroup(gl.createSequentialGroup()
                .addComponent(scrollpane)
                .addGroup(gl.createParallelGroup()
                        .addComponent(addbtn)
                        .addComponent(renbtn)
                        .addComponent(delbtn)
                        .addComponent(remallbtn))
        );

        gl.setVerticalGroup(gl.createParallelGroup(CENTER)
                .addComponent(scrollpane)
                .addGroup(gl.createSequentialGroup()
                        .addComponent(addbtn)
                        .addComponent(renbtn)
                        .addComponent(delbtn)
                        .addComponent(remallbtn))
        );

        gl.linkSize(addbtn, renbtn, delbtn, remallbtn);

        pack();

        setTitle("JList models");
        setLocationRelativeTo(null);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
    }

    public static void main(String[] args) {

        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                ListModels ex = new ListModels();
                ex.setVisible(true);
            }
        });
    }
}

Trong ví dụ này, chúng ta hiển thị một JList và 4 button. Các button sẽ điều khiển JList.

model = new DefaultListModel();
model.addElement("Amelie");
model.addElement("Aguirre, der Zorn Gottes");
...

Đầu tiên chúng ta tạo một model cho JList và thêm các item vào đó.

list = new JList(model);
list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);

Tiếp theo chúng ta tạo một đối tượng JList và thiết lập chế độ chỉ cho phép chọn một item tại một thời điểm.

if (text != null) {
    item = text.trim();
} else {
    return;
}

if (!item.isEmpty()) {
    model.addElement(item);
}

Chúng ta chỉ thêm item vào list nếu item đó không rỗng.

ListSelectionModel selmodel = list.getSelectionModel();
int index = selmodel.getMinSelectionIndex();
if (index >= 0) {
    model.remove(index);
}

Đoạn code trên thực hiện xóa các item, đầu tiên chúng ta lấy ra số thứ tự của item đang được chọn bằng phương thức getMinSelectionIndex() từ model của JList. Sau đó dùng phương thức remove() để xóa item này.

Trong ví dụ này mình dùng cả 2 model và dùng cả 2 phương pháp là sử dụng model và dùng phương thức trực tiếp của component. Các phương thức add(), remove(), clear() là dùng trực tiếp phương thức của component, còn lấy item đang được chọn là lấy từ model.

Capture

Qt 5 C++ – Tải file qua mạng với QNetworkAccessManager

Trong phần này chúng ta sẽ học cách gửi các yêu cầu HTTP và nhận file HTML gửi về bằng lớp QNetworkAccessManager.

QNetworkAccessManager

Lớp QNetworkAccessManager cho phép ứng dụng gửi các yêu cầu và nhận trả lời qua mạng. Đối tượng QNetworkAccessManager lưu trữ các phương thức kết nối mạng, cấu hình thường dùng và các thiết lập cần thiết cho các yêu cầu được gửi đi, thông số proxy và cache, các Signal liên quan… Lớp QNetworkAccessManager rất mạnh, gần như bạn chỉ cần tạo một đối tượng QNetworkAccessManager là có thể dùng cho toàn bộ ứng dụng Qt.

Khi bạn tạo một đối tượng QNetworkAccessManager và dùng nó để gửi các yêu cầu qua mạng thì thông điệp gửi về sẽ nằm trong một đối tượng QNetworkReplpy.

Mini Browser

Chúng ta sẽ dùng QNetworkAccessManager để gửi yêu cầu HTTP và lấy về một file HTML.

minibrowser.pro

...
QT += core
QT += gui widgets
QT += webkitwidgets
QT += network
...

Đầu tiên bạn phải chỉ định một số module được dùng trong file .pro bao gồm network, webkitwidgets.

#include <QPushButton>
#include <QGridLayout>
#include <QLineEdit>
#include <QLabel>
#include <QWidget>
#include <QTextBrowser>
#include <QtWebKitWidgets/QWebView>

#include <QVBoxLayout>
#include <QHBoxLayout>

#include <QNetworkAccessManager>
#include <QNetworkRequest>
#include <QNetworkReply>

#include <QObject>

class MiniBrowser : public QWidget
{
    Q_OBJECT
public:
    MiniBrowser(QWidget *parent = 0);

public slots:
    void goToPage();
    void receiveFinishedSignal(QNetworkReply*);
private:
    QPushButton *goBtn;
    QLabel *urlLbl;
    QLineEdit *line;
    QWebView *browser;

    QNetworkAccessManager *manager;
};

Lớp MiniBrowser sẽ là cửa sổ chính.

QWebView *browser;

Lớp QWebView là một widget hỗ trợ hiển thị trang web HTML.

public slots:
    void goToPage();
    void receiveFinishedSignal(QNetworkReply*);

Slot gotoPage() xử lý sự kiện click button. Slot receiveFinishedSignal() xử lý thông điệp trả về của QNetworkAccessManager.

#include "minibrowser.h"
MiniBrowser::MiniBrowser(QWidget *parent) : QWidget(parent)
{
    manager = new QNetworkAccessManager();

    goBtn = new QPushButton("Go", this);
    urlLbl = new QLabel("Url:", this);
    line = new QLineEdit(this);
    browser = new QWebView(this);    

    QVBoxLayout *vbox = new QVBoxLayout(this);
    QHBoxLayout *hbox = new QHBoxLayout(this);

    hbox->addWidget(urlLbl);
    hbox->addWidget(line);
    hbox->addWidget(goBtn);
    vbox->addLayout(hbox);
    vbox->addWidget(browser);

    setLayout(vbox);

    QObject::connect(goBtn, SIGNAL(pressed()), this, SLOT(goToPage()));
    QObject::connect(manager, SIGNAL(finished(QNetworkReply*)), 
                     this, SLOT(receiveFinishedSignal(QNetworkReply*)));
}

void MiniBrowser::goToPage()
{
    QNetworkRequest request(QUrl(this->line->text()));
    manager->get(request);
}

void MiniBrowser::receiveFinishedSignal(QNetworkReply *reply)
{
    if(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute) == 302)
    {
        QUrl url = reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
        manager->get(QNetworkRequest(url));
    }
    QByteArray array = reply->readAll();
    QString str = QString::fromUtf8(array.data(), array.size());
    this->browser->setHtml(str);
}

Chúng ta sẽ tạo một giao diện của một browser đơn giản và cài đặt các slot xử lý.

manager = new QNetworkAccessManager();

Tạo một đối tượng QNetworkAccessManager.

QNetworkRequest request(QUrl(this->line->text()));
manager->get(request);

Để tiến hành gửi các lệnh request qua mạng thì chúng ta dùng phương thức get(), phương thức này nhận một đối  tượng QNetworkRequest, đây là lớp lưu trữ các thông tin cấu hình chuẩn cho các giao thức ứng dụng, hàm khởi tạo QNetworkRequest nhận một đối tượng QUrl, QUrl chẳng qua chỉ là một lớp lưu trữ thông tin của một đường dẫn Url bình thường.

QObject::connect(manager, SIGNAL(finished(QNetworkReply*)), 
                     this, SLOT(receiveFinishedSignal(QNetworkReply*)));

Sau khi gọi phương thức get(), QNetworkAccessManager sẽ thực hiện việc gửi yêu cầu và chờ thông điệp trả lời và sẽ thông báo qua SIGNAL finished(QNetworkReply*) với dữ liệu đi kèm là một đối tượng QNetworkReply, chúng ta kết nối signal này với slot receiveFinishedSignal() để nhận lấy dữ liệu truyền về trong QNetworkReply.

if(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute) == 302)
{
    QUrl url = reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
    manager->get(QNetworkRequest(url));
}

Bạn có thể sẽ nhận được một mã lỗi 302 nếu vào đường dẫn google.commã này chuyển hướng bạn đến một đường dẫn khác nên mình lấy đường dẫn mới và gửi một yêu cầu khác.

QByteArray array = reply->readAll();
QString str = QString::fromUtf8(array.data(), array.size());
this->browser->setHtml(str);

Bạn có thể lấy toàn bộ nội dung thông điệp qua phương thức QNetworkReply::readAll(), trong trường hợp này đây là một đoạn code HTML, phương thức này trả về một đối tượng QByteArray() nên nếu bạn muốn đọc kiểu text thì phải chuyển nó qua QString bằng phương thức QString::fromUtf8(). Cuối cùng bạn đưa nội dung HTML vô webview bằng phương thức setHtml().

#include <QApplication>
#include "minibrowser.h"
int main(int argc, char *argv[])
{ 
    QApplication a(argc, argv);
    MiniBrowser browser;
    browser.resize(640, 480);
    browser.setWindowTitle("Mini Browser");
    browser.show();
    return a.exec();
}

Untitled

Nội dung HTML trả về có thể sẽ không đầy đủ hoặc lỗi vì chúng ta không thiết lập một số thông số dành cho giao thức HTTP. Trong bài này mình chỉ hướng dẫn các thao tác cơ bản với lớp QNetworkAccessManager.

 

Java – Kết nối cơ sở dữ liệu MySQL – Phần 2

Trong phần này chúng ta tiếp tục tìm hiểu các thao tác thêm, sửa, xóa với CSDL MySQL.

Trước tiên chúng ta cần có vài dòng dữ liệu mẫu.

CREATE TABLE IF NOT EXISTS Authors(Id INT PRIMARY KEY AUTO_INCREMENT, 
    Name VARCHAR(25)) ENGINE=InnoDB;

INSERT INTO Authors(Id, Name) VALUES(1, 'Jack London');
INSERT INTO Authors(Id, Name) VALUES(2, 'Honore de Balzac');
INSERT INTO Authors(Id, Name) VALUES(3, 'Lion Feuchtwanger');
INSERT INTO Authors(Id, Name) VALUES(4, 'Emile Zola');
INSERT INTO Authors(Id, Name) VALUES(5, 'Truman Capote');
INSERT INTO Authors(Id, Name) VALUES(100, 'Taylor Swift');
INSERT INTO Authors(Id, Name) VALUES(1912, 'Titanic');

INSERT – UPDATE – DELETE

Ở đây chúng ta dùng lớp PreparedStatement lưu trữ các câu truy vấn mẫu, khi nào cần thực thi thì chúng ta chỉ cần truyền tham số vào và chạy thay vì viết câu truy vấn trực tiếp lên CSDL.

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.logging.Level;
import java.util.logging.Logger;

public class JDBCExample {

    public static void main(String[] args) {

        Connection con = null;
        PreparedStatement pst = null;

        String url = "jdbc:mysql://localhost:3306/test";
        String user = "root";
        String password = "";

        try {

            String author = "J.K. Rowling";
            con = DriverManager.getConnection(url, user, password);
            // INSERT
            pst = con.prepareStatement("INSERT INTO Authors(Name) VALUES(?)");
            pst.setString(1, author);
            pst.executeUpdate();

            // UPDATE
            pst = con.prepareStatement("UPDATE Authors SET Name=? WHERE Id=?");
            pst.setString(1, "Christopher Paolini");
            pst.setInt(2, 100);
            pst.executeUpdate();

            // DELETE
            pst = con.prepareStatement("DELETE FROM Authors WHERE Id=?");
            pst.setInt(1, 1912);
            pst.executeUpdate();

        } catch (SQLException ex) {
            ex.printStackTrace();

        } finally {

            try {
                if (pst != null) {
                    pst.close();
                }
                if (con != null) {
                    con.close();
                }

            } catch (SQLException ex) {
                ex.printStackTrace();
            }
        }
    }
}

Chúng ta thêm một dòng dữ liệu mới vào bảng Authors.

pst = con.prepareStatement("INSERT INTO Authors(Name) VALUES(?)");

Chúng ta tạo đối tượng PreparedStatement. Dùng lớp này sẽ an toàn hơn là thực thi trực tiếp các câu truy vấn lên CSDL bằng lớp Statement vì có thể chống được kiểu tấn công SQL Injection. Ở đây chúng ta có câu truy vấn mẫu và các tham số là ký tự ?, chúng ta phải truyền giá trị vào các tham số đó.

pst.setString(1, author);

Để truyền giá trị thì chúng ta dùng các phương thức thiết lập dữ liệu như setString(), tham số thứ nhất của phương thức này là kí tự ? thứ mấy và tham số thứ 2 là giá trị truyền vào. Ở đây chúng ta truyền vào dấu ? thứ 1 và sẽ truyền vào đó biến author, các phương thức như setInt() cũng tương tự.

pst.executeUpdate();

Sau khi đã có đầy đủ tham số cho câu truy vấn thì chúng ta thực thi câu truy vấn bằng phương thức executeUpdate(). Phương thức này dùng chung cho tất cả các loại truy vấn mà không trả về bảng dữ liệu nào như INSERT, UPDATE, DELETE, giá trị trả về của phương thức này là True hoặc False.

SELECT

Tiếp theo chúng ta sẽ lấy dữ liệu từ CSDL.

import java.sql.PreparedStatement;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.logging.Level;
import java.util.logging.Logger;

public class QueryExample {
    
    public static void main(String[] args) {

        Connection con = null;
        PreparedStatement pst = null;
        ResultSet rs = null;

        String url = "jdbc:mysql://localhost:3306/test";
        String user = "root";
        String password = "";

        try {
            
            con = DriverManager.getConnection(url, user, password);
            pst = con.prepareStatement("SELECT * FROM Authors");
            rs = pst.executeQuery();

            while (rs.next()) {
                System.out.print(rs.getInt(1));
                System.out.print(": ");
                System.out.println(rs.getString(2));
            }

        } catch (SQLException ex) {
                ex.printStackTrace();
        } finally {

            try {
                if (rs != null) {
                    rs.close();
                }
                if (pst != null) {
                    pst.close();
                }
                if (con != null) {
                    con.close();
                }

            } catch (SQLException ex) {
                ex.printStackTrace();
            }
        }
    }
}

Chúng ta sẽ truy xuất dữ liệu từ bảng Authors.

pst = con.prepareStatement("SELECT * FROM Authors");
rs = pst.executeQuery();

Chúng ta thực thi câu lệnh SELECT bằng phương thức executeQuery(), phương thức này trả về một đối tượng ResultSet.

while (rs.next()) {
      System.out.print(rs.getInt(1));
      System.out.print(": ");
      System.out.println(rs.getString(2));
}

Trong đối tượng ResultSet có một con trỏ chỉ đến hàng hiện tại, mặc định con trỏ này chỉ đến hàng số 0, hàng đầu tiên chứa dữ liệu là hàng số 1, phương thức next() có tác dụng di chuyển con trỏ đó lên một hàng. Phương thức getInt()getString() sẽ lấy giá trị ở cột và chúng ta chỉ định với hàng hiện tại của con trỏ.

1: Jack London
2: Honore de Balzac
3: Lion Feuchtwanger
4: Emile Zola
5: Truman Capote
100: Christopher Paolini
1913: J.K. Rowling

Java – Kết nối cơ sở dữ liệu MySQL – Phần 1

Trong bài này chúng ta sẽ học cách kết nối cơ sở dữ liệu MySQL.

JDBC

JDBC là một tập các hàm API cho phép kết nối đến cơ sở dữ liệu, cũng như thực hiện các câu truy vấn, cập nhật trên cơ sở dữ liệu. JDBC được phát triển dành riêng cho cơ sở dữ liệu quan hệ. JDBC nằm trong gói java.sql.

Cơ sở dữ liệu MySQL

Bạn tất nhiên là phải có MySQL cài đặt trong máy rồi. Và ngoài ra bạn cũng cần phải có driver tương ứng với từng môi trường nữa, chẳng hạn như ODBC là driver cho môi trường Windows, Linux, và MacOSX, ADO.NET là driver cho môi trường .NET… còn đối với Java thì driver là JDBC. Nếu bạn dùng IDE là NetBeans thì JDBC đã có sẵn trong máy bạn rồi. Còn các IDE khác thì bạn phải lên trang chủ của MySQL download về cài vào. Ở đây mình dùng NetBeans.

Để sử dụng JDBC thì bạn click chuột phải vào Libraries trong project của bạn rồi chọn MySQL JDBC Driver và bấm Add Library.

Capture

Bây giờ bạn cần có một database để test. Bạn chạy MySQL lên rồi tạo một database mới và đặt tên tùy ý mình..

CREATE DATABASE test;
Query OK, 1 row affected (0.02 sec)

Ở đây mình tạo database với tên là test. Sau khi đã có CSDL rồi thì chúng ta bắt đầu kết nối và thao tác với CSDL từ Java.

Kết nối và xem phiên bản MySQL

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.logging.Level;
import java.util.logging.Logger;

public class Version {

    public static void main(String[] args) {

        Connection con = null;
        Statement st = null;
        ResultSet rs = null;

        String url = "jdbc:mysql://localhost:3306/test";
        String user = "root";
        String password = "";

        try {
            con = DriverManager.getConnection(url, user, password);
            st = con.createStatement();
            rs = st.executeQuery("SELECT VERSION()");

            if (rs.next()) {
                System.out.println(rs.getString(1));
            }

        } catch (SQLException ex) {
            ex.printStackTrace();

        } finally {
            try {
                if (rs != null) {
                    rs.close();
                }
                if (st != null) {
                    st.close();
                }
                if (con != null) {
                    con.close();
                }

            } catch (SQLException ex) {
                ex.printStackTrace();
            }
        }
    }
}

Chúng ta truy vấn phiên bản MySQL trên máy của mình.

String url = "jdbc:mysql://localhost:3306/testdb";

Để kết nối đến CSDL thì chúng ta cần có một đường dẫn url theo quy định. Cú pháp của đường dẫn này bao gồm jdbc:mysql:// tiếp theo là địa chỉ của máy tính, số hiệu cổng 3306, và tên của CSDL mà mình muốn kết nối.

con = DriverManager.getConnection(url, user, password);

Để thực hiện kết nối thì chúng ta gọi DriverManager.getConnection() và đưa vào tham số url, user và password của MySQL khi bạn cài đặt.

st = con.createStatement();

Phương thức createStatement() tạo ra một đối tượng lớp Statement, lớp này có nhiệm vụ gửi các câu truy vấn lên cơ sở dữ liệu.

rs = st.executeQuery("SELECT VERSION()");

Phương thức executeQuery() của lớp Statement sẽ thực thi các câu truy vấn SQL và trả về một đối tượng ResultSet, đây chính là bảng dữ liệu mà CSDL trả về.

if (result.next()) {
    System.out.println(result.getString(1));
}

Bên trong đối tượng ResultSet có một con trỏ chỉ đến các hàng trong bảng dữ liệu, mặc định thì con trỏ này trỏ đến trước hàng đầu tiên một hàng. Phương thức next() sẽ di chuyển con trỏ lên 1 hàng nếu có, nếu không còn hàng nào thì phương thức này trả về false. Phương thức getString() lấy giá trị của hàng hiện tại với tham số cột trong bảng. Các cột được đánh số từ 1.

try {
    if (rs != null) {
        rs.close();
    }
    if (st != null) {
        st.close();
    }
    if (con != null) {
        con.close();
    }
...

Sau khi đã kết thúc truy vấn thì chúng ta phải đóng kết nối tới CSDL.

10.1.9-MariaDB

Qt 5 C++ – Cơ chế hoạt động của Signal và Slot

Trong bài này chúng ta sẽ tìm hiểu về cơ chế hoạt động của Signal và Slot

Trong lập trình GUI thì có một thứ rất quan trọng đó là sự kiện (event), khi một sự kiện nào đó xảy ra thì sẽ có các đối tượng xử lý sự kiện đó. Chẳng hạn như khi click vào nút X trên góc cửa sổ thì thoát chương trình. Qt xử lý sự kiện bằng cách tạo ra Signal và Slot. Trong một số bài trước chúng ta đã tìm hiểu sơ qua về cơ chế này, trong bài này chúng ta sẽ tìm hiểu kỹ hơn.

Signal

Signal tiếng Việt có nghĩa là tín hiệu. Trong Qt, khi một sự kiện nào đó xảy ra, một signal sẽ được phát đi giống như đài truyền hình phát sóng vậy, thực ra nó chỉ là một phương thức của một lớp nhưng không có phần thân hàm {}. Các lớp Widget có sẵn trong Qt có rất nhiều signal được định nghĩa sẵn, và chúng ta cũng có thể viết các signal riêng cho các lớp của chúng ta. Signal không có kiểu trả về, kiểu trả về của signal luôn luôn là void.

Slot

Slot chẳng qua cũng là một phương thức bình thường của một lớp, các phương thức này sẽ được gọi khi có một signal nào đó được phát đi. Cũng giống như signal, các lớp Widget trong Qt cũng có sẵn rất nhiều slot và chúng ta cũng có thể viết slot cho lớp của riêng chúng ta.

Connect

Signal và slot được kết nối qua từng đối tượng (chứ không phải qua từng lớp như nhiều bạn vẫn nghĩ). Tức là chúng ta chỉ có kết nối đối tượng này với đối tượng kia chứ không kết nối lớp này với lớp kia, giả sử chúng ta có đối tượng object1, object2 thuộc lớp A và object3 thuộc lớp B thì chúng ta chỉ có thể kết nối object1->object2, object1->object3 hoặc object3->object2 chứ không kết nối lớp A đến lớp B.

Khi kết nối như vậy thì một đối tượng sẽ làm vai trò phát signal, một đối tượng sẽ nhận signal. Đối tượng phát signal có thể phát các signal và cứ mỗi lần phát như vậy thì đối tượng nhận signal tương ứng sẽ thực thi slot của đối tượng đó.

Một signal có thể kết nối đến nhiều slot và một slot có thể kết nối đến nhiều signal.

Slot sẽ được gọi khi có signal tương ứng được phát ra, nhưng vì slot cũng là một phương thức bình thường như bao phương thức khác nên chúng ta cũng có thể gọi chúng như gọi phương thức bình thường vậy.

Tham số của signal phải ít hơn hoặc bằng tham số của slot. Khi một signal được phát đi, nó sẽ mang theo dữ liệu là các tham số của nó, và slot nhận signal này sẽ nhận các tham số đó thông qua tham số của nó. Thứ tự các tham số của signal và slot phải giống nhau, chẳng hạn như signal gửi 1 int, sau đó là 1 string thì slot cũng phải nhận 1 int rồi mới tới string.

Một signal cũng có thể kết nối đến một signal khác, tức là như thế sẽ phát ra 2 signal.

(ảnh : Qt)

Ví dụ

Chúng ta sẽ viết 2 lớp, một lớp phát signal và một lớp có slot nhận signal.

Emitter

#include <QObject>
#include <QString>

class Emitter : public QObject
{
    Q_OBJECT
public:
    Emitter();

    void emitSignal1();
    void emitSignal2(int);
    void emitSignal3(QString, int);

    void setName(QString);
signals:
    void signal1();
    void signal2(int);
    void signal3(QString, int);
};

Lớp Emitter là lớp phát signal, trong đó có 3 signal khác nhau.

class Emitter : public QObject
{
    Q_OBJECT
    ...
}

Để một lớp có thể sử dụng signal và slot thì lớp đó phải kế thừa từ lớp QObject và ở đầu lớp bạn phải khai báo macro Q_OBJECT.

signals:
    void signal1();
    void signal2(int);
    void signal3(QString, int);

Chúng ta dùng từ khóa signals để báo cho Qt biết phương thức nào là một signal. Các signal sẽ có kiểu trả về là void, và cũng có các tham số đi kèm như một phương thức bình thường.

#include "emitter.h"

Emitter::Emitter() {}

void Emitter::emitSignal1()
{
    emit signal1();
}

void Emitter::emitSignal2(int arg)
{
    emit signal2(arg);
}

void Emitter::emitSignal3(QString arg1, int arg2)
{
    emit signal3(arg1, arg2);
}

void Emitter::setName(QString arg)
{
    this->setObjectName(arg);
}

Các signal sẽ được gọi trong các phương thức khác nhau.

void Emitter::emitSignal1()
{
    emit signal1();
}

Để phát các signal thì bạn dùng từ khóa emit.

void Emitter::setName(QString arg)
{
    this->setObjectName(arg);
}

Lớp QObject có sẵn một thuộc tính là objectName dùng để đặt tên cho đối tượng, thuộc tính này có giá trị rỗng, chúng ta có thể đặt giá trị cho thuộc tính này thông qua phương thức setObjectName().

Receiver

#include <QString>
#include <QObject>
#include <QDebug>
#include <iostream>

#include "emitter.h"
class Receiver : public QObject
{
    Q_OBJECT
public:
    Receiver();

public slots:
    void receiveSignal1();
    void receiveSignal2(int);
    void receiveSignal3(QString, int);
};

Chúng ta viết lớp Receiver có 3 slot xử lý từng signal khác nhau.

public slots:
    void receiveSignal1();
    void receiveSignal2(int);
    void receiveSignal3(QString, int);

Các slot cũng chỉ là các phương thức bình thường. Khi khai báo bạn phải thêm từ khóa slots ở phía trước.

#include "receiver.h"

Receiver::Receiver()
{

}

void Receiver::receiveSignal1()
{
    std::cout << "Signal 1 received!\n";
}

void Receiver::receiveSignal2(int arg)
{
    std::cout << "Signal 2 came with an integer: "
              << arg
              << "\n";
}

void Receiver::receiveSignal3(QString arg1, int arg2)
{ 
     std::cout << "Signal 3 from "
               << QObject::sender()->objectName().toStdString()
               << ": "
               << arg1.toStdString()
               << " "
               << QString::number(arg2).toStdString()
               << "\n";
}

Tham số đi kèm với signal cũng sẽ là tham số được truyền vào lời gọi các phương thức slot tương ứng.

QObject::sender()->objectName().toStdString()

Ngoài ra bạn có thể lấy con trỏ tham chiếu đến đối tượng đã gửi signal thông qua phương thức QObject::sender(). Phương thức objectName() lấy thuộc tính objectName.

arg1.toStdString()

std::cout là đối tượng xuất trong thư viện C++ chuẩn, không liên quan gì đến Qt cả nên chúng ta phải chuyển đối kiểu dữ liệu từ QString của Qt sang std::string của C++ bằng phương thức toStdString().

Main

#include <QCoreApplication>
#include <QObject>

#include "emitter.h"
#include "receiver.h"

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    Emitter e1, e2;
    Receiver r1, r2;

    e1.setName("Emitter 1");
    e2.setName("Emitter 2");

    QObject::connect(&e1, SIGNAL(signal1()), &r1, SLOT(receiveSignal1()));
    e1.emitSignal1();

    QObject::connect(&e2, Emitter::signal2, &r2, Receiver::receiveSignal2);
    e2.emitSignal2(3);

    QObject::connect(&e1, Emitter::signal3, &r1, Receiver::receiveSignal3);
    QObject::connect(&e2, Emitter::signal3, &r1, Receiver::receiveSignal3);
    e1.emitSignal3("Current year is", 2016);
    e2.emitSignal3("Sex is", 0);

    return a.exec();
}

Trong hàm main() chúng ta khai báo các đối tượng Emitter và Receiver để kết nối.

QObject::connect(&e1, SIGNAL(signal1()), &r1, SLOT(receiveSignal1()));
...

QObject::connect(&e2, Emitter::signal2, &r2, Receiver::receiveSignal2);
...

Để kết nối một signal của một đối tượng với một slot của một đối tượng khác thì chúng ta dùng phương thức QObject::connect(), phương thức này có rất nhiều override khác nhau vì Qt đã được phát triển từ rất lâu rồi. Ở trên là 2 cú pháp dùng trong phương thức này.

Signal 1 received!
Signal 2 came with an integer: 3
Signal 3 from Emitter 1 : Current year is 2016
Signal 3 from Emitter 2 : Sex is 0

Java Swing – Hộp Thoại

Trong phần này chúng ta sẽ tìm hiểu cách sử dụng một số hộp thoại cơ bản là MessageBox, JFileChooser, JColorChooser.

Hộp thoại (Dialog) có 2 loại là modal (trạng thái) và modeless (vô trạng thái), hộp thoại modal khi hiện ra sẽ không cho phép người dùng tương tác với cửa sổ cha của nó, còn hộp thoại modeless thì cho phép.

Message Dialog

MessageBox dùng để hiển thị thông báo cho người dùng.

import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
public class MessageDialogsEx extends JFrame {
    
    private JPanel panel;

    public MessageDialogsEx() {

        initUI();
    }

    private void initUI() {
        
        panel = (JPanel) getContentPane();
 
        JMenuBar menubar = new JMenuBar();
        JMenu fileMenu = new JMenu("File");
 
        JMenuItem about = new JMenuItem("About");
        about.addActionListener(new ActionListener()
        {
            @Override
            public void actionPerformed(ActionEvent e)
            {
                JOptionPane.showMessageDialog(
                    panel, 
                    "This is a modal MessageBox", 
                    "About", 
                    JOptionPane.INFORMATION_MESSAGE);
            }
        });
         
        fileMenu.add(about);
      
        menubar.add(fileMenu);
        setJMenuBar(menubar);
 
        setTitle("Dialog Example");
        setSize(300, 200);
        setLocationRelativeTo(null);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
    }

    public static void main(String[] args) {

        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                MessageDialogsEx md = new MessageDialogsEx();
                md.setVisible(true);
            }
        });
    }
}

Chúng ta tạo một menu có item là About, click vào item này thì hiển thị một messagebox.

JOptionPane.showMessageDialog(panel, "This is a modal MessageBox", "About", 
                              JOptionPane.INFORMATION_MESSAGE);

Để hiển thị MessageBox thì chúng ta gọi phương thức JOptionPane.showMessageBox(), JOptionPane là một lớp tĩnh nên bạn có thể gọi nó bất kỳ lúc nào mà không cần phải tạo một đối tượng mới. Tham số gồm có đối tượng cửa sổ cha hoặc panel cha, text, title, và kiểu hộp thoại. Kiểu hộp thoại có 4 kiểu sau đây:

  • ERROR_MESSAGE
  • WARNING_MESSAGE
  • QUESTION_MESSAGE
  • INFORMATION_MESSAGE

Capture

Confirm Dialog

Đây là kiểu hộp thoại cho bạn 2 lựa chọn để trả lời cho một câu hỏi nào đó.

import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
class ConfirmDialogEx extends JFrame{

    private JPanel panel;
    public ConfirmDialogEx() {        
        initUI();
    }

    private void initUI() {

        panel = (JPanel) getContentPane();
 
        JMenuBar menubar = new JMenuBar();
        JMenu fileMenu = new JMenu("File"); 
 
        JMenuItem exit = new JMenuItem("Exit");
        exit.addActionListener(new ActionListener()
        {
            @Override
            public void actionPerformed(ActionEvent e)
            {
                int n = JOptionPane.showConfirmDialog(
                            panel, 
                            "Are you sure you want to quit?", 
                            "Alert", 
                            JOptionPane.YES_NO_OPTION);
                if(n == JOptionPane.YES_OPTION)
                    System.exit(0);
            }
        });
 
        fileMenu.add(exit);
 
        menubar.add(fileMenu);
        setJMenuBar(menubar);
  
        setTitle("Dialog Example");
        setSize(400, 300);
        setLocationRelativeTo(null);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
    }   

    public static void main(String[] args) {

        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                JDialogEx sd = new JDialogEx();
                sd.setVisible(true);
            }
        });
    }
}

Chúng ta tạo một menu có một item là exit, click vào item này thì hộp thoại sẽ hiện ra để xác nhận.

int n = JOptionPane.showConfirmDialog(
            panel, 
            "Are you sure you want to quit?", 
            "Alert", 
            JOptionPane.YES_NO_OPTION);
if(n == JOptionPane.YES_OPTION)
    System.exit(0);

Chúng ta dùng phương thức showConfirmDialog() với kiểu hộp thoại là JOptionPane.YES_NO_QUESTION. Phương thức này sẽ trả về một giá trị int khi người dùng click vào một trong hai nút Yes hoặc No tương ứng.

Capture

JFileChooser

JFileChooser là hộp thoại cho phép bạn chọn file.

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.border.LineBorder;
import javax.swing.filechooser.FileNameExtensionFilter;

public class FileChooserEx extends JFrame {

    private JPanel panel;
    private JLabel path;

    public FileChooserEx() {

        initUI();
    }

    private void initUI() {

        panel = new JPanel();
        panel.setAlignmentX(Component.LEFT_ALIGNMENT);
        panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS));
 
        JButton openFileButton = new JButton("Open File"); 
        openFileButton.addActionListener(new ActionListener()
        {
            @Override
            public void actionPerformed(ActionEvent e)
            {
                JFileChooser fc = new JFileChooser();
                fc.addChoosableFileFilter(new FileNameExtensionFilter(
                    "Image (jpg, jpeg, png, bmp, gif)", 
                    "jpg", "jpeg", "png", "bmp", "gif"
                ));
                int n = fc.showOpenDialog(panel);
 
                if(n == JFileChooser.APPROVE_OPTION)
                    path.setText(fc.getSelectedFile().getAbsolutePath());
            }
        });
 
        JButton openFolderButton = new JButton("Open Folder"); 
        openFolderButton.addActionListener(new ActionListener()
        {
            @Override
            public void actionPerformed(ActionEvent e)
            {
                JFileChooser fc = new JFileChooser();
 
                int n = fc.showOpenDialog(panel);
                fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
                if(n == JFileChooser.APPROVE_OPTION)
                    path.setText(fc.getSelectedFile().getAbsolutePath());
            }
        });
 
        panel.add(Box.createRigidArea(new Dimension(5, 5)));
        panel.add(openFileButton);
        panel.add(Box.createRigidArea(new Dimension(5, 0)));
        panel.add(openFolderButton);
 
        add(panel);
 
        path = new JLabel("Path:"); 
        path.setPreferredSize(new Dimension(-1, 22));
        path.setBorder(LineBorder.createGrayLineBorder());            
        add(path, BorderLayout.SOUTH);
 
        setTitle("Dialog Example");
        setSize(300, 100);
        setLocationRelativeTo(null);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
    }
   
    public static void main(String[] args) {

        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                FileChooserEx fcd = new FileChooserEx();
                fcd.setVisible(true);
            }
        });
    }
}

Chúng ta tạo 2 button và một label, một button mở hộp thoại chọn file, một button mở hộp thoại chọn thư mục, đường dẫn đến file và thư mục được chọn sẽ hiển thị trên label.

JFileChooser fc = new JFileChooser();

Khác với 2 messagebox trên, ở đây bạn phải tạo một đối tượng JFileChooser riêng.

fc.addChoosableFileFilter(new FileNameExtensionFilter(
    "Image (jpg, jpeg, png, bmp, gif)", 
    "jpg", "jpeg", "png", "bmp", "gif"
));

Bạn có thể thêm các bộ lọc file theo phần mở rộng của tên file với phương thức addChoosableFileFilter()  và tham số là một đối tượng FileNameExtensionFilter.

int n = fc.showOpenDialog(panel);

Để hiển thị hộp thoại chọn file thì chúng ta gọi phương thức showOpenDialog().

if(n == JFileChooser.APPROVE_OPTION)
    path.setText(fc.getSelectedFile().getAbsolutePath());

Nếu có file được chọn thì phương thức này sẽ trả về một giá trị int và bạn có thể lấy đường dẫn đến file được chọn đó.

fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);

Ngoài ra bạn cũng có thể chỉ định là chỉ mở thư mục với phương thức setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY).

Untitled

JColorChooser

Java Swing cung cấp lớp JColorChooser tạo hộp thoại chọn màu rất mạnh.

import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JColorChooser;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.border.LineBorder;

public class ColorChooserEx extends JFrame {

    private JPanel panel; 
    private JPanel display;

    public ColorChooserEx() {

        initUI();
    }

    private void initUI() {

        panel = new JPanel();
 
        JButton openColor = new JButton("Choose Color");
        openColor.addActionListener(new ActionListener()
        {
            @Override
            public void actionPerformed(ActionEvent e)
            {
                Color color = JColorChooser.showDialog(
                    panel, 
                    "Choose color", 
                    Color.white);
                display.setBackground(color);
            }
        });
 
        display = new JPanel();
        display.setPreferredSize(new Dimension(150, 150));
        display.setBorder(LineBorder.createGrayLineBorder());
        display.setBackground(Color.black); 
 
        panel.add(openColor);
        panel.add(display);
        add(panel);
  
        pack();
 
        setTitle("Dialog Example"); 
        setLocationRelativeTo(null);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
    }

    public static void main(String[] args) {

        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                ColorChooserEx ccd = new ColorChooserEx();
                ccd.setVisible(true);
            }
        });
    }
}

Để mở hộp thoại chọn màu thì chỉ cần gọi JColorChooser.showDialog(), tham số đầu tiên là đối tượng cửa sổ cha hoặc panel cha, tham số thứ 2 là tiêu đề của hộp thoại, tham số thứ 3 là màu mặc định khi mở.

Color color = JColorChooser.showDialog(panel, "Choose color", Color.white);
display.setBackground(color);

Phương thức này sẽ trả về một đối tượng Color chứa thông tin màu đã được chọn hoặc null nếu người dùng không chọn màu nào.

Capture

Java Swing – Các component cơ bản – phần 2

Trong phần này chúng ta tiếp tục tìm hiểu về một số component trong Java Swing là JList, JTextAreaJTextPane.

JList

JList được dùng để hiển thi một danh sách các đối tượng và cho phép chọn một hoặc nhiều đối tượng.

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.GraphicsEnvironment;

import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.SwingUtilities;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;


public class ListExample extends JFrame {

    private JLabel label;
    private JList list;


    public ListExample() {

        initUI();
    }

    private void initUI() {

        JPanel panel = new JPanel();
        panel.setLayout(new BorderLayout());
        panel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));

        GraphicsEnvironment ge =
            GraphicsEnvironment.getLocalGraphicsEnvironment();

        String[] fonts = ge.getAvailableFontFamilyNames();

        list = new JList(fonts);
        list.addListSelectionListener(new ListSelectionListener() {
            @Override
            public void valueChanged(ListSelectionEvent e) {
                String name = (String) list.getSelectedValue();
                Font font = new Font(name, Font.PLAIN, 12);
                label.setFont(font);                
            }
        });

        JScrollPane pane = new JScrollPane();
        pane.getViewport().add(list);
        pane.setPreferredSize(new Dimension(250, 200));
        panel.add(pane);

        label = new JLabel("War is Hell");
        label.setFont(new Font("Serif", Font.PLAIN, 12));
        add(label, BorderLayout.SOUTH);

        add(panel);

        pack();
        setTitle("Component Example");
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setLocationRelativeTo(null);
    }

    public static void main(String[] args) {
        
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                ListExample ex = new ListExample();
                ex.setVisible(true);
            }
        });
    }
}

Chúng ta tạo một JList hiển thị toàn bộ font chữ có trong máy tính và một JLabel để hiển thị font chữ đó.

GraphicsEnvironment ge = 
    GraphicsEnvironment.getLocalGraphicsEnvironment();

String[] fonts = ge.getAvailableFontFamilyNames();

Đầu tiên chúng ta lấy bộ font chữ trong máy thông qua lớp GraphicsEnvironment.

list = new JList(fonts);

Tiếp theo chúng ta tạo đối tượng JList.

String name = (String) list.getSelectedValue();
Font font = new Font(name, Font.PLAIN, 12);
label.setFont(font);

Khi đã lấy được font rồi thì chúng ta thiết lập font đó cho JLabel.

JScrollPane pane = new JScrollPane();
pane.getViewport().add(list);

Mặc định JList không có thanh trượt, muốn trượt được thì bạn phải đưa vào một đối tượng JScrollPane.

aaa

JTextArea

import java.awt.BorderLayout;
import java.awt.Dimension;

import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;

public class TextAreaExample extends JFrame {

    public TextAreaExample() {
        
        initUI();
    }

    private void initUI() {

        JPanel panel = new JPanel();
        panel.setLayout(new BorderLayout());
        panel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));

        JScrollPane pane = new JScrollPane();
        JTextArea area = new JTextArea();

        area.setLineWrap(true);       
        area.setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8));

        pane.getViewport().add(area);
        panel.add(pane);

        add(panel);

        setTitle("Component Example");
        setSize(new Dimension(350, 300));
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setLocationRelativeTo(null);
    }

    public static void main(String[] args) {

        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                TextAreaExample ex = new TextAreaExample();
                ex.setVisible(true);
            }
        });
    }
}

JTextArea cho phép tạo các ô gõ văn bản.

area.setLineWrap(true);

Phương thức setLineWrap() cho phép các dòng text tự động xuống dòng khi vượt quá kích thước của TextArea.

pane.getViewport().add(area);

Cũng giống như JList, mặc định JTextArea không thể cuộn được, chúng ta phải đưa chúng vào một đối tượng JScrollPane.

Capture

JTextPane

JTextPane là một component cấp cao dùng để hiển thị text. JTextPane cung cấp nhiều phương thức định dạng text và có thể dùng để hiển thị nguyên cả một trang HTML.

package com.javaswingtut;

import java.awt.BorderLayout;

import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextPane;
import javax.swing.SwingUtilities;

public class TextPaneExample extends JFrame {

    JTextPane textPane;

    public TextPaneExample() {

        initUI();
    }

    private void initUI() {

        JPanel panel = new JPanel();
        panel.setLayout(new BorderLayout());
        panel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));

        JScrollPane pane = new JScrollPane();
        textPane = new JTextPane();

        textPane.setContentType("text/html");
        textPane.setEditable(false);

        textPane.setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8));

        loadFile();

        pane.getViewport().add(textPane);
        panel.add(pane);

        add(panel);
        pack();

        setTitle("JTextPane");
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setLocationRelativeTo(null);
    }

    private void loadFile() {

        try {
            String cd = System.getProperty("user.dir") + "/";
            textPane.setPage("File:///" + cd + "/src/com/javaswingtut/phocode.html");
        } catch (IOException ex) {
            System.err.println("Cannot set page");
        }
    }

    public static void main(String[] args) {

        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                TextPaneExample ex = new TextPaneExample();
                ex.setVisible(true);
            }
        });
    }
}

Chúng ta sẽ tạo một JTextPane và hiển thị một trang HTML lên đó.

<html>
<head>
    <title>Welcome to Pho Code</title>
</head>
<body>

    <h2>JTextPane Example</h2>
    <b>Pho Code</b> provides tutorials on various areas such as GUI, Graphics, Programming Language.
    <p>The website's mission is to provide quick and easy to understand tutorials.</p>   
</body>
</html>
aaa

Java Swing – Các component cơ bản – phần 1

Trong phần này chúng ta tìm hiểu một số component cơ bản gồm có JLabel, JCheckBox, JSlider, JComboBox, JProgressBar, và JToggleButton.

JLabel

JLabel được dùng để hiển thị text và ảnh, không thể bắt được sự kiện.

import java.awt.Color;
import java.awt.Container;
import java.awt.EventQueue;
import java.awt.Font;
import javax.swing.GroupLayout;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;


public class LabelEx extends JFrame {

    public LabelEx() {
        
        initUI();
    }
    
    private void initUI() {

        String lyrics =  "<html>It's way too late to think of" + 
        "<br>Someone I would call now" + 
        "<br>And neon signs got tired" + 
        "<br>Red eye flights help the stars out" + 
        "<br>I'm safe in a corner" + 
        "<br>Just hours before me" + 
        "</html>";
 
        JLabel label = new JLabel(lyrics);
        label.setFont(new Font("Serif", Font.PLAIN, 14));
        label.setForeground(new Color(50, 50, 25));

        Container pane = getContentPane();
        GroupLayout gl = new GroupLayout(pane);
        pane.setLayout(gl); 
        gl.setAutoCreateContainerGaps(true);

        gl.setHorizontalGroup(gl.createSequentialGroup()
                             .addComponent(label));

        gl.setVerticalGroup(gl.createParallelGroup()
                            .addComponent(label));

        pack();

        setTitle("Component Example");
        setLocationRelativeTo(null);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
    }
       
    public static void main(String[] args) {

        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                LabelEx ex = new LabelEx();
                ex.setVisible(true);
            }
        });
    }
}

Chúng ta in lời bài hát lên cửa sổ, có một điều thú vị là chúng ta có thể dùng các tag trong HTML để tùy chỉnh phần text trong JLabel.

JLabel label = new JLabel(lyrics);
label.setFont(new Font("Serif", Font.PLAIN, 14));

Chúng ta tạo đối tượng JLabel, bạn có thể thiết lập kiểu font thông qua phương thức setFont() như trên.

pack();

Phương thức pack() thiết lập kích thước màn hình vừa đủ bao phủ các component.

Capture

JCheckBox

JCheckBox là lớp hỗ trợ tạo các checkbox. Chúng ta có thể dùng ActionListener hoặc ItemListener để lắng nghe sự kiện của JCheckBox, nhưng trong ví dụ này chúng ta sẽ dùng ItemListener.

import java.awt.Container;
import java.awt.EventQueue;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import javax.swing.GroupLayout;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
import javax.swing.JFrame;

public class CheckBoxEx extends JFrame
        implements ItemListener {

    public CheckBoxEx() {
        
        initUI();
    }

    private void initUI() {

        JCheckBox cb = new JCheckBox("Show title", true);
        cb.addItemListener(this);

        Container pane = getContentPane();
        GroupLayout gl = new GroupLayout(pane);
        pane.setLayout(gl);
        gl.setAutoCreateContainerGaps(true);

        gl.setHorizontalGroup(gl.createParallelGroup()
                             .addComponent(cb));

        gl.setVerticalGroup(gl.createSequentialGroup()
                             .addComponent(cb));
        
        setSize(280, 200);
        setTitle("Component Example");
        setLocationRelativeTo(null);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }
    
    @Override
    public void itemStateChanged(ItemEvent e) {
        
        int sel = e.getStateChange();
        
        if (sel==ItemEvent.SELECTED) {
            
            setTitle("Component Example");
        } else {
            
            setTitle("");
        }
    }           

    public static void main(String[] args) {

        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                CheckBoxEx ex = new CheckBoxEx();
                ex.setVisible(true);
            }
        });
    }
}

Chúng ta sẽ cho ẩn hoặc hiện tiêu đề cửa sổ tùy vào trạng thái của checkbox.

JCheckBox checkbox = new JCheckBox("Show title", true);

Đầu tiên chúng ta tạo đối tượng JCheckBox. Tham số true tức là mặc định checkbox sẽ được check.

cb.addItemListener(this);

Chúng ta gọi phương thức addItemListener() để gắn listener vào checkbox, ở đây listener chính là lớp chứa phương thức main() vì lớp này đã cài đặt giao diện ItemListener.

@Override
public void itemStateChanged(ItemEvent e) {
    
    int sel = e.getStateChange();
    
    if (sel==ItemEvent.SELECTED) {
        
        setTitle("JCheckBox");
    } else {
        
        setTitle("");
    }
}  

Lớp cài đặt giao diện ItemListener sẽ phải override phương thức ảo  getStateChange(), ở đây chúng ta thiết lập lại tiêu đề cửa sổ tùy thuộc vào trạng thái của checkbox.

Capture

JSlider

JSlider hỗ trợ tạo slider, đây là một thanh trượt biểu diễn giá trị. Slider sử dụng listener là lớp ChangeListener với phương thức stateChanged().

import java.awt.Container;
import java.awt.EventQueue;
import javax.swing.GroupLayout;
import static javax.swing.GroupLayout.Alignment.CENTER;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JSlider;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

public class SliderEx extends JFrame {

    private JSlider slider;
    private JLabel lbl;
   
    public SliderEx() {
        
        initUI();
    }

    private void initUI() {                      
        
        slider = new JSlider(0, 100, 0);

        slider.addChangeListener(new ChangeListener() {
 
            @Override
            public void stateChanged(ChangeEvent event) { 
                lbl.setText(String.valueOf(slider.getValue()));
            }
        });

        lbl = new JLabel("0", JLabel.CENTER);

        Container pane = getContentPane();
        GroupLayout gl = new GroupLayout(pane);
        pane.setLayout(gl); 
 
        gl.setAutoCreateContainerGaps(true);
        gl.setAutoCreateGaps(true);
 
        gl.setHorizontalGroup(gl.createSequentialGroup()
            .addComponent(slider)
            .addComponent(lbl));

        gl.setVerticalGroup(gl.createParallelGroup(CENTER)
            .addComponent(slider)
            .addComponent(lbl)); 

        pack();
 
        setTitle("Example Component");
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setLocationRelativeTo(null);
    }    

    public static void main(String[] args) {

        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                SliderEx ex = new SliderEx();
                ex.setVisible(true);
            }
        });
    }
}

Chúng ta hiển thị một slider biểu diễn giá trị từ 0 đến 100 và 1 label để in giá trị hiện tại lên cửa sổ.

slider = new JSlider(0, 100, 0);

Đầu tiên chúng ta khởi tạo đối tượng JSlider, hai tham số đầu tiên là giá trị min và max, tham số thứ 3 là giá trị khởi tạo của slider.

slider.addChangeListener(new ChangeListener() {
...
});

Chúng ta gắn listener vào slider, listener này sẽ lắng nghe sự thay đổi của slider và cập nhật lại giá trị lên label.

Capture

JComboBox

Java Swing cung cấp lớp JComboBox hỗ trợ tạo combo box.

import java.awt.Container;
import java.awt.EventQueue;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import javax.swing.GroupLayout;
import static javax.swing.GroupLayout.Alignment.BASELINE;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JSlider;

public class ComboBoxEx extends JFrame
        implements ItemListener {

    private JLabel display;
    private JComboBox<String> box;
    private String[] distros;

    public ComboBoxEx() {

        initUI();        
    }

    private void initUI() {

        distros = new String[]{"Ubuntu", "Redhat", "Arch",
            "Debian", "Mint"};

        box = new JComboBox<>(distros);
        box.addItemListener(this);

        display = new JLabel("Ubuntu");
        
        Container pane = getContentPane();
        GroupLayout gl = new GroupLayout(pane);
        pane.setLayout(gl); 

        gl.setAutoCreateContainerGaps(true);
        gl.setAutoCreateGaps(true);

        gl.setHorizontalGroup(gl.createSequentialGroup()
            .addComponent(box)
            .addComponent(display));

        gl.setVerticalGroup(gl.createParallelGroup(BASELINE)
            .addComponent(box)
            .addComponent(display));

        pack();

        setTitle("Component Example"); 
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
        setLocationRelativeTo(null);
    }
    
    @Override
    public void itemStateChanged(ItemEvent e) {

        if (e.getStateChange() == ItemEvent.SELECTED) {
            display.setText(e.getItem().toString());
        }
    }

    public static void main(String[] args) {

        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                ComboBoxEx ex = new ComboBoxEx();
                ex.setVisible(true);
            }
        });
    }
}

Chúng ta hiển thị một combo box bao gồm các item là tên của một số bản phân phối khác nhau của hệ điều hành linux, một label hiển thị tên tùy theo item đang được chọn trong combo box.

distros = new String[] {"Ubuntu", "Redhat", "Arch", 
    "Debian", "Mint"};

Các item trong JComboBox được lưu trong một mảng string, tuy nhiên bạn có thể dùng các phương thức riêng của JComboBox để thêm các item vào.

box = new JComboBox<>(distros);
box.addItemListener(this);

Chúng ta khởi tạo một đối tượng JComboBox và gắn listener vào nó.

@Override
public void itemStateChanged(ItemEvent e) {
    
    if (e.getStateChange() == ItemEvent.SELECTED) {
        display.setText(e.getItem().toString());
    }
}    

Phương thức itemStateChanged() sẽ được gọi mỗi khi một item khác được chọn trong combobox, bên trong chúng ta sẽ cho thay đổi giá trị của label tùy thuộc vào item trong combobox.Capture

JProgressBar

“Progress Bar” được dùng để biểu diễn tiến trình hoạt động của một công việc nào đó. Java Swing cung cấp lớp JProgressBar để tạo các progress bar.

import java.awt.Container;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.AbstractAction;
import javax.swing.GroupLayout;
import static javax.swing.GroupLayout.Alignment.CENTER;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JProgressBar;
import javax.swing.Timer;
public class ProgressBarEx extends JFrame {

    private Timer timer;
    private JProgressBar progressBar;
    private JButton button;

    public ProgressBarEx() {
        
        initUI();
    }

    private void initUI() {

        progressBar= new JProgressBar();
        progressBar.setStringPainted(true);
        
        button= new JButton("Start");
        button.addActionListener(new ClickAction());

        timer = new Timer(50, new UpdateBarListener());

        Container pane = getContentPane();
        GroupLayout gl = new GroupLayout(pane);
        pane.setLayout(gl); 

        gl.setAutoCreateContainerGaps(true);
        gl.setAutoCreateGaps(true);

        gl.setHorizontalGroup(gl.createSequentialGroup()
            .addComponent(progressBar)
            .addComponent(button));

        gl.setVerticalGroup(gl.createParallelGroup(CENTER)
            .addComponent(progressBar)
            .addComponent(button)); 

        pack();
        
        setTitle("Component Example");
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setLocationRelativeTo(null);
    }       
    
    private class UpdateBarListener implements ActionListener {

        @Override
        public void actionPerformed(ActionEvent e) {
            
                int val = progressBar.getValue();
                
                if (val >= 100) {
                    
                    timer.stop();
                    button.setText("End");
                    return;
                }

                progressBar.setValue(++val);           
        }
    }
    
    private class ClickAction extends AbstractAction {

        @Override
        public void actionPerformed(ActionEvent e) {
            
                if (timer.isRunning()) {
                    
                    timer.stop();
                    button.setText("Start");

                } else if (!"End".equals(button.getText())) {
                    
                    timer.start();
                    button.setText("Stop");
                }       
        }
    }

    public static void main(String[] args) {
        
        EventQueue.invokeLater(new Runnable() {
            
            @Override
            public void run() {
                ProgressBarEx ex = new ProgressBarEx();
                ex.setVisible(true);
            }
        });
    }
}

Chúng ta hiển thị một progress bar và một button. Button có tác dụng kích hoạt hoặc dừng progress bar.

progressBar = new JProgressBar();
progressBar.setStringPainted(true);

Chúng ta khởi tạo đối tượng JProgressBar, mặc định thì giá trị khởi tạo là từ 0 đến 100. Phương thức setStringPainted(true) có tác dụng hiển thị số % trong progressbar.

timer = new Timer(50, new UpdateBarListener());

Chúng ta tạo một đối tượng Timer có delay là 50ms và gắn một ActionListener vào timer này,

private class UpdateBarListener implements ActionListener {

    @Override
    public void actionPerformed(ActionEvent e) {
        
            int val = progressBar.getValue();
            
            if (val >= 100) {
                
                timer.stop();
                button.setText("End");
                return;
            }

            progressBar.setValue(++val);           
    }
}

Bên trong listener chúng ta lấy giá trị hiện tại và tăng lên trong progress bar. Nếu giá trị đạt đến 100 thì dừng timer và thay đổi text bên trong button.

private class ClickAction extends AbstractAction {

    @Override
    public void actionPerformed(ActionEvent e) {
    
        if (timer.isRunning()) {
        
            timer.stop();
            button.setText("Start");

        } else if (!"End".equals(button.getText())) {
        
            timer.start();
            button.setText("Stop");
        }       
    }
}

Button cũng được gắn một listener làm công việc dừng hoặc chạy tiếp timer đồng thời thay đổi giá trị trên button.

Untitled

JToggleButton

JToggleButton cho phép tạo các nút button có 2 trạng thái là bật hoặc tắt, giống như chech box vậy nhưng có hình dạng như button.

import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.GroupLayout;
import static javax.swing.GroupLayout.Alignment.CENTER;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JToggleButton;
import static javax.swing.LayoutStyle.ComponentPlacement.UNRELATED;
import javax.swing.border.LineBorder;

public class ToggleButtonEx extends JFrame
        implements ActionListener {

    private JToggleButton redButton;
    private JToggleButton greenButton;
    private JToggleButton blueButton;
    private JPanel display;

    public ToggleButtonEx() {

        initUI();
    }

    private void initUI() {

        redButton = new JToggleButton("red");
        redButton.addActionListener(this);

        greenButton = new JToggleButton("green");
        greenButton.addActionListener(this);

        blueButton = new JToggleButton("blue");
        blueButton.addActionListener(this);

        display = new JPanel();
        display.setPreferredSize(new Dimension(120, 120));
        display.setBorder(LineBorder.createGrayLineBorder());
        display.setBackground(Color.black);

        Container pane = getContentPane();
        GroupLayout gl = new GroupLayout(pane);
        pane.setLayout(gl); 
 
        gl.setAutoCreateContainerGaps(true);
        gl.setAutoCreateGaps(true);
 
        gl.setHorizontalGroup(gl.createSequentialGroup()
            .addGroup(gl.createParallelGroup()
            .addComponent(redButton)
            .addComponent(greenButton)
            .addComponent(blueButton))
            .addPreferredGap(UNRELATED)
            .addComponent(display));

        gl.setVerticalGroup(gl.createParallelGroup(CENTER)
            .addGroup(gl.createSequentialGroup()
            .addComponent(redButton)
            .addComponent(greenButton)
            .addComponent(blueButton)) 
            .addComponent(display)); 
 
        gl.linkSize(redButton, greenButton, blueButton);

        pack(); 
 
        setTitle("Component Example");
        setLocationRelativeTo(null);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }    

    @Override
    public void actionPerformed(ActionEvent e) {

        Color color = display.getBackground();
        int red = color.getRed();
        int green = color.getGreen();
        int blue = color.getBlue();

        if (e.getActionCommand().equals("red")) {
            if (red == 0) {
                red = 255;
            } else {
                red = 0;
            }
        }

        if (e.getActionCommand().equals("green")) {
            if (green == 0) {
                green = 255;
            } else {
                green = 0;
            }
        }

        if (e.getActionCommand().equals("blue")) {
            if (blue == 0) {
                blue = 255;
            } else {
                blue = 0;
            }
        }

        Color setCol = new Color(red, green, blue);
        display.setBackground(setCol);
    }

    public static void main(String[] args) {

        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                ToggleButtonEx ex = new ToggleButtonEx();
                ex.setVisible(true);
            }
        });
    }
}

Chúng ta tạo 3 JToggleButton đại diện cho 3 màu đỏ, xanh lá, xanh lam và một JPanel để hiển thị màu. Button nào được bật thì màu tương ứng sẽ tham gia vào quá trình trộn màu để hiển thị trên panel.

redButton = new JToggleButton("red");
redButton.addActionListener(this);

Chúng ta tạo từng đối tượng JToggleButton, thiết lập text cho chúng và gắn listener vào.

Color color = display.getBackground();
int red = color.getRed();
int green = color.getGreen();
int blue = color.getBlue();

Lấy giá trị RGB từ JPanel.

if (e.getActionCommand().equals("red")) {
    if (red == 0) {
        red = 255;
    } else {
        red = 0;
    }
}

Bên trong listener, chúng ta xác định xem button nào đang được bật để thiết lập giá trị màu tương ứng.

Color setCol = new Color(red, green, blue);
display.setBackground(setCol);

Sau đó trộn chúng lại trong một đối tượng Color và đưa vào JPanel thông qua phương thức setBackground().

Capture