Hướng dẫn lập trình Java Desktop sử dụng SWT
Công ty Vĩnh Cửu tuyển dụng lập trình viên Java

1- Giới thiệu

Tài liệu này được viết dựa trên:
  • Eclipse 4.4 (LUNA)

Trong tài liệu hướng dẫn này tôi sẽ giới thiệu với các bạn về lập trình ứng dụng Desktop với SWT.

Câu hỏi là trong Java có các cách lựa chọn công nghệ nào để lập trình ứng dụng Desktop, hoặc công nghệ nào giúp lập trình một ứng dụng web có giao diện giống ứng dụng Desktop. Nếu chưa xem bạn có thể xem trước khi quyết định sẽ sử dụng cái nào.

Bạn có thể xem tại ĐÂY.

2- RCP (Rich Client Platform)

RCP (Rich Client Platform) - Là một Platform xây dựng trên SWT, dùng để lập trình các ứng các ứng dụng Desktop và xa hơn nữa nó đã xây dựng sẵn một nền tảng cho phép bạn phát triển các ứng dụng Desktop kiểu Workbench, giống Eclipse IDE, hoặc lập trình các Plugin có thể tích hợp lên Eclipse IDE.

Tuy nhiên cho dù chỉ muốn dùng SWT để lập trình và không cần sử dụng nhưng thứ được cung cấp bở RCP bạn cũng nên tạo một ứng dụng RCP.
 

SWT:

Workbench Application:

Để tạo một ứng dụng Desktop sử dụng SWT. Trên Eclipse chúng ta sẽ tạo một RCP Plugin Project. Bạn có 2 lựa chọn.
  • Chỉ sử dụng các tính năng của SWT
  • Sử dụng nền tảng cung cấp bởi RCP để lập trình ứng dụng RCP Workbench.
Trong tài liệu này tôi sẽ hướng dẫn các bạn làm quen với lập trình SWT cơ bản, sử dụng WindowBuilder để kéo thả các thành phần vào giao diện.

3- Các cài đặt cần thiết trước khi bắt đầu

Một số cài đặt cần thiết trước khi bắt đầu:

Bạn cần có Eclipse phiên bản mới nhất. Hiện tại đang là Eclipse 4.4 (Mã hiệu LUNA). Theo tôi bạn lên download package: "Eclipse IDE for Java EE Developers". Các package chỉ khác nhau số lượng Plugin, cho các mục đích lập trình khác nhau. Trong quá trình lập trình có thể cài thêm các Plugin cho các mục đích khác.
Cài đặt Plugin WindowBuilder, đây là 1 Plugin cho phép bạn thiết kế giao diện ứng dụng SWT bằng cách kéo thả rất tiện lợi.
Xem hướng dẫn cài đặt tại:

4- Một số khái niệm về SWT.

4.1- Display & Shell

Class Display và Shell là các thành phần khóa của ứng dụng SWT.

Class org.eclipse.swt.widgets.Shell mô tả một cửa sổ (Window)

Lớp org.eclipse.swt.widgets.Display chịu trách nhiệm quản lý sự kiện lặp (event loops), phông chữ, màu sắc và kiểm soát các thông tin liên lạc giữa các tiến trình giao diện người dùng (UI Thread) và các tiến trình khác (other Thread). Display là cơ sở cho tất cả các khả năng của SWT.

Mỗi ứng dụng SWT đòi hỏi ít nhất một Display và một hoặc nhiều đối tượng Shell. 
Ví dụ:
Display display = new Display();
Shell shell = new Shell(display);
shell.open();

// Chạy vòng lặp sự kiện miễn là các cửa sổ đang mở
while (!shell.isDisposed()) {
   // Đọc các sự kiện hàng đợi tiếp theo của hệ điều hành và chuyển nó vào một sự kiện SWT

 if (!display.readAndDispatch())
  {
 // Nếu hiện tại không có sự kiện của hệ điều hành nào để xử lý
 // Ngủ cho tới khi có sự kiện
   display.sleep();
  }
}

// Trường hợp người dùng đóng cửa sổ
// Hủy (dispose) cửa sổ, và tất cả các thành phần liên quan
display.dispose();

4.2- SWT Widgets

Các SWT widgets được đặt tại packages org.eclipse.swt.widgetsorg.eclipse.swt.custom. Các Widget này được mở rộng từ class Widget hoặc Control. Một vài widget được mô tả tại hình minh họa dưới. Chi tiết bạn có thể xem tại SWT widget homepage.

5- Tạo RCP Plugin Project

Trên Eclipse chọn File/New/Other...
  • Vì không có nhu cầu tạo một ứng dụng Workbench cho nên chúng ta không check vào (1) như hình minh họa dưới.
  • Chọn Yes tại vùng (2) để Eclipse tạo ra RCP Application (Chạy trên Desktop), ngược lại nó sẽ tạo ra RAP Application (Chạy trên Web).
Đây là hình ảnh Project được tạo ra:
Thêm thư viện swt:  org.eclipse.swt

Nếu bạn lập trình RAP thư viện tương ứng là org.eclipse.rap.rwt

6- Ví dụ bắt đầu

Đây là ví dụ đơn giản, viết bằng tay, chưa sử dụng đến công cụ kéo thả WindowBuilder.
HelloSWT.java
package org.o7planning.tutorial.swt.helloswt;

import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;

public class HelloSWT {

   public static void main(String[] args) {
       // Tạo đối tượng Display
       Display display = new Display();
       // Tạo đối tượng Shell (Window)
       Shell shell = new Shell(display);

       shell.open();

       while (!shell.isDisposed()) {
           if (!display.readAndDispatch())
               display.sleep();
       }
       display.dispose();
   }
}
Nhấn phải chuột vào HelloSWT.java và chọn Run As/Java Application.
Kết quả chạy ví dụ:

7- Sử dụng WindowBuilder

Tiếp theo chúng ta sẽ tạo một ví dụ với việc kéo thả trên WindowBuilder.
File/New/Other ..
Đây là cửa sổ thiết kế của WindowBuilder. Nó cho phép bạn kéo thả các Widget một cách dễ dàng.
Bạn có thể xem video dưới đây:
 
 

8- SWT Widget

8.1- Tổng quan

Đây là cây phân cấp các Widget có trong SWT.
Bạn có thể xem Demo về các Control tại link dưới đây, đó là RWT Control, nhưng về bản chất chúng giống với SWT control.

Demo:

8.2- Widget có thể chứa các Widget khác (Container)

8.3- Các Control

9- SWT Layout

9.1- Layout là gì?

Nói một cách đơn giản Layout là cách bố trí sắp xếp các thành phần lên trên giao diện.
Các Layout chuẩn của SWT là:
  • FillLayout – Làm các Widget có kích thước bằng nhau trong một hàng hoặc cột
  • RowLayout – Giàng buộc các Widget trong một hàng hoặc hàng, lấp đầy (Fill), bọc (Wrap), và các tùy chọn khoảng cách
  • GridLayout – Giàng buộc các Widget trong một mạng lưới  

9.2- Ví dụ trực tuyến

Đây là một ví dụ trực tuyến, cho phép bạn xem cách hành sử của các Layout.

9.3- FillLayout

FillLayout là class bố trí đơn giản. Nó đưa ra các vật dụng (Widget) trong một hàng hoặc cột, buộc chúng phải có cùng kích thước. Ban đầu, các Widget tất cả sẽ được cao như các Widget cao nhất, và rộng như widget rộng nhất. FillLayout không bọc (wrap), và bạn không thể xác định lề (margin) hoặc khoảng cách (space).
FillLayout fillLayout = new FillLayout();
fillLayout.type = SWT.VERTICAL;
shell.setLayout(fillLayout);
  Ban đầu Sau khi thay đổi kích thước
fillLayout.type = SWT.HORIZONTAL
(Mặc định)
fillLayout.type = SWT.VERTICAL
Video:
 
 
FillLayoutExample.java
package org.o7planning.tutorial.swt.layout;

import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.RowLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;

public class FillLayoutExample {

   public static void main(String[] args) {
       Display display = new Display();
       final Shell shell = new Shell(display);
       shell.setLayout(new FillLayout());

       //
       Composite parent = new Composite(shell, SWT.NONE);
       
       FillLayout fillLayout= new FillLayout();
       fillLayout.type= SWT.VERTICAL;
       
       parent.setLayout(fillLayout);

       Button b1 = new Button(parent, SWT.NONE);
       b1.setText("B1");

       Button b2 = new Button(parent, SWT.NONE);
       b2.setText("B2");

       Button button3 = new Button(parent, SWT.NONE);
       button3.setText("Button 3");
       
       // Làm cửa sổ trở về kích thước tự nhiên.
       shell.pack();
       //
       shell.open();
       while (!shell.isDisposed()) {
           if (!display.readAndDispatch())
               display.sleep();
       }
       // tear down the SWT window
       display.dispose();
   }
}
Kết quả chạy ví dụ:

9.4- RowLayout

RowLayout được sử dụng nhiều hơn FillLayout vì khả năng của mình để bọc (Wrap), và bởi vì nó cung cấp cấu hình margin và spacing. RowLayout có một số trường cấu hình. Ngoài ra, chiều cao và chiều rộng của mỗi widget trong một RowLayout có thể được xác định bằng cách thiết lập một đối tượng RowData vào các widget bằng cách sử dụng setLayoutData

Các trường cấu hình:

Wrap, Pack, Justify:
  Ban đầu Sau khi thay đổi kích thước
wrap = true
pack = true
justify = false
 
(Mặc định)
wrap = false
 
(Xén bớt nếu không đủ không gian)
pack = false
 
(Tất cả các Widget sẽ có cùng kích thước)
justify = true
 
(Các Widget sẽ được trải ra trên các không gian có sẵn, căn đều)
MarginLeft, MarginTop, MarginRight, MarginBottom, Spacing:

Là các trường kiểm soát số lượng điểm ảnh (Pixel) giữa các widget (space - khoảng cách) và số lượng điểm ảnh giữa một widget và một bên Composite mẹ (margin - Lề). Theo mặc định, RowLayouts lại 3 Pixel cho căn lề (margin) và khoảng cách (space). Chi tiết xem hình minh họa dưới đây.
Video:
 

9.5- GridLayout

GridLayout là hữu ích và mạnh mẽ nhất trong các Layout tiêu chuẩn, nhưng nó cũng là phức tạp nhất. Với một GridLayout, các Widget con của một Composite được đặt trong một mạng lưới. GridLayout có một số trường cấu hình, và, giống như RowLayout, các Widget nằm trong GridLayout có thể có một đối tượng dữ liệu Layout liên quan, được gọi là GridData. Sức mạnh của GridLayout nằm trong khả năng cấu hình GridData cho mỗi widget điều khiển bởi GridLayout.

Các trường cấu hình của GridLayout:

  • NumColumns
  • MakeColumnsEqualWidth

Video GridLayout:

 

9.6- StackLayout

StackLayout xếp tất cả Widget thành một chồng, chúng sẽ có cùng kích thước và vị trí. Trường topControl quy định Widget nào đứng ở trên cùng và có thể nhìn thấy. Người sử dụng phải thiết lập giá trị topControl và sau đó gọi layout() tại Composite cha.

Video StackLayout:

 

9.7- Kết hợp các Layout

Trên kia chúng ta đã làm quen với các Layout tiêu chuẩn, và việc kết hợp các Layout khác nhau, và các bộ chứa (Container) khác nhau (Composite, TabFolder, SashForm,.. ) sẽ tạo nên các giao diện mong muốn.

10- Viết các class mở rộng từ các widget của SWT

Đôi khi bạn có nhu cầu viết một class mở rộng từ một class widget có sẵn của SWT. Việc đó hoàn toàn bình thường, tuy nhiên có một ghi chú nhỏ, bạn cần viết đè method checkSubclass() mà không cần phải làm gì trong method đó.
package org.o7planning.tutorial.swt.swtsubclass;

import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;

public class MyButton extends Button {

   public MyButton(Composite parent, int style) {
       super(parent, style);
   }

   // Cần phải ghi đè method này.
   // Vì method cha ném ra ngoại lệ
   @Override
   protected void checkSubclass() {
      // Không cần làm gì cả.    
   }

   
}

11- Module hóa các thành phần giao diện

Trong các trường hợp bạn phải thiết kế một giao diện phức tạp. Việc chia nhỏ các thiết kế ra là cần thiết và sau đó ghép lại với nhau, với việc thiết kế trên WindowBuilder việc đó càng trở lên dễ dàng hơn.

Hãy xem một giao diện như sau, và chúng ta sẽ tìm cách chia nhỏ nó ra.
Giả sử rằng bạn muốn thiết kế một giao diện giống hình minh họa dưới đây. (Nó không phức tạp để cần phải phân nhỏ thiết kế, tuy nhiên đây là ví dụ minh họa cho việc chia nhỏ thiết kế giao diện như thế nào).
Chúng ta có thể thiết kế 2 Composite riêng biệt và ghép nó lại trên MainComposite.

TopComposite

  • File/New/Other...
Thiết kế giao diện cho TopComposite.
TopComposite.java
package org.o7planning.tutorial.swt.module;

import org.eclipse.swt.widgets.Composite;

public class TopComposite extends Composite {
  private Text text;

  /**
   * Create the composite.
   * @param parent
   * @param style
   */
  public TopComposite(Composite parent, int style) {
      super(parent, style);
      setLayout(new FillLayout(SWT.HORIZONTAL));
     
      Composite composite = new Composite(this, SWT.NONE);
      composite.setLayout(new GridLayout(1, false));
     
      Button btnPreferredSite = new Button(composite, SWT.CHECK);
      btnPreferredSite.setText("Preferred Site");
     
      Label lblColumnWidth = new Label(composite, SWT.NONE);
      lblColumnWidth.setText("Column width");
     
      text = new Text(composite, SWT.BORDER);
      text.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));

  }

  @Override
  protected void checkSubclass() {
  }
}

BottomComposite

Tương tự tạo class BottomComposite:
Thiết kế giao diện cho BottomComposite:
BottomComposite.java
package org.o7planning.tutorial.swt.module;

import org.eclipse.swt.SWT;

public class BottomComposite extends Composite {
  private Text text;

  /**
   * Create the composite.
   * @param parent
   * @param style
   */
  public BottomComposite(Composite parent, int style) {
      super(parent, style);
      setLayout(new FillLayout(SWT.HORIZONTAL));
     
      Composite composite = new Composite(this, SWT.NONE);
      composite.setLayout(new GridLayout(1, false));
     
      Composite composite_1 = new Composite(composite, SWT.NONE);
      GridLayout gl_composite_1 = new GridLayout(3, false);
      gl_composite_1.marginHeight = 0;
      gl_composite_1.marginWidth = 0;
      composite_1.setLayout(gl_composite_1);
      composite_1.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
     
      Button btnNewButton = new Button(composite_1, SWT.NONE);
      btnNewButton.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
      btnNewButton.setText("Add");
     
      Button btnNewButton_1 = new Button(composite_1, SWT.NONE);
      btnNewButton_1.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
      btnNewButton_1.setText("Delete");
     
      Button btnNewButton_2 = new Button(composite_1, SWT.NONE);
      btnNewButton_2.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
      btnNewButton_2.setText("Clear");
     
      text = new Text(composite, SWT.BORDER | SWT.MULTI);
      text.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1));

  }

  @Override
  protected void checkSubclass() {
      // Disable the check that prevents subclassing of SWT components
  }

}

MainComposite

Tương tự tạo class MainComposite:

Đăng ký TopComposite & BottomComposite lên Palette

Nhấn phải chuột lên Palette và chọn Add category...
Đặt tên Pallete Category:
  • My Composite
Nhấn phải chuột vào Pallette Category "My Composite" để thêm TopComposite & BottomComposite vào.
Tương tự add BottomComposite vào catalog My Composite.
Bây giờ TopComposite & BottomComposite dễ dàng được kéo thả vào các Composite khác.
MainComposite.java
package org.o7planning.tutorial.swt.module;

import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;

public class MainComposite extends Composite {

  /**
   * Create the composite.
   * @param parent
   * @param style
   */
  public MainComposite(Composite parent, int style) {
      super(parent, style);
      setLayout(new FillLayout(SWT.HORIZONTAL));
     
      Composite composite = new Composite(this, SWT.NONE);
      composite.setLayout(new GridLayout(1, false));
     
      TopComposite topComposite = new TopComposite(composite, SWT.BORDER);
      topComposite.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
     
      BottomComposite bottomComposite = new BottomComposite(composite, SWT.BORDER);
      bottomComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1));

  }

  @Override
  protected void checkSubclass() {
  }

}

12- Sử lý sự kiện

Sử lý sự kiện SWT rất đơn giản với sự hỗ trợ của WindowBuilder.

Các ví dụ minh họa:

  • File/New/Other...
  • Package: org.o7planning.tutorial.swt.event1
  • Name: ButtonEventDemo
Nhấn phải chuột lên Button, chọn Add event handler, một loạt các sự kiện tương ứng với Button hiển thị ra.
WindowBuilder sẽ tự động tạo code cho bạn:
btnClickToMe.addSelectionListener(new SelectionAdapter() {
    @Override
    public void widgetSelected(SelectionEvent e) {
        // Sử lý sự kiện Button được chọn tại đây.
        System.out.println("Button selected!");
    }
});
ButtonEventDemo.java
package org.o7planning.tutorial.swt.event1;

import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.RowLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;

public class ButtonEventDemo {

  protected Shell shlButtonEventDemo;

  public static void main(String[] args) {
      try {
          ButtonEventDemo window = new ButtonEventDemo();
          window.open();
      } catch (Exception e) {
          e.printStackTrace();
      }
  }

  public void open() {
      Display display = Display.getDefault();
      createContents();
      shlButtonEventDemo.open();
      shlButtonEventDemo.layout();
      while (!shlButtonEventDemo.isDisposed()) {
          if (!display.readAndDispatch()) {
              display.sleep();
          }
      }
  }

  protected void createContents() {
      shlButtonEventDemo = new Shell();
      shlButtonEventDemo.setSize(296, 205);
      shlButtonEventDemo.setText("Button Event Demo");
      shlButtonEventDemo.setLayout(new RowLayout(SWT.HORIZONTAL));

      Button btnClickToMe = new Button(shlButtonEventDemo, SWT.NONE);
      btnClickToMe.addSelectionListener(new SelectionAdapter() {
          @Override
          public void widgetSelected(SelectionEvent e) {
              // Handle Button selected here!
              System.out.println("Button selected!");
          }
      });
      btnClickToMe.setText("Click to me");

  }

}

13- Dialog

  • TODO ...

14- JFace

Bạn có thể xem thêm JFace, một API bổ xung cho SWT: