Программирование приложения Java Desktop используя SWT

1- Введение

Статья основана на:
  • Eclipse 4.6, 47 (NEON, OXYGEN)

В данной статье я ознакомлю вас с программированием приложения Desktop с SWT.

Вопросом является, есть ли в Java способ выбора технологии для программирования приложения Desktop, или какая технология помогает программировать веб приложение с интерфейсом похожим на приложение Desktop.

Смотрите так же:

2- RCP (Rich Client Platform)

RCP (Rich Client Platform) - Это платформа (Platform) разработанная на основе SWT, используется для программирования приложений Desktop и еще дальше он построил для вас платформу, позволяющую вас разрабатывать приложения Desktop вида  Workbench, похоже на  Eclipse IDE, или программировать Plugin которые могут интегрировать в  Eclipse IDE.

Но даже если вы хотите использовать только SWT для программирования не использовать то, что предоставлено RCP-ом вам так же нужно создать приложение RCP.
 

SWT:

Workbench Application:

Чтобы создать приложение  Desktop используя  SWT. В Eclipse мы создадим  RCP Plugin project. У вас есть 2 варианта.
  • Использовать только свойства SWT
  • Использовать платформу предоставленную RCP для программирования приложения RCP Workbench.
В данной статье я покажу вам как ознакомиться с программированием стандартного  SWT, используя  WindowBuilder чтобы перетаскивать компоненты в интерфейс.

3- Необходимые настройки для начала

Есть необходимые настройки, которые нужно сделать перед тем, как начать:

Вам нужно иметь  Eclipse самой новой версии. Сейчас это Eclipse 4.7 (Код OXYGEN).
По-моему вам нужно скачать пакет: "Eclipse IDE for Java EE Developers". Пакеты отличаются колличеством Plugin, для разных целей программирования. В процессе программирования можно установить дополнительно Plugin для других целей.
Установка Plugin WindowBuilder, это 1 Plugin позволяющий вас сделать интерфейс дизайна приложения SWT с помощью перетаскивания, очень удобно.

Смотрите инструкцию установки по ссылке:

4- Некоторые понятия о SWT.

4.1- Display & Shell

Класс  Display и Shell являются ключевыми компонентами приложения  SWT.

- org.eclipse.swt.widgets.Shell описывает окно (Window)

- org.eclipse.swt.widgets.Display отвечает за управление цикла события (event loops), шрифт, цвет и контролирует информацию коммуникации между потоками интерфкйса пользователя (UI Thread) и другими потоками (other Thread). Display это основа для всех способностей свойств  SWT.

Каждое приложение  SWT требует минимум один  Display и один или более объектов  Shell
Пример:
Display display = new Display();
Shell shell = new Shell(display);
shell.open();

// run the event loop as long as the window is open
while (!shell.isDisposed()) {
   // read the next OS event queue and transfer it to a SWT event
 if (!display.readAndDispatch())
  {
 // if there are currently no other OS event to process
 // sleep until the next OS event is available
   display.sleep();
  }
}

// disposes all associated windows and their components
display.dispose();

4.2- SWT Widgets

SWT widget установлены в пакетах  org.eclipse.swt.widgets и org.eclipse.swt.custom. Этом  widget расширен из класса  Widget или  Control. Некоторые widget иллюстрированы в изображении ниже.
Детали вы можете просмотреть в  SWT widget homepage.

5- Создать RCP Plugin Project

В Eclipse выбрать :
  • File/New/Other...
  • Так как нет надобности создать приложение Workbench поэтому мы не выбираем (1) как в изображении ниже.
  • Выбрать Yes в области (2) чтобы Eclipse создал RCP Application (Работающий на Desktop), напротив он создаст RAP Application (Работающий на Web).
Project создан:
Добавить библиотеку swt:  org.eclipse.swt

Если вы программируете приложение RAP, соответствующей библиотекой является org.eclipse.rap.rwt

6- Первый пример

Это простой пример, не используя инструмент перетаскивания 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) {
      // Create Display
      Display display = new Display();
      // Create Shell (Window) from diplay
      Shell shell = new Shell(display);

      shell.open();

      while (!shell.isDisposed()) {
          if (!display.readAndDispatch())
              display.sleep();
      }
      display.dispose();
  }
}
Нажмите на правую кнопку мыши на HelloSWT.java и выберите  Run As/Java Application.
Результаты запуска примера:

7- Использование WindowBuilder

Далее, мы создадим пример с перетаскиванием на WindowBuilder.
  • File/New/Other ..
Это окно дизайна в WindowBuilder. Оно позволяет вам легко перетаскивать  Widget.
You can watch the video below:

8- SWT Widget

8.1- Обзор

Это иерарзия  Widget в SWT.
Вы можете посмотреть демо (demo) про  Control по ссылке ниже, это  RWT Control, но на самом деле они похожи на SWT control.

Demo:

8.2- Widget могут содержать другие Widget (Container)

8.3- Control

9- SWT Layout

9.1- Что такое Layout?

Проще говоря,  Layout это способ распорядка компонентов на интерфейсе.
Стандартные  Layout в SWT это:
  • FillLayout – Выравнивает размер Widget в строке или столбце
  • RowLayout – Связывает Widget в строке или заполненной строке (Fill), обернутой (Wrap), и с вариантами пробела (space).
  • GridLayout – Связывает Widget в сети 

9.2- Онлайн пример

Это пример онлайн, позволяющий вам увидеть действия Layout.
  • TODO Link?

9.3- FillLayout

FillLayout это простой класс  layout. н расставляет виджет (Widget) в одной строке или столбце, заставляя их иметь один размер. Сначала  Widget иметь высоту как самый высокий  Widget, и широту как самый широкий widget. FillLayout не wrap (обертывает) Widget, и вы не можете определить грань (margin) или расстояние (space).

FillLayout fillLayout = new FillLayout();
fillLayout.type = SWT.VERTICAL;
shell.setLayout(fillLayout);

Сначала После изменения размера
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");
     
      // Windows back to natural size.
      shell.pack();
      //
      shell.open();
      while (!shell.isDisposed()) {
          if (!display.readAndDispatch())
              display.sleep();
      }
      // tear down the SWT window
      display.dispose();
  }
}
Результаты запуска примера:

9.4- RowLayout

RowLayout используются больше чем  FillLayout так как он имеет способность wrap (обертывать), и потому что он поддерживает  margin и  spacing. RowLayout имеет некоторые поля (field) конфигурации. Помимо этого, высота и ширина каждого widget в каждом  RowLayout может быть определен созданием объекта RowData для  widget используя  setLayoutData

The field configuration:

Wrap, Pack, Justify:

Сначала После изменения размера
wrap = true
pack = true
justify = false
 
(По умолчанию)
wrap = false
 
(Немного отрезан если не хватает пространства)
pack = false
 
(Все Widget имеют одинаковый размер)
justify = true
 
(Widget будут распространены на готовом пространстве и поровну)

MarginLeft, MarginTop, MarginRight, MarginBottom, Spacing:

Поля (field) контролирующие пиксели (Pixel) между widget (space - расстояние) и количество пикселей между widget и стороной родительского  Composite (margin - грань). По умолчанию, RowLayout использует 3 Pixel для выравнивания (margin) и интервалом (space). Смотрите детали в следующем изображении.
Video:
 

9.5- GridLayout

GridLayout это самый полезный и сильный в стандартных  Layout, но так же являеся самым сложным. С GridLayout, дочерний  Widget одного  Composite будет расставлен в сетке. GridLayout имеет некоторые конфигурационные поля, и так же как  RowLayout, Widget расположенные в  GridLayout могут иметь связанный объект данных Layout, так же называется  GridData. Сила  GridLayout находится в способности конфигурации  GridData для каждого widget управляемый с помощью  GridLayout.

Конфигурации GridLayout:

  • NumColumns
  • MakeColumnsEqualWidth

Video GridLayout:

9.6- StackLayout

StackLayout расставляет все  Widget в стопку, они будут иметь одинаковый размер и местоположение. Поле  topControl определяет какой Widget находится в самом верху и видно. Пользователь должен настроить значение topControl и потом вызвать метод  layout() родительского  Composite.

Video StackLayout:

 

9.7- Комбинирование Layout

Выше мы ознакомились со стандартными Layout, и сочетаниями разных  Layout, и разными контейнерами (Container) ( Composite, TabFolder, SashForm,.. ) которые создают желаемый интерфейс.
  • TODO

10- Написать расширенные классы из widget в SWT

Иногда вам нжуно написать расширенный класс из класса widget готовый в  SWT. Это совершенно нормально, но есть маленькое примечание, вам нужно переопределить метод   checkSubclass() ничего не делая в том методе.
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);
   }

   // You have to override this method.
   @Override
   protected void checkSubclass() {
       // No need to do anything.
   }

   
}

11- Module становится компонентом интерфейса

В случае, когда вам нужно сделать дизайн сложного интерфейса. Разделение дизайна это необходимо и потом соединить их снова, с дизайном на WindowBuilder это становится легче.

Посмотрим следующий интерфейс, мы попытаемся разделить его.
Предположим, вы хотите сделать дизайн похожим на изображение ниже. (Он сложный и нужно разделить дизайн, но это иллюстрация примера разделения дизайна интерфейса)
Мы можем сделать дизайн 2-х отдельных  Composite и соединить их на  MainComposite.

TopComposite

  • File/New/Other...
Дизайн интерфейса для  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

Индентично создайте класс  BottomComposite:
Дизайн интерфейса для  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

Индентично создайте класс  MainComposite:

Регистрация TopComposite & BottomComposite на Palette

Нажмите на правую кнопку мыши на  Palette и выберите  Add category...
Назовите  Pallete Category:
  • My Composite
Нажмите на правую кнопку мыши на  "My Composite" чтобы добавить  TopComposite & BottomComposite.
Индентично добавьте  BottomComposite в каталог  My Composite.
Теперь  TopComposite & BottomComposite могут быть легко перетащены в другие  Composite.
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- Обработка события

Обработка события  SWT является очень простой с поддержкой  WindowBuilder.

Иллюстрированные примеры:

  • File/New/Other...
  • Package: org.o7planning.tutorial.swt.event1
  • Name: ButtonEventDemo
Кликнете правой кнопкой мыши на Button, выберите  "Add event handler", отобразится серия событий соответствующие с Button.
WindowBuilder автоматически создает для вас  code:
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- JFace

Вы можете посмотреть больше про  JFace, дополнительный  API для  SWT: