2014-08-24

Red Hat Enterprise Linux 7がやってきた[起動処理編] - 第3回 systemdの基本操作:ITpro

Systemd が Fedora に導入されたのは 2011 年 5 月 24 日にリリースされ Fedora 15 の時ですから、先日リリースされた RHL 7 / Cent OS 7 に systemd が採用されたからと言って、自分にとって真新しいものはないはずです。しかし、マジメに systemd を勉強してこなかったので、引用サイト 1. を読み、自分の環境 (Cent OS 7) での使用例をまとめました。例に取り上げているサービスは httpd です。

systemctl コマンドで現在有効な Unit を一覧する場合

# systemctl --type=service --no-pager
UNIT                         LOAD   ACTIVE SUB     DESCRIPTION
abrt-ccpp.service            loaded active exited  Install ABRT coredump hook
abrt-oops.service            loaded active running ABRT kernel log watcher
abrt-xorg.service            loaded active running ABRT Xorg log watcher
abrtd.service                loaded active running ABRT Automated Bug Reporting 
...
(省略)
...
udisks2.service              loaded active running Disk Manager
upower.service               loaded active running Daemon for power management

LOAD   = Reflects whether the unit definition was properly loaded.
ACTIVE = The high-level unit activation state, i.e. generalization of SUB.
SUB    = The low-level unit activation state, values depend on unit type.

73 loaded units listed. Pass --all to see loaded but inactive units, too.
To show all installed unit files use 'systemctl list-unit-files'.
# 

定義されているすべてのサービスを確認する場合

# systemctl list-unit-files --type=service --no-pager
UNIT FILE                                   STATE   
abrt-ccpp.service                           enabled
abrt-oops.service                           enabled
abrt-pstoreoops.service                     disabled
abrt-vmcore.service                         enabled 
...
(省略)
...
vmtoolsd.service                            enabled 
wacom-inputattach@.service                  static  
wpa_supplicant.service                      disabled

251 unit files listed.
# 

HTTP デーモンを起動する Unit である httpd.service について、自動起動を有効化/無効化する場合

# systemctl enable httpd.service
ln -s '/usr/lib/systemd/system/httpd.service' '/etc/systemd/system/multi-user.target.wants/httpd.service'
# systemctl disable httpd.service
rm '/etc/systemd/system/multi-user.target.wants/httpd.service'

システム起動後に手動で起動/停止/再起動する場合

# systemctl start httpd.service 
# systemctl stop httpd.service 
# systemctl restart httpd.service 

httpd.service」の稼働状態を表示する例

# systemctl status httpd.service
httpd.service - The Apache HTTP Server
   Loaded: loaded (/usr/lib/systemd/system/httpd.service; enabled)
   Active: active (running) since 月 2014-08-25 13:18:04 JST; 15s ago
  Process: 5654 ExecStop=/bin/kill -WINCH ${MAINPID} (code=exited, status=0/SUCCESS)
 Main PID: 5659 (httpd)
   Status: "Total requests: 0; Current requests/sec: 0; Current traffic:   0 B/sec"
   CGroup: /system.slice/httpd.service
           ├─5659 /usr/sbin/httpd -DFOREGROUND
           ├─5660 /usr/libexec/nss_pcache 1277958 off /etc/httpd/alias
           ├─5661 /usr/sbin/httpd -DFOREGROUND
           ├─5662 /usr/sbin/httpd -DFOREGROUND
           ├─5663 /usr/sbin/httpd -DFOREGROUND
           ├─5664 /usr/sbin/httpd -DFOREGROUND
           ├─5665 /usr/sbin/httpd -DFOREGROUND
           └─5666 /usr/sbin/httpd -DFOREGROUND

 8月 25 13:18:03 localhost.localdomain httpd[5659]: AH00558: httpd: Could no...
 8月 25 13:18:04 localhost.localdomain systemd[1]: Started The Apache HTTP S...
Hint: Some lines were ellipsized, use -l to show in full.
# 

journald によるログの表示

# journalctl -u httpd.service -a --no-pager
-- Logs begin at 月 2014-08-25 12:30:25 JST, end at 月 2014-08-25 13:20:01 JST. --
 8月 25 12:31:14 localhost.localdomain systemd[1]: Starting The Apache HTTP Server...
 8月 25 12:31:15 localhost.localdomain httpd[1212]: AH00558: httpd: Could not reliably determine the server's fully qualified domain name, using localhost.localdomain. Set the 'ServerName' directive globally to suppress this message
 8月 25 12:31:16 localhost.localdomain systemd[1]: Started The Apache HTTP Server.
 8月 25 13:15:48 localhost.localdomain systemd[1]: Started The Apache HTTP Server.
 8月 25 13:15:55 localhost.localdomain systemd[1]: Stopping The Apache HTTP Server...
...
(省略)
...

引用サイト

  1. Red Hat Enterprise Linux 7がやってきた[起動処理編] - 第3回 systemdの基本操作:ITpro

2014-08-17

SWT による GUI プログラミング (7) - NatTable のサンプル (2)

NatTable をネットで見つけて、これを使えるようにするには時間を書ける必要があると見積もり、夏休みが始まるのを首を長くして待っていました。

NatTable を利用するには、まず SWT/JFace を理解しておく必要があると考え、休みの前半にはそこそこ理解できたと思えるようになりました。そこで、そろそろ NatTable に取り組めるだろうと判断し、サンプルプログラムを眺めながら内容の理解を始めました。

いろいろと迷いがでてきたのはこの頃です。迷走を繰り返し、結局、Java プログラミングに対する自分の取り組み方があまり良くないことに気が付きました。それは、

  • 機能を理解するための早道は単純な一つのファイルで記述することだ、という幻想を捨てるべき。
    • AWT や SWT の機能を確認するだけなら良いが、あまりこだわると、却って Java らしくないプログラミングを追求することになりかねない。
  • Eclipse を開発環境に使うのであれば、Eclipse RCP を利用すべし。
    • 手っ取り早く、プラグインのリソースを利用したアプリケーションを効果的に構築するのであれば、これを利用しない手はない。

今日は夏休みの最終日。残念ながら Eclipse RCP の理解はいまいちで、時間切れです。もう一週間休むことができれば…

以前勤めていた外資系の会社では(激務であるためか) 3 年毎に 4 週間の sabbatical leave が認められましたが、今の会社にはそのような制度はありません。本業はプログラマでないので、継続的に少しずつ時間を見つけて習得していくしかありません。

そういう訳で夏休みの取り組みの成果としては心許無いのですが、NatTable のサンプルをひとつ紹介します。

NatTable のサンプル

NatTable については、理解しているというレベルにはまだ程遠いです。NatTable のサンプルプログラム NatTableExamples.jar から興味があるものを抜き出して、自分が理解しやすいように手を加えてコードの中身を調べているような段階です。ここでは、サンプル _304_DynamicColumnExample.java を(要するに)パクったものを紹介するにとどめます。余分な機能は整理して削除してあります。

今回紹介するサンプルは右の 4 つになります。

最初は、サンプルの機能概要を記述したインターフェイスです。

List: ISampleApp.java
/*******************************************************************************
 * Copyright (c) 2012 Original authors and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 * 
 * Contributors:
 *     Original authors and others - initial API and implementation
 ******************************************************************************/
// Original File is INatExample.java in NatTableExamples.jar
// modified by Suguri Fuhito, 15-Aug-2014
package sample;

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

public interface ISampleApp {

    public String getName();
 
    public Control createNatTableControl(Composite parent);
 
    public void onStart();
 
    public void onStop();
 
}

次は、インターフェイス INatTableApp.java を実装した抽象クラスです。

List: AbstractSampleApp.java
/*******************************************************************************
 * Copyright (c) 2012 Original authors and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 * 
 * Contributors:
 *     Original authors and others - initial API and implementation
 ******************************************************************************/
// Original File is AbstractNatExample.java in NatTableExamples.jar
// modified by Suguri Fuhito, 15-Aug-2014
package sample;

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

public abstract class AbstractSampleApp implements ISampleApp {

    public String getName() {
        return getClass().getSimpleName().replaceAll("^_[0-9]*_", "")
                .replace('_', ' ');
    }

    public void onStart() {
    }

    public void onStop() {
    }

    public Control createNatTableControl(Composite parent) {
        return null;
    }
}

抽象クラス AbstractNatTable を継承/オーバーライドして、NatTable のインスタンスを生成するクラスです。

List: NatTableApp.java
// Original File is _304_DynamicColumnExample.java in NatTableExamples.jar
// modified by Suguri Fuhito, 15-Aug-2014

package sample;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.nebula.widgets.nattable.NatTable;
import org.eclipse.nebula.widgets.nattable.config.ConfigRegistry;
import org.eclipse.nebula.widgets.nattable.config.DefaultNatTableStyleConfiguration;
import org.eclipse.nebula.widgets.nattable.data.IColumnPropertyAccessor;
import org.eclipse.nebula.widgets.nattable.data.IDataProvider;
import org.eclipse.nebula.widgets.nattable.data.ListDataProvider;
import org.eclipse.nebula.widgets.nattable.grid.data.DefaultCornerDataProvider;
import org.eclipse.nebula.widgets.nattable.grid.data.DefaultRowHeaderDataProvider;
import org.eclipse.nebula.widgets.nattable.grid.layer.ColumnHeaderLayer;
import org.eclipse.nebula.widgets.nattable.grid.layer.CornerLayer;
import org.eclipse.nebula.widgets.nattable.grid.layer.DefaultRowHeaderDataLayer;
import org.eclipse.nebula.widgets.nattable.grid.layer.GridLayer;
import org.eclipse.nebula.widgets.nattable.grid.layer.RowHeaderLayer;
import org.eclipse.nebula.widgets.nattable.layer.DataLayer;
import org.eclipse.nebula.widgets.nattable.layer.ILayer;
import org.eclipse.nebula.widgets.nattable.layer.event.ColumnInsertEvent;
import org.eclipse.nebula.widgets.nattable.layer.stack.DefaultBodyLayerStack;
import org.eclipse.nebula.widgets.nattable.sort.config.SingleClickSortConfiguration;
import org.eclipse.nebula.widgets.nattable.ui.menu.HeaderMenuConfiguration;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;

public class NatTableApp extends AbstractSampleApp {

    private List<String> columns = new ArrayList<String>();
    private List<Map<String, String>> values = new ArrayList<Map<String, String>>();

    public static void main(String[] args) throws Exception {
        StandaloneRunner.run(600, 300, new NatTableApp());
    }

    @Override
    public Control createNatTableControl(Composite parent) {
        // start with 3 columns
        columns.add("Column_0");
        columns.add("Column_1");
        columns.add("Column_2");

        values.add(createValueRow("Homer"));
        values.add(createValueRow("Marge"));
        values.add(createValueRow("Bart"));
        values.add(createValueRow("Lisa"));
        values.add(createValueRow("Maggie"));

        Composite panel = new Composite(parent, SWT.NONE);
        panel.setLayout(new GridLayout());
        GridDataFactory.fillDefaults().grab(true, true).applyTo(panel);

        Composite gridPanel = new Composite(panel, SWT.NONE);
        gridPanel.setLayout(new GridLayout());
        GridDataFactory.fillDefaults().grab(true, true).applyTo(gridPanel);

        Composite buttonPanel = new Composite(panel, SWT.NONE);
        buttonPanel.setLayout(new GridLayout());
        GridDataFactory.fillDefaults().grab(true, true).applyTo(buttonPanel);

        ConfigRegistry configRegistry = new ConfigRegistry();

        // create the body layer stack
        IDataProvider bodyDataProvider = new ListDataProvider<Map<String, String>>(
                values, new MyColumnPropertyAccessor());
        final DataLayer bodyDataLayer = new DataLayer(bodyDataProvider);
        DefaultBodyLayerStack bodyLayerStack = new DefaultBodyLayerStack(
                bodyDataLayer);

        // create the column header layer stack
        IDataProvider columnHeaderDataProvider = new SimpleColumnHeaderDataProvider();
        ILayer columnHeaderLayer = new ColumnHeaderLayer(new DataLayer(
                columnHeaderDataProvider), bodyLayerStack.getViewportLayer(),
                bodyLayerStack.getSelectionLayer());

        // create the row header layer stack
        IDataProvider rowHeaderDataProvider = new DefaultRowHeaderDataProvider(
                bodyDataProvider);
        ILayer rowHeaderLayer = new RowHeaderLayer(
                new DefaultRowHeaderDataLayer(new DefaultRowHeaderDataProvider(
                        bodyDataProvider)), bodyLayerStack.getViewportLayer(),
                bodyLayerStack.getSelectionLayer());

        // create the corner layer stack
        ILayer cornerLayer = new CornerLayer(new DataLayer(
                new DefaultCornerDataProvider(columnHeaderDataProvider,
                        rowHeaderDataProvider)), rowHeaderLayer,
                columnHeaderLayer);

        // create the grid layer composed with the prior created layer stacks
        GridLayer gridLayer = new GridLayer(bodyLayerStack, columnHeaderLayer,
                rowHeaderLayer, cornerLayer);

        final NatTable natTable = new NatTable(gridPanel, gridLayer, false);
        natTable.setConfigRegistry(configRegistry);
        natTable.addConfiguration(new DefaultNatTableStyleConfiguration());
        natTable.addConfiguration(new HeaderMenuConfiguration(natTable));
        natTable.addConfiguration(new SingleClickSortConfiguration());
        natTable.configure();
        GridDataFactory.fillDefaults().grab(true, true).applyTo(natTable);

        Button addColumnButton = new Button(buttonPanel, SWT.PUSH);
        addColumnButton.setText("列の追加");
        addColumnButton.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                String newColumn = "Column_" + columns.size();
                columns.add(newColumn);

                for (Map<String, String> value : values) {
                    String prefix = value.get("Column_0");
                    prefix = prefix.substring(0, prefix.indexOf("_"));
                    value.put(newColumn, prefix + "_" + (columns.size() - 1));
                }

                bodyDataLayer.fireLayerEvent(new ColumnInsertEvent(
                        bodyDataLayer, columns.size() - 1));
            }
        });

        return panel;
    }

    private Map<String, String> createValueRow(String value) {
        Map<String, String> valueRow = new HashMap<String, String>();

        for (int i = 0; i < columns.size(); i++) {
            String column = columns.get(i);
            valueRow.put(column, value + "_" + i);
        }

        return valueRow;
    }

    class MyColumnPropertyAccessor implements
            IColumnPropertyAccessor<Map<String, String>> {

        @Override
        public Object getDataValue(Map<String, String> rowObject,
                int columnIndex) {
            return rowObject.get(getColumnProperty(columnIndex));
        }

        @Override
        public void setDataValue(Map<String, String> rowObject,
                int columnIndex, Object newValue) {
            rowObject.put(getColumnProperty(columnIndex), newValue.toString());
        }

        @Override
        public int getColumnCount() {
            return columns.size();
        }

        @Override
        public String getColumnProperty(int columnIndex) {
            return columns.get(columnIndex);
        }

        @Override
        public int getColumnIndex(String propertyName) {
            return columns.indexOf(propertyName);
        }
    }

    class SimpleColumnHeaderDataProvider implements IDataProvider {

        @Override
        public Object getDataValue(int columnIndex, int rowIndex) {
            return "Column " + (columnIndex + 1); //$NON-NLS-1$
        }

        @Override
        public void setDataValue(int columnIndex, int rowIndex, Object newValue) {
            throw new UnsupportedOperationException();
        }

        @Override
        public int getColumnCount() {
            return columns.size();
        }

        @Override
        public int getRowCount() {
            return 1;
        }
    }
}

最後はインスタンスを実行する部分です。

List: StandaloneRunner.java
/*******************************************************************************
 * Copyright (c) 2012 Original authors and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 * 
 * Contributors:
 *     Original authors and others - initial API and implementation
 ******************************************************************************/
// Original File is StandaloneNatExampleRunner.java in NatTableExamples.jar
// modified by Suguri Fuhito, 15-Aug-2014
package sample;

import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;

public class StandaloneRunner {

    public static void run(ISampleApp obj) {
        run(800, 800, obj);
    }

    public static void run(int width, int height, ISampleApp obj) {
        // Setup
        Display display = Display.getDefault();
        Shell shell = new Shell(display, SWT.SHELL_TRIM);
        shell.setLayout(new FillLayout());
        shell.setSize(width, height);
        shell.setText(obj.getName());

        // Create example control
        Control instanceControl = obj.createNatTableControl(shell);

        // Start
        obj.onStart();

        shell.open();

        while (!shell.isDisposed()) {
            if (!display.readAndDispatch()) {
                display.sleep();
            }
        }

        // Stop
        obj.onStop();

        instanceControl.dispose();

        shell.dispose();
        display.dispose();
    }
}

実行例

引用サイト

  1. Overview (parent 1.1.0-SNAPSHOT API)
  2. NatTable - Download
  3. Lang - Download Apache Commons Lang
    • NatTable のライブラリが一部の処理で commons-lang を使用しています。最新のではなく、common-lang 2.x で動作しました。

 

2014-08-14

SWT による GUI プログラミング (6) - NatTable のサンプル

JFace に数日間取り組んで、なんとなく使い方が判ってきたので、興味の対象であった Nebula Project の NatTable を使ってみようとしましたが、雲をつかむようで使い始める切り口をつかめません。そこで まずは、NatTable のサイトで公開されているサンプルを実行してみる事にしました。

用意するもの

NatTable のサンプルを実行するには、Java の実行環境に加えて、下記 1. のサンプルファイルと、2. のサンプルを実行するプラットフォームに対応した SWT ライブラリが必要になります。自分のプラットフォームでは、swt-4.4-gtk-linux-x86_64.zip をダウンロードしました。

  1. NatTableExamples-1.1.0.jar
  2. プラットフォームに対応した SWT ライブラリ

サンプルの実行(CentOS 7 の場合)

ダウンロードした SWT ライブラリについては、適当な場所に ZIP ファイルを展開して、SWT.jar を NatTable のサンプル NatTableExamples-1.1.0.jar と同じディレクトリへコピーしておきます。端末エミュレータ上で前述の 2 つのファイルが保存されているディレクトリに移動し、以下のようにタイプしてサンプルを起動します。

$ java -cp swt.jar:NatTableExamples.jar org.eclipse.nebula.widgets.nattable.examples.NatTableExamples

注)パスの区切りは Linux の bash では : (コロン)ですが Windows などでは ; (セミコロン)かもしれません。未確認です、すみません。

実行例

NatTable に目を付けた時の印象(期待する機能)とはズレていないことを確認できましたが、サンプルをいくら眺めても使えるようにはならないので、サンプルのコードを読み始めています。

NatTable の使い途

NatTable を何に使いたいかというと、それは、遅々として開発が進まない Ammonite に実装して、大量のデータを扱えるようにしたいと考えているのです。Ammonite は、エンジニアリングで必要な統計解析をなんでも簡単にできるツールにしたいのですが、昨年、ちょっとブログで紹介しただけでちっとも進んでいないプロジェクトです。

誰に期待されているわけでもないのですが、こうやってひとたびブログに話題を出してしまって、あとで何も進捗していないことを振り返り自己嫌悪に陥ることしばしばです。それでも懲りずに中途半端を繰り返してしまうのですが…。

JavaFX の TableView を使いこなせない者が NatTable を使いこなせるのか?という謗りを受けそうですが、たぶんその通りでしょう。しかし、基本機能として重要となるスプレッドシート状の GUI 部品である、大変魅力的な NatTable を前にして、入れ物だけはさっさと JavaFX から SWT/JFace へ移し替えてしまっています。そこそこ使いこなせるようになるまで、いや、納得できるまでじっくり取り組むことにします。

NatTable とは何か?

本来であれば、最初に NatTable とは何かを説明すべきなのですが、十分に使いこなせない段階で説明をしようとすると、不備が出てしまいそうでなかなかできません。YouTube で NatTable の解説を見つけたので掲載します[資料]。

2014-08-13

【備忘録】Google Noto Fonts

気分転換に近所の本屋へ出かけ、日経 Linux 9 月号をパラパラと眺めていたら、Adobe と Google が共同開発したオープンソースのフォントファミリーが公開されたという記事に目がとまりした。遅ればせながら、ダウンロードして CentOS 7 上で試してみました。

  1. Google Noto Fonts
  2. Google Developers Blog: Noto: A CJK Font That is Complete, Beautiful and Right for Your Language and Region
  3. The Typekit Blog | Source Han Sansの紹介:オープンソースのPan-CJK書体
  4. Typekit
  5. Noto - Wikipedia
  6. Source Han Sans - Wikipedia
  7. Source Han Sans(源ノ角ゴシック)/Noto Sansがリリースされました - DTP Transit(Creative Cloud, Illustrator, InDesign, Web Fonts, アドビ, フォント, イラレ)

Noto Sans フォントは、Apache License 2.0 の下で無償配布されています。上記 1. のサイトからフォントをダウンロードして解凍し、Nautilus 上で日本語のフォントをダブルクリックするとフォントビューアー(あとで、念の為にと確認してみると、起動していたのは、なぜか mate-font-viewer でした。評価用に MATE も使えるようにしてはあるのですが…)が起動します。このままインストールボタンを押すと、ユーザーアカウントのエリアにフォントがインストールされ、アプリケーションで利用できるようになります。

下記は、LibreOffice Writer で試した例です。

少し使いこまないと、英字フォントと混ぜて使って画面上で見やすいフォントなのかを評価できませんが、少なくとも、7 つものウエイトが用意されている高品位なフォントであることは確かなようです。詳細は、上記 7. のサイトをご覧ください。生半可な私のコメントより有益な情報が掲載されています。

2014-08-12

SWT による GUI プログラミング (5) - JFace

JFace は、Wikipadia の説明を借りれば、SWT の上層に位置し、一般的な UI プログラミングタスクを制御するクラスを提供するライブラリです。SWT に Model View Controller の視点を持ち込んだものと言える、とのことですが、これはおいおいサンプルで理解していくことにします。

SWT の基本的なウィジェットのサンプルについては、しばらく更新をサボっているホームページにもっと網羅的にまとめる予定です。ブログではそろそろ JFace の話題に移りたいと思います。

JFace を利用する際のプロジェクトのビルドパス設定 (Eclipse)

JFace を利用するには、SWT のライブラリの他に、Eclipse 本体にあるライブラリを利用します。

SWT ライブラリを利用したアプリケーション開発では、SWT ライブラリをプロジェクトとしてワークスペースにインポートしておき、それを右のようにプロジェクトのビルドパスに加えて利用していました。

JFace を利用する際は、この設定に加えて下記のライブラリをビルドパスに加ます。<version info> の部分は、それぞれのライブラリのバージョン番号です。

  • org.eclipse.core.commands_<version info>.jar
  • org.eclipse.equinox.common_<version info>.jar
  • org.eclipse.jface_<version info>.jar
  • org.eclipse.osgi_<version info>.jar
  • org.eclipse.ui.workbench_<version info>.jar

プロジェクトのビルドパスの設定画面で、(バージョン番号には違いがあるかもしれませんが)ライブラリが以下のようになっていれば OK です。

これは、ライブラリの設定タブの画面で 「Add Variable ...」ボタンをクリックして、環境変数一覧が表示されたウィンドウを開き、 ECLIPSE_HOME を選択して「Extend...」ボタンをクリックして、この環境変数が差すディレクトリの中から、ライブラリ(jar ファイル)を選択します。

上述のライブラリは、全て plugins ディレクトリ内に格納されています。

JFace 版 Hello World!

bitWalk's: SWT による GUI プログラミング (2) で紹介した SWT の Label クラスのサンプル SWTAppLabel.java を JFace 用に書き直しました。

List: JFaceApp.java
package applicationwindow;

import org.eclipse.jface.window.ApplicationWindow;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;

public class JFaceApp extends ApplicationWindow {
    public JFaceApp() {
        super(null);
    }

    @Override
    protected Control createContents(Composite parent) {
        Composite container = new Composite(parent, SWT.NONE);
        container.setBackground(new Color(null, 220, 220, 255));
        GridLayout gl = new GridLayout(1, true);
        gl.marginBottom = 2;
        gl.marginTop = 2;
        gl.marginLeft = 10;
        gl.marginRight = 10;
        container.setLayout(gl);
        
        Label lab = new Label(container, SWT.CENTER);
        lab.setText("こんにちは、世界!");
        lab.setBackground(new Color(null, 200, 200, 255));
        return container;
    }

    public static void main(String[] args) {
        ApplicationWindow w = new JFaceApp();
        w.setBlockOnOpen(true);
        w.open();
        Display.getCurrent().dispose();
    }
}

JFace の流儀に従って、ApplicationWindow クラスを継承したフレームワークによってウィンドウを作成していますが、SWT のウィジェットやレイアウト・マネージャをそのまま使っています。

実行例

ブログで紹介する単純なサンプルを用意する前に、具体的に機能するサンプルをいくつか作ってみて、試行錯誤をしながら理解を深めていきたいので、本トピックの続きを掲載するのには、少し間が空いてしまうかもしれません。

参考サイト

  1. JFace - Eclipsepedia

2014-08-11

SWT による GUI プログラミング (4)

SWT, Standard Widget Toolkit を利用した基本的なサンプルを紹介します。今回は List と Table クラスのウィジェットです。

List

List クラスのインスタンスはリスト(一次元のデータ列)を表示するウィジェットです。

List: SWTAppList.java
package list;

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

public class SWTAppList {
    Shell shell;
    String[] prefData = { "北海道", "青森県", "岩手県", "宮城県", "秋田県", "山形県", "福島県",
            "茨城県", "栃木県", "群馬県", "埼玉県", "千葉県", "東京都", "神奈川県", "山梨県", "長野県",
            "新潟県", "富山県", "石川県", "福井県", "岐阜県", "静岡県", "愛知県", "三重県", "滋賀県",
            "京都府", "大阪府", "兵庫県", "奈良県", "和歌山県", "鳥取県", "島根県", "岡山県", "広島県",
            "山口県", "徳島県", "香川県", "愛媛県", "高知県", "福岡県", "佐賀県", "長崎県", "熊本県",
            "大分県", "宮崎県", "鹿児島県", "沖縄県" };

    public SWTAppList(Display display) {
        shell = new Shell(display);
        shell.setText("List");

        initUI();

        // shell.pack();
        shell.setSize(100, 200);
        shell.setLocation(100, 100);
        shell.open();

        while (!shell.isDisposed()) {
            if (!display.readAndDispatch()) {
                display.sleep();
            }
        }

    }

    public void initUI() {
        shell.setLayout(new FillLayout());

        List list = new List(shell, SWT.SINGLE | SWT.BORDER | SWT.V_SCROLL);
        for (String prefName : prefData) {
            list.add(prefName);
        }
        list.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                List l = (List) e.widget;
                int idx = l.getSelectionIndex();
                if (idx > -1) {
                    System.out.println(l.getItem(idx) + "がクリックされました。");
                }
            }
        });

        list.pack();
    }

    public static void main(String[] args) {
        Display display = new Display();
        new SWTAppList(display);
        display.dispose();
    }
}

実行例

List: コンソール上の出力
北海道がクリックされました。
東京都がクリックされました。

Table

Table および関連クラスのインスタンスは、二次元のテーブルにテキストやイメージのリストを(行単位で)表示するウィジェットです。

List: SWTAppTable.java
package table;

import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.TableItem;

public class SWTAppTable {
    Shell shell;
    String[] colNames = { "チーム", "試合数", "勝数", "負数", "引分数", "勝率" };
    String[][] rowData = { { "ソフトバンク", "24", "18", "4", "2", "0.818" },
            { "オリックス", "24", "15", "7", "2", "0.682" },
            { "日本ハム", "24", "16", "8", "0", "0.667" },
            { "中日", "24", "14", "10", "0", "0.583" },
            { "西武", "24", "12", "11", "1", "0.522" },
            { "ヤクルト", "24", "10", "12", "2", "0.455" },
            { "巨人", "24", "10", "13", "1", "0.435" },
            { "阪神", "24", "10", "14", "0", "0.417" },
            { "楽天", "24", "9", "13", "2", "0.409" },
            { "ロッテ", "24", "8", "14", "2", "0.364" },
            { "横浜", "24", "7", "13", "4", "0.35" },
            { "広島", "24", "6", "16", "2", "0.273" } };

    public SWTAppTable(Display display) {
        shell = new Shell(display);
        shell.setText("Table");

        initUI();

        shell.pack();
        shell.setLocation(100, 100);
        shell.open();

        while (!shell.isDisposed()) {
            if (!display.readAndDispatch()) {
                display.sleep();
            }
        }
    }

    public void initUI() {
        shell.setLayout(new FillLayout());

        Table table = new Table(shell, SWT.MULTI | SWT.FULL_SELECTION
                | SWT.BORDER);

        table.setHeaderVisible(true);
        table.setLinesVisible(true);

        for (int i = 0; i < colNames.length; i++) {
            int colAlign, colWidth;

            if (i == 0) {
                colAlign = SWT.LEFT;
                colWidth = 150;
            } else {
                colAlign = SWT.RIGHT;
                colWidth = 100;
            }
            TableColumn col = new TableColumn(table, colAlign);
            col.setText(colNames[i]);
            col.setWidth(colWidth);
        }

        for (String[] data : rowData) {
            TableItem item = new TableItem(table, SWT.NULL);
            item.setText(data);
        }

        table.pack();
    }

    public static void main(String[] args) {
        Display display = new Display();
        new SWTAppTable(display);
        display.dispose();
    }

}

実行例

参考サイト

  1. Help - Eclipse Platform
  2. Java SWT tutorial
  3. FrontPage - SWTサンプル集

2014-08-10

SWT による GUI プログラミング (3)

SWT, Standard Widget Toolkit を利用した基本的なサンプルを紹介します。今回は Menu, Toolbar クラスのウィジェットです。

Menu

Menu および関連クラスのインスタンスはプルダウンメニューのメニューバーです。

List: SWTAppMenu.java
package menu;

import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.FileDialog;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.MenuItem;
import org.eclipse.swt.widgets.Shell;

public class SWTAppMenu {
    Shell shell;

    public SWTAppMenu(Display display) {
        shell = new Shell(display);
        shell.setText("Menu");

        initUI();

        shell.setSize(300, 200);
        shell.setLocation(100, 100);
        shell.open();

        while (!shell.isDisposed()) {
            if (!display.readAndDispatch()) {
                display.sleep();
            }
        }
    }

    public void initUI() {
        Menu menuBar = new Menu(shell, SWT.BAR);
        shell.setMenuBar(menuBar);

        MenuItem cascadeFileMenu = new MenuItem(menuBar, SWT.CASCADE);
        cascadeFileMenu.setText("ファイル");
        Menu fileMenu = new Menu(shell, SWT.DROP_DOWN);
        cascadeFileMenu.setMenu(fileMenu);

        MenuItem newItem = new MenuItem(fileMenu, SWT.PUSH);
        newItem.setText("新規ファイル");

        MenuItem openItem = new MenuItem(fileMenu, SWT.PUSH);
        openItem.setText("ファイルを開く");
        openItem.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                String[] filters = new String[] { "Java sources",
                        "All Files (*)" };
                String[] extensions = new String[] { "*.java", "*" };

                FileDialog dialog = new FileDialog(shell, SWT.OPEN);
                dialog.setFilterNames(filters);
                dialog.setFilterExtensions(extensions);

                String path = dialog.open();
                if (path != null) {
                    System.out.println(path + "が選択されました。");
                }
            }
        });

        new MenuItem(fileMenu, SWT.SEPARATOR);
        
        MenuItem exitItem = new MenuItem(fileMenu, SWT.PUSH);
        exitItem.setText("アプリケーションの終了");
        exitItem.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                shell.getDisplay().dispose();
                System.exit(0);
            }
        });
    }

    public static void main(String[] args) {
        Display display = new Display();
        new SWTAppMenu(display);
        display.dispose();
    }
}

実行例

Toolbar

Toolbar および関連クラスのインスタンスはプルダウンメニューの代わりにアイコンイメージなどのボタンでコマンドメニュー群を構成する場合に利用します。

List: SWTAppToolbar.java
package toolbar;

import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Device;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.FileDialog;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.ToolBar;
import org.eclipse.swt.widgets.ToolItem;

public class SWTAppToolbar {
    private Shell shell;
    private Image newImage;
    private Image openImage;
    private Image exitImage;

    public SWTAppToolbar(Display display) {
        shell = new Shell(display);
        shell.setText("Toolbar");

        initUI();

        shell.setSize(300, 200);
        shell.setLocation(100, 100);
        shell.open();

        while (!shell.isDisposed()) {
            if (!display.readAndDispatch()) {
                display.sleep();
            }
        }
    }

    public void initUI() {
        Device dev = shell.getDisplay();
        try {
            newImage = new Image(dev, "image/document-new.png");
            openImage = new Image(dev, "image/document-open.png");
            exitImage = new Image(dev, "image/application-exit.png");
        } catch (Exception e) {
            System.out.println("Cannot load images");
            System.out.println(e.getMessage());
            System.exit(1);
        }

        ToolBar toolBar = new ToolBar(shell, SWT.BORDER);

        ToolItem newItem = new ToolItem(toolBar, SWT.PUSH);
        newItem.setImage(newImage);
        newItem.setToolTipText("新規ファイル");

        ToolItem openItem = new ToolItem(toolBar, SWT.PUSH);
        openItem.setImage(openImage);
        openItem.setToolTipText("ファイルを開く");
        openItem.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                String[] filters = new String[] { "Java sources",
                        "All Files (*)" };
                String[] extensions = new String[] { "*.java", "*" };

                FileDialog dialog = new FileDialog(shell, SWT.OPEN);
                dialog.setFilterNames(filters);
                dialog.setFilterExtensions(extensions);

                String path = dialog.open();
                if (path != null) {
                    System.out.println(path + "が選択されました。");
                }
            }
        });

        new ToolItem(toolBar, SWT.SEPARATOR);

        ToolItem exitItem = new ToolItem(toolBar, SWT.PUSH);
        exitItem.setImage(exitImage);
        exitItem.setToolTipText("アプリケーションの終了");
        exitItem.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                shell.getDisplay().dispose();
                System.exit(0);
            }
        });
        toolBar.pack();
    }

    public static void main(String[] args) {
        Display display = new Display();
        new SWTAppToolbar(display);
        display.dispose();
    }
}

このサンプルでは、終了時にイメージの廃棄処理をしていませんので、メモリに残ってしまいます。ImageRegistry を利用した例を、JFace のサンプルで説明する予定です。

実行例

参考サイト

  1. Help - Eclipse Platform
  2. Java SWT tutorial
  3. FrontPage - SWTサンプル集

2014-08-09

SWT による GUI プログラミング (2)

SWT, Standard Widget Toolkit を利用した基本的なサンプルを紹介します。今回は Shell, Label, Button クラスのウィジェットです。

Shell

Shell クラスのインスタンスはトップレベルのウィンドウです。

List: SWTAppShell.java
package shell;

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

public class SWTAppShell {
    Shell shell;

    public SWTAppShell(Display display) {
        shell = new Shell(display);
        shell.setText("Shell");

        shell.setSize(200, 200);
        shell.setLocation(100, 100);
        shell.open();

        while (!shell.isDisposed()) {
            if (!display.readAndDispatch()) {
                display.sleep();
            }
        }
    }

    public static void main(String[] args) {
        Display display = new Display();
        new SWTAppShell(display);
        display.dispose();
    }
}

実行例

SWT のアプリケーションでは、まず Display クラスのインスタンスを作成します。 このインスタンスは、SWT のアプリケーションと、下部で動作している OS とを結び付けています。

Shell クラスのインスタンスは、トップレベルのウィンドウを生成しています。

SWT のアプリケーションでは下記のようにイベントループの処理を明確に記述する必要があります。

while (!shell.isDisposed()) {
    if (!display.readAndDispatch()) {
        display.sleep();
    }
}

Shell クラスのインスタンス shell が、ウィンドウが閉じられるなどの処理がされるまでループが継続されます。このループは、アプリケーションが終了された時にリソースを開放する display.dispose(); の処理の前に定型句のように記述しておく必要があります。

Label

Label クラスのインスタンスは 文字や画像、あるいはセパレータを表示します。

List: SWTAppLabel.java
package label;

import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;

public class SWTAppLabel {
    Shell shell;

    public SWTAppLabel(Display display) {
        shell = new Shell(display);
        shell.setText("Label");

        initUI();

        shell.setLocation(100, 100);
        shell.open();

        while (!shell.isDisposed()) {
            if (!display.readAndDispatch()) {
                display.sleep();
            }
        }
    }

    public void initUI() {
        shell.setBackground(new Color(null, 220, 220, 255));

        GridLayout gl = new GridLayout(1, true);
        gl.marginBottom = 2;
        gl.marginTop = 2;
        gl.marginLeft = 10;
        gl.marginRight = 10;
        shell.setLayout(gl);

        Label lab = new Label(shell, SWT.CENTER);
        lab.setText("こんにちは、世界!");
        lab.setBackground(new Color(null, 200, 200, 255));

        shell.pack();
    }

    public static void main(String[] args) {
        Display display = new Display();
        new SWTAppLabel(display);
        display.dispose();
    }
}

実行例

Button

Button クラスのインスタンスは プッシュボタン、チェックボタン、ラジオボタンなど複数の種類のボタンとして利用できます。

List: SWTAppButton.java
package button;

import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.layout.RowLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Shell;

public class SWTAppButton {

    Shell shell;

    public SWTAppButton(Display display) {
        shell = new Shell(display);
        shell.setText("Button");

        initUI();

        shell.pack();
        shell.setLocation(100, 100);
        shell.open();

        while (!shell.isDisposed()) {
            if (!display.readAndDispatch()) {
                display.sleep();
            }
        }
    }

    public void initUI() {
        shell.setBackground(new Color(null, 220, 255, 255));

        GridLayout gl = new GridLayout(1, true);
        gl.marginBottom = 2;
        gl.marginTop = 2;
        gl.marginLeft = 4;
        gl.marginRight = 4;
        shell.setLayout(gl);

        GridData gd = new GridData(SWT.FILL, SWT.FILL, false, false);

        // プッシュボタン
        Button but = new Button(shell, SWT.PUSH);
        but.setText("プッシュボタン");
        but.setLayoutData(gd);
        but.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                Button b = (Button) e.widget;
                System.out.println(b.getText() + "がクリックされました。");
            }
        });

        // 矢印ボタン
        Button arw = new Button(shell, SWT.ARROW | SWT.DOWN);
        arw.setLayoutData(gd);
        arw.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                System.out.println("矢印ボタンがクリックされました。");
            }
        });

        // チェックボタン
        Button chk = new Button(shell, SWT.CHECK);
        chk.setText("チェックボタン");
        chk.setLayoutData(gd);
        chk.addSelectionListener(toggleSelectionAdapter);

        // ラジオボタン
        Group rbgroup = new Group(shell, SWT.SHADOW_IN);
        rbgroup.setText("グループ");
        rbgroup.setLayout(new RowLayout(SWT.VERTICAL));

        Button rb1 = new Button(rbgroup, SWT.RADIO);
        rb1.setText("ラジオボタンA");
        rb1.addSelectionListener(radioSelectionAdapter);

        Button rb2 = new Button(rbgroup, SWT.RADIO);
        rb2.setText("ラジオボタンB");
        rb2.addSelectionListener(radioSelectionAdapter);

        Button rb3 = new Button(rbgroup, SWT.RADIO);
        rb3.setText("ラジオボタンC");
        rb3.addSelectionListener(radioSelectionAdapter);

        rb1.setSelection(true);

        // トグルボタン
        Button tgl = new Button(shell, SWT.TOGGLE);
        tgl.setText("トグルボタン");
        tgl.setLayoutData(gd);
        tgl.addSelectionListener(toggleSelectionAdapter);
    }

    private SelectionAdapter radioSelectionAdapter = new SelectionAdapter() {
        @Override
        public void widgetSelected(SelectionEvent e) {
            Button b = (Button) e.widget;

            if (b.getSelection()) {
                System.out.println(b.getText() + "が選択されました。");
            }
        }
    };

    private SelectionAdapter toggleSelectionAdapter = new SelectionAdapter() {
        @Override
        public void widgetSelected(SelectionEvent e) {
            Button b = (Button) e.widget;

            if (b.getSelection()) {
                System.out.println(b.getText() + "がオンになりました。");
            } else {
                System.out.println(b.getText() + "オフになりました。");
            }
        }
    };

    public static void main(String[] args) {
        Display display = new Display();
        new SWTAppButton(display);
        display.dispose();
    }
}

実行例

List: コンソール上の出力
プッシュボタンがクリックされました。
矢印ボタンがクリックされました。
チェックボタンがオンになりました。
ラジオボタンBが選択されました。
トグルボタンがオンになりました。

参考サイト

  1. Help - Eclipse Platform
  2. Java SWT tutorial
  3. FrontPage - SWTサンプル集

2014-08-08

SWT による GUI プログラミング

SWT, Standard Widget Toolkit は、Java プラットフォーム用ウィジェット・ツールキットの一種で、各種プラットフォームのネイティブウィジェットにアクセスできる共通な API を提供することを目的とするライブラリです。すなわち SWT はプラットフォーム固有の GUI ライブラリへのラッパーであるため、複数のプラットフォーム対応としてアプリケーションを配布する際には、プラットフォーム毎の SWT(JARファイル)が必要になります。また、SWT は AWT と同様に、手動でオブジェクトの解放をする必要があります。

Java の GUI ライブラリについては、JavaFX 2 に注目しているものの、SWT のウィジェットである Nebula / NatTable をどうしても使ってみたくなり、まずは SWT ライブラリをある程度使えるようにするべく情報収集を始めました。

Eclipse のインストール

CentOS 7 を使うようになって、開発環境はとりあえず NetBeans IDE を使おうとインストールしたばかりなのですが、SWT を習得するには、SWT を利用している Eclipse を使った方が、なにかと情報も得やすいだろうと考え、結局 Eclipse もダウンロードして使えるようにしました (Eclipse 4.4)。と言っても、ダウンロードした圧縮ファイルを展開しただけです。

$ tar xvf ダウンロード/eclipse-standard-luna-R-linux-gtk-x86_64.tar.gz
eclipse/
eclipse/.eclipseproduct
eclipse/about_files/
eclipse/about_files/mpl-v20.txt
eclipse/about_files/IJG_README
(以下、省略)
...

したがって、Eclipse のメッセージは日本語では表示されません。

自分が使っている CentOS 7 は「開発およびクリエイティブワークステーション」としてインストールされていますので、java-1.7.0-openjdk および関連パッケージがインストールされているため、何か追加でインストールすることなく Eclipse を起動できました。

ちなみに、私は、表示アイコンとして eclipse.png と、以下のようなファイルを $HOME/デスクトップ 内に用意して、デスクトップにアイコンを表示しています。

List: Eclipse.desktop
#!/usr/bin/env xdg-open
[Desktop Entry]
Version=1.0
Type=Application
Name=Eclipse
Comment=
Exec=/home/bitwalk/eclipse/eclipse
Icon=/home/bitwalk/share/icons/eclipse.png
Path=
Terminal=false
StartupNotify=false

自分が使っている CentOS 7 では、デスクトップ環境に「GNOME クラシック」を使っています。PC が古く、今どきのデスクトップ環境を存分に利用するにはやや非力なので、もっと軽量なデスクトップを使いたいとは思うものの、頻繁に利用するアプリケーションのアイコンをデスクトップ上に表示できさえすれば、これでも結構満足できてしまいます。

SWT ライブラリのダウンロード

SWT は前述したように、プラットフォームに依存した GUI ライブラリであるため、SWT を利用したアプリケーションを開発するには、まず、開発環境が動作するプラットフォーム用の SWT ライブラリをダウンロードしておく必要があります。下記のサイトからプラットフォームにあった SWT のライブラリをダウンロードします。

私の環境では、Linux (x86_64/GTK+)の swt-4.4-gtk-linux-x86_64.zip をダウンロードします。

次に、Eclipse を起動して、メニューから FileImport... を選択して、ダウンロードした SWT ライブラリを、プロジェクトとしてワークスペースにインポートします。

以上で SWT ライブラリを Eclipse で利用するための準備は終わりです。プロジェクトの生成時に、今回インポートした org.eclipse.swt へパスを通せば、SWT ライブラリが利用できます。

動作確認

SWT ライブラリが利用できることを確認するために、古い記事ですが、下記のサイトで紹介されているサンプルを試してみました。

Java のプロジェクトを生成する際に、先程インポートした org.eclipse.swt へパスを通しておきます。

上記記事で紹介されていたサンプルをコピーしてクラス名を調整します。

コンパイルして実行したサンプルに、適当な CSV ファイルを読み込ませてみた例です。

SWT ライブラリを利用したアプリケーションの動作が確認できましたので、しばらくは、(今更ではありますが)基本的なウィジェットのサンプルを備忘録がわりに紹介していきたいと思います。とりあえずサンプルの解説は後回しにして、サンプルコードと実行例だけ掲載していきます。

下記は Windows の例ですが、丁寧に説明されています。