Lập trình ứng dụng Java Desktop sử dụng SWT

1- Giới thiệu

Tài liệu này được viết dựa trên :

  • Eclipse 4.6, 47 (NEON, OXYGEN)

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.

Xem thêm :

  • Các nền tảng nào bạn nên chọn để lập trình ứng dụng Java Desktop?

2- RCP (Rich Client Platform)

RCP (Rich Client Platform) – Là một nền tảng (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 với Eclipse IDE, hoặc lập trình các Plugin có thể tích hợp vào 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ởi RCP bạn cũng nên tạo một ứng dụng RCP.
 


SWT :

Workbench Application :

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.

Để tạo một ứng dụngsử dụng. Trênchúng ta sẽ tạo mộtproject. Bạn có 2 lựa chọn .

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.7 (Mã hiệu OXYGEN).

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

Lớp DisplayShell là các thành phần khóa (key) của ứng dụng SWT.

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

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 widget được đặt tại packages org.eclipse.swt.widgetsorg.eclipse.swt.custom. Các widget này được mở rộng từ lớp 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 hoàn toà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).



Project đã được tạo ra :

Thêm thư viện swt:  org.eclipse.swt

Nếu bạn lập trình ứng dụng 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, 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 hoàn toà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 bản giới thiệu (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.


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 trống (space).
  • GridLayout – Giàng buộc các Widget trong một mạng lưới  

Cácchuẩn củalà :

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.

  • TODO Link?

9.3- FillLayout

FillLayout là lớp layout đơn giản. Nó đặt 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 wrap (gói) các Widget, 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ì nó có khả năng wrap (gói), và bởi vì nó hỗ trợ margin spacing. RowLayout có một số trường (field) 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 cho các widget bằng cách sử dụng setLayoutData

Các trường thông số kỹ thuật :
Wrap, Pack, Justify :

  Ban đầu Sau khi thay đổi kích thước
wrap = true
pack = true
justify = false
 
(Mặc định)
wrap = false
 
(Bị 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, và căn đều)

MarginLeft, MarginTop, MarginRight, MarginBottom, Spacing :

Là các trường (field) 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 cạnh của Composite cha (margin – Lề). Theo mặc định, RowLayout sử dụng 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 thông số kỹ thuật 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ể được nhìn thấy. Người sử dụng phải thiết lập giá trị topControl và sau đó gọi phương thức layout() của 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.

  • TODO

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 lớp mở rộng từ một lớp 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 đè phương thức checkSubclass() mà không cần phải làm gì trong phương thức đó.

MyButton. java


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 phong cách 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ỏ phong cách thiết kế, tuy nhiên đây là ví dụ minh họa cho việc chia nhỏ phong cách 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 lớp 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 lớp 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ự thêm 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- Xử lý sự kiện

Xử 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: