ルックアンドフィールの作成-パートI

こんにちは、ガーディアン!

Swingグラフィックスについてはかなり多くのことが言われいますが、サードパーティライブラリによって提供されるさまざまな優れた機能については少し言及れています。

今日、私はあなたの裁判所に、Swingが提供するすべての可能性に飛び込む「最後のフロンティア」を提示します-独自のルックアンドフィールクラスと、プロセスに必要なすべてを作成します。

いくつかの興味深い点を「覗き見」するだけでなく、例を作成したい場合は、すぐに言う必要があります -もちろん、多かれ少なかれ活気があり、あらゆるアプリケーションで使用できるものをリリースする場合は、このビジネスに多くの時間を費やす準備をしてください「ラッパー」としてだけでなく、作成したLaFを引き続きサポートします。

ただし、LaFを作成する予定がない場合でも、コンテンツに精通することをお勧めします。 新しくて面白いものがたくさん見つかると思います。 そして、おそらく、あなたは、これまたはそのインターフェースの問題の原因が何であるかを理解するでしょう。おそらく、何ヶ月も何年もあなたを苦しめたでしょう。

ヒント:まだグラフィックスに精通しておらず、Swing in Javaが多少詳しくなっている場合は、まずトピックに関するいくつかの記事を読むことをお勧めします( 公式チュートリアル 、SkipyのSwing 記事 、または以前の入門 記事など )。

MyLookAndFeel.java


したがって、時間を無駄にすることなく、基本から始めましょう-LookAndFeelクラス自体。 「空の」LookAndFeelではなく、BasicLookAndFeelのMyLookAndFeelクラスに従います。さまざまなUIクラスの定数、標準コンポーネントショートカット、カラースキームなど、多くのUIクラスが既に実装されているため、正しく初期化することさえできません(特に、これらの変数のほとんどは再定義する必要がないため)。

そこで、既製のBasicLookAndFeelを使用し、将来のLaFを記述するいくつかの必要なメソッドを再定義します。
パブリック クラス MyLookAndFeel BasicLookAndFeelを拡張します
{
public String getDescription ()
{
// LaFの説明
return "クロスプラットフォームJava Look&Feel" ;
}

public String getName ()
{
// LaFの名前
return "MyLookAndFeel" ;
}

public String getID ()
{
//一意の識別子LaF
return getName ();
}

public boolean isNativeLookAndFeel ()
{
//このLaFは現在のプラットフォームにバインドされていますか(ネイティブ実装ですか)
falseを 返し ます
}

public boolean isSupportedLookAndFeel ()
{
//このLaFは現在のプラットフォームでサポートされていますか?
trueを 返し ます
}
}

これで、アプリケーションを作成する前に1つの「マジック」行を適用し、LaFを使用します。
UIManager setLookAndFeel MyLookAndFeel。class。getCanonicalName () );

簡単な例で得られるものは次のとおりです。


このLaFにはすでに注目すべきプラスが1つあります。これは完全にクロスプラットフォームです。つまり、現在の実装では、Windows、Mac OS、Linuxシステムでひどく 同じに見えます。

それは化粧品に取り組むために残っています-それは私たちがやることです!..

コンポーネントのスタイル


したがって、各コンポーネントを変更するにはいくつかのものが必要です。最も明白なものから始めましょう-選択したコンポーネントのUIクラス作成します。

簡単にするために、ボタンを見てみましょう-最もよく知られているコンポーネントです。
BasicLookAndFeelと同様に、すべてのJコンポーネントにはBasicUI実装があり、コンポーネントのスタイル設定に必要な操作を大幅に簡素化および最小化します。 ボタンの場合、推測するのは難しくありません。BasicButtonUIです。これを継承して再定義します。

すべてのUIクラスには、特殊なメソッド(このUIのコンポーネントに応じて独自のメソッドがある)に加えて、ComponentUIから継承されたいくつかの標準メソッドがあります。

  1. public void installUI(JComponent c)
    このメソッドは、特定のJコンポーネントにUIクラスを「インストール」するように設計されています。 このメソッドでは、JコンポーネントのリスナーまたはUIの正しいレンダリングと操作に必要なその他のデータを定義する価値があります。
    このメソッドは、Jコンポーネントを作成するときにupdateUI()から呼び出されます。

  2. public void uninstallUI(JComponent c)
    この方法では、コンポーネントに追加したすべてのリスナーを削除し、メモリから不要なデータを消去することが非常に望ましいです。
    このメソッドは、コンポーネントが現在のUIを変更したとき、またはコンポーネントが完全に削除され、依存関係が「クリア」されたときにsetUI(ComponentUI newUI)から呼び出されます。

  3. public void paint(グラフィックスg、JComponent c)
    このメソッドは、コンポーネントのレンダリングに直接関与します。 このメソッドは、あらゆる状態のコンポーネントの視覚的なデザインを決定するため、UIクラスのキーです。
    このメソッドは、JComponentクラスの子孫に存在するpaintComponent(Graphics g)からコンポーネントをレンダリング/再描画するために間接的に呼び出されます。つまり、このメソッドは、再描画が必要な場合にのみ機能します。 paintComponent(Graphics g) )で。

  4. public void update(Graphics g、JComponent c)
    このメソッドは、 paint(Graphics g、JComponent c)への小さな追加にすぎませんが、JComponentsの paintComponent(Graphics g)から呼び出され、UIクラスから前述のpaint(Graphics g、JComponent c)を呼び出します。 実際、このメソッドは、オブジェクトの「時代遅れの」背景を上書きしてから、「クリーンシート」にレンダリングすることを実装しています。

  5. public Dimension getPreferredSize(JComponent c)
    このメソッドは、このUIクラスがインストールされるコンポーネントの望ましいサイズを決定します。
    必要に応じて、Jomponentまたはその相続人の同じ名前のgetPreferredSize()メソッドから呼び出され、必要なコンポーネントサイズを再計算します。

  6. public Dimension getMinimumSize(JComponent c)
    public Dimension getMaximumSize(JComponent c)
    これらのメソッドのうち2つは、コンポーネントの最小サイズと最大サイズを決定し、サイズ変更時にさまざまなレイアウトマネージャーが考慮することができます。 デフォルトでは、UIクラスに実装され、前述のgetPreferredSize(JComponent c)と同じ値を返します。

  7. public boolean contains(JComponent c、int x、int y)
    このメソッドは、ポイントがこのコンポーネントに属するかどうかを決定します。 ほとんどの場合、マウスイベントを正しく処理するために必要です。 任意のマウスイベントで、特定のポイントで「見られる」最上部のコンポーネントに送信されます。 また、特定のポイントの最上部にあるコンポーネントを見つけるために、正確に、そこにあるコンポーネントが順次調べられます。 つまり、このメソッドを使用すると、たとえば、実際にコンポーネントが描画されず、イベントを受け取る必要のない場所(たとえば、タブの右側の空の領域や丸いボタンのコーナー領域)で、コンポーネントにイベントを強制的に渡すことができます。
    このメソッドはcontains(int x、int y)から呼び出され、JComponentクラスのすべての子孫に存在します。
別の重要な方法については少し後で説明します...

ヒント: ComponentUIにはさらにいくつかのメソッドがありますが、今日はそれらを必要とせず、あなたが使用することはほとんどないので、時間を節約し、説明をスキップできるようにします。

混乱しないように、もう一度言及したクラス/メソッドのリストを提供します:

成分Componententui実際、ComponentUIのこれらの基本メソッドに基づいて、コンポーネントのすべてのUIクラスが構築されます。 生活を簡素化するために、BasicUIクラスには、コンポーネントのレンダリングを指定および簡略化する他の多くのメソッドも用意されています。

LookAndFeelについてもう少し


LookAndFeelクラスの構築についてもう少し説明を追加して、将来的にこのクラスに戻らないようにします。

LookAndFeelの初期化時にUIDefaultsで上記の標準コンポーネントのUIクラスを指定する可能性に加えて、lookAndFeelクラスを実行する他の多くの要素があります。 それらのほとんどは、BasicLookAndFeelクラスに既に含まれているため、ほとんど実装/再定義する必要はありませんが、順序のために、そこで何が起こっているかを知る価値があります。

したがって、LookAndFeelクラスで提示されるメソッドを検討してください。
  1. installColors(JComponent c、String defaultBgName、String defaultFgName)
    installColorsAndFont(JComponent c、String defaultBgName、String defaultFgName、String defaultFontName)
    installBorder(JComponent c、String defaultBorderName)
    uninstallBorder(JComponent c)
    installProperty(JComponent c、String propertyName、Object propertyValue)
    makeKeyBindings(Object [] keyBindingList)
    makeInputMap(オブジェクト[]キー)
    makeComponentInputMap(JComponent c、オブジェクト[]キー)
    loadKeyBindings(InputMap retMap、Object []キー)
    makeIcon(最終クラス<?> baseClass、最終文字列gifFile)
    getDesktopPropertyValue(String systemPropertyName、Object fallbackValue)
    この膨大な静的メソッドのセットはすべて、LookAndFeelクラスで実質的に1つの目的で使用できます。キーの使用を簡略化し、これらのキーから取得した値をさまざまなコンポーネントに設定します。 背景やフォントの色、テキストフィールドのキーボードショートカット、コンポーネントの境界線などです。 それらは、個別のUIクラスおよびUIDefaultsの大部分で使用されます。 各メソッドの前にあるコメントで詳細に説明されているため、個々のメソッドの目的を説明することには特別な意味はありません。

  2. public LayoutStyle getLayoutStyle()
    このメソッドは、さまざまなレイアウトのコンポーネントの動作を決定するLayoutStyleクラスのインスタンスを返します。 たとえば、同じコンテナ上に次々に配置されているラベルとテキストフィールドの間でどのインデントが優先されるかを決定できます(はい、そのような特定のケースまで)。 ただし、これはかなり抽象的なものであり、実際には、このクラスで定義されたパラメーターを使用するレイアウトは非常に少なく、この点はクラスへのコメントで開発者によって指摘されていました。 たとえば、Swingでは、LayoutStyleを使用する唯一のレイアウトはGroupLayoutであり、その詳細を考えると奇妙ではありません。

  3. public void provideErrorFeedback(コンポーネントコンポーネント)
    このメソッドは、ユーザーが違法なアクションを実行するたびに呼び出されます(たとえば、空のテキストフィールドでBackspaceキーを押すか、編集できないフィールドのテキストを削除しようとするなど)。 標準実装では、このような場合は、現在のOSの標準である不正な操作の短い音声信号を再生するToolkit.getDefaultToolkit().beep()メソッドを呼び出すだけです。 ただし、この動作はいつでも変更できます(たとえば、この迷惑な*ビープ*を完全に削除します)。

  4. public Icon getDisabledIcon(JComponentコンポーネント、アイコンアイコン)
    このメソッドは、現在のLookAndFeelに対応するロックされたアイコンの表示を返します。 このメソッドは、他の標準コンポーネントのロックされたボタン、ラベル、およびロックされた要素のアイコンを表示するために使用されます。 「ロックされた」タイプのアイコンを作成するためのより成功した/より速い方法がある場合-このメソッドの再定義に行ってください!

  5. public Icon getDisabledSelectedIcon(JComponentコンポーネント、アイコンアイコン)
    この方法は前の方法と同じで、「選択された」アイコンに対してのみ機能します。 標準実装では、以前の方法はささいなものです。

  6. public String getName()
    public String getID()
    public String getDescription()
    public boolean isNativeLookAndFeel()
    public boolean isSupportedLookAndFeel()
    これらのメソッドは、LookAndFeelクラスを作成するときに既に説明されています。 彼らはこのLookAndFeelに関する基本的な情報を提供するだけです。

  7. public boolean getSupportsWindowDecorations()
    このメソッドはより興味深い-このLookAndFeelによって返されたRootPaneUIがウィンドウを装飾できるかどうかをUIManagerに伝えます。 つまり、標準のシステム設計の代わりにウィンドウ設計を作成します。 このメソッドがtrueを返す場合、RootPaneUIに特定のウィンドウ装飾を実装しています。次の2つのメソッドに従ってください。
    JFrame setDefaultLookAndFeelDecorated true );
    JDialog setDefaultLookAndFeelDecorated true );
    JDialog / JFrameインスタンスを特別なデザインでラップするため。 スプレーしないように、このようなデザインを作成する方法とほぼ同じ方法で、時間内に別のトピックを作成します。

  8. public void initialize()
    public void uninitialize()
    これらのメソッドはインストール中に直接呼び出され、それに応じて、UIManagerを介したLookAndFeelの変更が呼び出されます。 これらをラップして、グローバルリスナーを作成し、LookAndFeelが正しく機能するために必要な変数を作成できます。

  9. public UIDefaults getDefaults()
    この方法は、概して、すべてのコンポーネントの基本設計の基礎です。 LookAndFeelが機能するために必要なさまざまな変数(フォント、色、ホットキーなど)とその個々のUIクラスをすべて提供します。 BasicLookAndFeelでは、このメソッドは3つの部分に分かれています。
    1. initClassDefaults(UIDefaultsテーブル)
      MyLookAndFeelでこのメソッドを再定義して、ボタンのUIクラスを「置換」しました。この目的のために再定義する必要があるのはまさにそれです。

    2. initSystemColorDefaults(UIDefaultsテーブル)
      このメソッドは、さまざまな要素の標準色を定義します(奇妙なことに、16進値)。

    3. initComponentDefaults(UIDefaultsテーブル)
      この同じ最大の方法では、必要に応じて、個々のコンポーネントのフォント、色、キーボードショートカット、ボーダーボード、その他のささいなものが決定されます。


おそらく今、あなたは最初のUIクラスに違反するのに十分なことを学んだでしょう...

MyButtonUI.java


上で書いたように、BasicButtonUIからUIクラスを継承します。 最初に、ボタンのシンプルなインターフェースをスローし、すべてが私たちの考えているとおりに動作することを確認します。
パブリック クラス MyButtonUI BasicButtonUIを拡張します
{
public void installUI JComponent c
{
//必ず基本UIクラスで実装されたUIインストールのままにしてください
スーパー installUI c );

//目的のJButton設定を設定します
//抽象化には、AbstractButtonを使用します。必要なものがすべて揃っているためです
AbstractButton button = AbstractButton c ;
ボタン setOpaque false );
ボタン setFocusable true );
ボタン setMargin 新しい Insets 0、0、0、0 );
ボタン setBorder BorderFactory。createEmptyBorder 4、4、4、4 );
}

public void paint Graphics g JComponent c
{
Graphics2D g2d = Graphics2D g ;
g2d setRenderingHint RenderingHints。KEY_ANTIALIASING RenderingHints。VALUE_ANTIALIAS_ON );

AbstractButton button = AbstractButton c ;
ButtonModel buttonModel = button getModel ();

//ボタンは角丸長方形になります

//ボタンの背景
g2d setPaint new GradientPaint 0、0、 Color。WHITE 0、 c。getHeight )、
新しい 200、200、200 );
//フォームを描画するときよりも丸めを行う必要がある
//それ以外の場合、明るい背景がエッジの周りを照らします
g2d fillRoundRect 0、0、 c。getWidth () c。getHeight () 8、8 );

//ボタンの境界線
g2d setPaint Color。GRAY );
//形状はコンポーネントの幅/高さよりも1ピクセル小さくする必要があることに注意してください。
//それ以外の場合、Figureの右端と下端はコンポーネントの境界から飛び出し、表示されません
//塗りつぶしには適用されません。ピクセルの最後の列/行は塗りつぶし時に無視されるためです
g2d drawRoundRect 0、0、 c。getWidth - 1、 c。getHeight - 1、6、6 );

//ボタンが押されている間に図面をシフトします
if buttonModel。isPressed ()
{
g2d 翻訳 1、1 );
}

//テキストと画像のアイコンを描画します
スーパー ペイント g c );
}
}
次に、MyLookAndFeelでMyButtonUIを使用して、すべてのボタンに自動的にインストールされるようにする必要があります。 これを行うには、MyLookAndFeelクラスで、 initClassDefaults(UIDefaultsテーブル)メソッドを継承し、必要な値を再定義します。
protected void initClassDefaults UIDefaultsテーブル
{
//すべてをまだ実装していないため、デフォルトの初期化のままにしておきます
// JコンポーネントのさまざまなUIクラス
スーパー initClassDefaults テーブル);

//そして実際、ここで最も重要なのは
テーブル put "ButtonUI" MyButtonUI。class。getCanonicalName () );
}
古い例を実行し、「更新済み」ボタンを表示する準備をします。


うーん、何かがうまくいかなかった... UIクラスのどこかで本当に台無しになったのか?
UIボタンを直接設定してみましょう。
final JButton jButton = new JButton "JButton" );
jButton setUI new MyButtonUI () );
追加 jButton );
私達は試みます:

いいえ、UIは問題ありません。 何が悪いの?

おそらく一日中戦ったのはこの瞬間でした。 その結果、もちろん、すべてが平凡なものであることが判明しました。まず、MyButtonUIクラスで最初に記述する必要がある別のメソッドcreateUI(JComponent c)を逃しました。 どうして彼を恋しく思いましたか? 非常に簡単です-時にはあまりにも有害で近視眼的でIDEに頼れないことがあります-視界から重要なポイントを失う可能性があります(私の不注意が非難であるため、決してIDEを非難しません)

このメソッドはComponentUIにありますが、... public static!? 問題は、抽象ComponentUIがオーバーライドできない静的メソッドである理由です(すぐに表示されなかった理由です)。

実際、このメソッドは、Reflectionを介してUIDefaultsクラスでUIを作成するときに呼び出されるため、この場合、このメソッドを作成しなかったため、呼び出すときに最初に使用したもの(BasicButtonUIの実装)が使用されました。 そして、ソースコードを見ると、彼女は次のようになっています。
public static ComponentUI createUI JComponent c {
AppContext appContext = AppContext getAppContext ();
BasicButtonUI buttonUI =
BasicButtonUI appContext get BASIC_BUTTON_UI_KEY );
if buttonUI == null {
buttonUI = new BasicButtonUI ();
appContext put BASIC_BUTTON_UI_KEY buttonUI );
}
戻る buttonUI ;
}
実際、これがMyButtonUIが使用されなかった理由です。 この見落としを修正し、 createUI(JComponent c)メソッドをMyButtonUIに追加します。
public static ComponentUI createUI JComponent c
{
// UIのインスタンスを作成します
新しい MyButtonUI ()を 返し ます。
}
JButtonにUIを強制的にインストールせずに、サンプルをもう一度実行してみましょう。

やれやれ!

ヒント:特定のタイプのすべてのコンポーネントに同じUIクラスを使用できる場合は、実行する必要があります。 MyButtonUIの場合、これも可能であるため、上記のメソッドを少し変更します。
private static MyButtonUI instance = null ;

public static ComponentUI createUI JComponent c
{
// UIのインスタンスを作成します
if instance == null
{
instance = new MyButtonUI ();
}
インスタンスを返す ;
}
このUIの使用により、必要なメモリと、個々のJButtonごとに作成されたMyButtonUIクラスの過剰なインスタンスの両方を保存できます。 ただし、各コンポーネントのUIを個別に様式化する機能を追加する場合、このオプションは機能しません。

軟膏で飛ぶ


これで、ボタンの独自のUIクラスをLookAndFeelで作成およびインストールする方法がわかりましたが、他のコンポーネントについてはどうでしょうか。 すべて同じです。すべて同じです。initClassDefaults(UIDefaultsテーブル)メソッド必要なUIクラスを追加するだけです。 最終的なフォームでは、次のようになります。
protected void initClassDefaults UIDefaultsテーブル
{
//ラベル
テーブル put "LabelUI" ... );
テーブル put "ToolTipUI" ... );

//ボタン
テーブル put "ButtonUI" ... );
テーブル put "ToggleButtonUI" ... );
テーブル put "CheckBoxUI" ... );
テーブル put "RadioButtonUI" ... );

//メニュー
テーブル put "MenuBarUI" ... );
テーブル put "MenuUI" ... );
テーブル put "PopupMenuUI" ... );
テーブル put "MenuItemUI" ... );
テーブル put "CheckBoxMenuItemUI" ... );
テーブル put "RadioButtonMenuItemUI" ... );
テーブル put "PopupMenuSeparatorUI" ... );

//セパレーター
テーブル put "SeparatorUI" ... );

//スクロール
テーブル put "ScrollBarUI" ... );
テーブル put "ScrollPaneUI" ... );

//テキスト
テーブル put "TextFieldUI" ... );
テーブル put "PasswordFieldUI" ... );
テーブル put "FormattedTextFieldUI" ... );
テーブル put "TextAreaUI" ... );
テーブル put "EditorPaneUI" ... );
テーブル put "TextPaneUI" ... );

//ツールバー
テーブル put "ToolBarUI" ... );
テーブル put "ToolBarSeparatorUI" ... );

//テーブル
テーブル put "TableUI" ... );
テーブル put "TableHeaderUI" ... );

//セレクター
テーブル put "ColorChooserUI" ... );
テーブル put "FileChooserUI" ... );

//コンテナ
テーブル put "PanelUI" ... );
テーブル put "ViewportUI" ... );
テーブル put "RootPaneUI" ... );
テーブル put "TabbedPaneUI" ... );
テーブル put "SplitPaneUI" ... );

//複雑なコンポーネント
テーブル put "ProgressBarUI" ... );
テーブル put "SliderUI" ... );
テーブル put "SpinnerUI" ... );
テーブル put "TreeUI" ... );
テーブル put "ListUI" ... );
テーブル put "ComboBoxUI" ... );

//デスクトップペイン
テーブル put "DesktopPaneUI" ... );
テーブル put "DesktopIconUI" ... );
テーブル put "InternalFrameUI" ... );

//オプションペイン
テーブル put "OptionPaneUI" ... );
}
もちろん、各「...」の代わりに、特定のJコンポーネントの外観を決定する特定のUIクラスへのパスが必要です。

これで、標準のSwingコンポーネントのすべてのUIを完全に再定義および実装するのにどれだけの作業(および時間)が必要になるかを少なくともおおよそ想像できます。

しかし、すべてがそれほど悪いわけではありません-最初にLookAndFeelのコンセプト/外観を決定した場合、事態はずっと速くなります。 私自身の経験から確信したように、ほとんどのUIの実装は、抽象化、個別のユーティリティクラスに入れ、どこでも使用できるいくつかの標準メソッドを使用することになります(たとえば、境界線、背景の描画、標準図形の作成など)。 一部の複雑なコンポーネントの外観を思い浮かべるには、ほんの少しの「魔法」しか必要ありません。

そのため、さまざまなUIクラスの知識を継続します。あるいは、BasicUIクラスが提供するメソッドと可能性、および個々のUIに関連付けられ、同じ「魔法」を必要とする微妙な点と複雑な点を検討します。

おそらく順序のために、上のコードで与えられたリストにまっすぐ行きましょう...

テキストコンポーネント


BasicLabelUI - JLabel

最も簡単なコンポーネントの1つ。 ほとんどのLookAndFeelsでは、LabelUIはまったくオーバーライドされず、BasicLabelUIを使用するか、わずかに変更されます。 たとえば、WindowsLabelUIでは、ニーモニックアンダースコアのレンダリングの動作は異なります。Altを押している場合にのみ表示されます。

必要なもの-プレーンテキストまたはHTMLテキストのレンダリング、およびアンダースコアニーモニック-はBasicLabelUIにあります。

ちなみに、主なトリックはテキストの正しいレンダリングにあります-SwingUtilities / SwingUtilities2のメソッドがアクティブに使用されます。 最初に、テキストの位置が決定され、次に標準メソッドが使用されて描画されます。
SwingUtilities2 drawStringUnderlineCharAt l g s mnemIndex textX textY );
実際、主な作業はSwingUtilities2クラスによって実行されます。

さらに学習することに興味がある場合は、BasicLabelUIクラスを徹底的に学習できます。 すでにスケジュールが非常に順調であれば、問題は発生しません。

私たちは長引かないで先に進みます。

BasicToolTipUI -JToolTip

JLabelコンポーネントほどシンプルではありません。 実際、JToolTip(抽象的に言えば)は背景上のテキストであり、何らかの境界線があります。

ただし、1つの小さなニュアンスがあります-このコンポーネントは、表示されると、ツールチップが表示されたウィンドウのJLayeredPaneに、またはツールチップがウィンドウの外側にある場合は別のJWindowに配置されます。 このニュアンスは、ツールチップの特定の形式をレンダリングして回避し、多かれ少なかれ長方形の形式に固執するときに考慮する必要があります。

また、JToolTipをsetOpaque(false)に設定し、現在のウィンドウ内にある場合、それは本当に透明になります。 ツールチップが別のJWindowに表示される場合、このトリックは機能しなくなります。

これまでのところ、この状況から抜け出すためのいくつかのトリックがあります-最近登場した機能(バージョン1.6 u10のAWTUtilitiesまたは1.7+のWindowメソッド)でウィンドウを透明にするか、ツールチップが表示される領域の「スクリーンショット」を取り、背景として配置する。

最初のオプションにはマイナスがあり、この機能はどこでも正しく動作しません。 2番目のものは、静的および操作速度にマイナスがあります。つまり、ツールチップウィンドウの下にアニメーションがあった場合、その不在は顕著になります。 ただし、ツールチップのコンテキストでは、これが大きな役割を果たすことはほとんどありません。

ボタン


BasicButtonUI -JButton

上記のMyButtonUIの例では、BasicButtonUIで定義されたメソッドを使用せず、単にpaintメソッド(Graphics g、JComponent c)を完全に再定義しました。

これを行うか、メソッドを継承します。
paintButtonPressed(グラフィックスg、AbstractButton b)
paintText(Graphics g、AbstractButton b、Rectangle textRect、String text)
paintIcon(グラフィックg、JComponent c、Rectangle iconRect)
paintFocus(グラフィックg、AbstractButton b、Rectangle viewRect、Rectangle textRect、Rectangle iconRect)
あなたのビジネスは純粋にあなたのものであり、描かれているボタンの複雑さにかなり依存しています。

必要に応じて、独自の方法を使用して、レンダリングを部分に完全に分割できます。 たとえば、アイコンと背景とテキストのレンダリングを別々のメソッドに入れますが、それ以上は行いません。

いずれにしても、もちろんどこでも間違いを犯さない限り、結果は同じになります。

BasicToggleButtonUI -JToggleButton

JToggleButtonにはToggleButtonUIクラスはありません-BasicToggleButtonUIのみがBasicButtonUIから継承されており、実際にはそれらの間にほとんど違いはありません。 ちょうど今、JToggleButtonでは、ボタンモデルはselected = true状態を取ることができますが、JButtonではできません。ただし、モデル自体は両方の場合で同じです。

したがって、視覚的な違いは、JToggleButtonがマウスでコンポーネントを保持せずに押された状態のままにできることだけです。 この場合、レンダリングもロジックも実際には変更されません。

したがって、JButtonとJToggleButtonの両方のUIを同じクラスで実装するか、視覚的なクリーンさ(MyToggleButtonUIがMyButtonUIを拡張する)を継承することは論理的に思えます。もちろん、静的createUIメソッド(JComponent c)をMyToggleButtonUIに追加することを忘れないでください。

BasicRadioButtonUI -JRadioButton

JToggleButtonと同様-このコンポーネントにはRadioButtonUIクラスがありません。 BasicRadioButtonUIはBasicToggleButtonUIから継承され、レンダリングされたアイコンと標準の背景の欠如のみが異なります。

BasicCheckBoxUI -JCheckBox

このUIは、アイコンを除き、BasicRadioButtonUIを完全に複製します。

メニュー


BasicMenuBarUI -JMenuBar

このコンポーネントは、JMenuコンポーネントの「スタンド」であり、それ自体では、背景を除き、何も描画しません。

ただし、BasicMenuBarUIをオーバーライドする場合、「継承によって」継承されるコンポーネントの境界線を再定義することも価値があります。

別の機能-Mac OSでは、システムメニューバーとの統合が使用されている場合、他のすべてのUIメニューとメニュー項目と同様に、デザインとUI自体は意味がありません。
すなわち、使用する場合:
システム setProperty "apple.laf.useScreenMenuBar" "true" );
Mac OSで実行している場合。

BasicMenuItemUI -JMenuItem

正直なところ、このコンポーネントは、実装とandの数で最も劣悪です。 したがって、すべての問題領域をより詳細に説明します。

BasicMenuItemUIには、ソース間隔のメソッドがいくつかあります。
  1. paintMenuItem(Graphics g、JComponent c、Icon checkIcon、Icon arrowIcon、Color background、Color foreground、int defaultTextIconGap)
    メニュー項目のさまざまな部分のレンダリングをまとめるメインメソッド。

  2. paintBackground(グラフィックスg、JMenuItem menuItem、Color bgColor)
    メニュー項目の背景を描画します。

  3. paintCheckIcon(グラフィックスg、MenuItemLayoutHelper lh、MenuItemLayoutHelper.LayoutResult lr、Color holdc、Color foreground)
    JRadioButtonMenuItemまたはJCheckBoxMenuItemの選択アイコンを描画します。

  4. paintIcon(グラフィックスg、MenuItemLayoutHelper lh、MenuItemLayoutHelper.LayoutResult lr、Color holdc)
    JMenuItemまたはJMenuによって提供されるアイコンを描画します。

  5. paintText(グラフィックスg、MenuItemLayoutHelper lh、MenuItemLayoutHelper.LayoutResult lr)
    メニュー項目のテキストを描画します。

  6. paintAccText(グラフィックスg、MenuItemLayoutHelper lh、MenuItemLayoutHelper.LayoutResult lr)
    このメニュー項目にハングアップしたホットキーのテキストを描画します(たとえば、「Alt + F4」)。

  7. paintArrowIcon(Graphics g、MenuItemLayoutHelper lh、MenuItemLayoutHelper.LayoutResult lr、Color foreground)
    JMenuのサブメニューを指す矢印を描画します。


最も興味深いのは、これらの半分が通常のJMenuItemでは必要ないことです。 しかし、先を見据えて、このクラスは、JMenu、JMenuItem、JCheckBoxMenuItem、JRadioButtonMenuItemの4つの異なるメニュー項目すべての視覚的表現を実装すると言います。

そして、これはすべてのおの始まりに過ぎません。

個々の要素のサイズと位置のすべての計算は、通常は別のパッケージ(sun.swing)にあるMenuItemLayoutHelperクラスで行われ、いかなる方法でも変更することはできません。 また、このクラスは、一部の古いJDKおよびOpenJDKアセンブリには含まれていません。

その結果、何らかの方法で要素の配置やサイズに満足できない場合は、トリックとトリックに頼り、あらゆる方法で標準のMenuItemLayoutHelperデータを回避する必要があります(たとえば、16x16アイコンのスペースで行のすべてのメニュー要素を揃えなければなりませんでした要素が指定されていない場合)。

彼との役に立たない戦争で数日間殺された後、私はその考えを放棄し、MenuItemLayoutHelperコードの一部を使用して、カウント用の独自のクラスを作成しました。 それでも、オプションとして、MenuItemLayoutHelperの使用を完全に放棄し、すべての計算を再度実装できます。

BasicMenuUI -JMenu

このUI 1 in 1はBasicMenuItemUIの実装を繰り返しますが、悪さの少ない方を選択する必要があります。BasicMenuItemUIの実装を継承してBasicMenuUIからJMenuのロジックをコピーするか、BasicMenuUIを継承して子孫BasicMenuItemUIから視覚実装をコピーします。

2番目のオプションを選択したのは、コードを理解する方が簡単であり、視覚的なデザインを複製するよりもロジックの変更をサポートすることがはるかに難しいからです。

BasicCheckBoxMenuItemUI -JCheckBoxMenuItem
BasicRadioButtonMenuItemUI -JRadioButtonMenuItem

これらのUIクラスはどちらも、BasicMenuItemUIの実装からの良心の影響を受けずに拡張でき、チェックボックス/ラジオボタンの選択を描画するメソッドのみをオーバーライドできます。

または、このレンダリングをBasicMenuItemUIの子孫に直接実装し、静的createUI(JComponent c)メソッドのみを追加することで、そこからUIクラスの2つのデータを単純に拡張できます。

BasicPopupMenuSeparatorUI -JSeparator

このクラスは、わずかに変更されたBasicSeparatorUIのみを表し、メニュー項目を分離するためにJPopupMenuでのみ使用されます。 JSeparatorコンポーネント自体は、単純な描画されたストリップ/ライン/ラインであり、インターフェースの論理部分を視覚的に分離するために使用されます。

特定のUIクラスをオーバーライドする場合、標準のpaintメソッド(Graphics g、JComponent c)を変更し 、その中に必要なセパレータの外観を描画するだけで十分です。

BasicPopupMenuUI -JPopupMenu

実際、メニューシリーズの最後のコンポーネントはJPopupMenuです。 このコンポーネントの操作に必要なすべてのロジックはBasicPopupMenuUIに実装されているため、外観を好みに合わせて変更するだけです。

実際には、ここには2つのポイントがあります。ポップアップメニューの端から内部要素までのイ​​ンデントを決定するメニュー境界線と、メニューの背景の描画です。

ボーダーはinstallUIメソッド(JComponent c)でインストールする必要があり、通常どおり、レンダリングはpaint(Graphics g、JComponent c)メソッドで行う必要があります。

また、エキゾチックにしたい場合は、ポップアップメニューの要素の選択のレンダリングを、個々のメニュー項目内ではなく、背景に実装できます。 これは、端から要素へのインデントが必要な場合に便利ですが、選択範囲を端から端までメニューの全幅まで描画する必要があります(このような選択は、たとえばMac OSのシステムポップアップメニューで確認できます)。

これを行うには、UIクラスの現在のポップアップメニューを追加で記憶し、背景をレンダリングするときに要素の状態を調べ、背景を再描画するための選択の変更を正しく聞く必要があります。

続行するには...


しばらくしてから、残りのすべてのBasicUIクラスを詳細に説明し、UIコンポーネントの実装の既製の例を提供する2番目のパートを公開します。

上記の資料をすべて読んで習得した場合は、LaFの実装を開始するか、いくつかの標準UIクラスを再定義してみてください。

また、私が現在取り組んでいる WebLookAndFeelライブラリのソースコードを掘り下げることもできます。これに基づいて、UIを書く際にかなり徹底的に練習することができました。 ここでは、すべての標準UIの本格的な実装を見つけることができます。そのほとんどは、この記事と次の記事で説明または説明しますが、その他のインターフェイス「グッズ」もあります。

約1週間で、続編の残りのUIクラスだけでなく、その詳細もお読みください。 ご清聴ありがとうございました!

Java LookAndFeel / UIについて質問がある場合は、コメントで登録を解除してください。 できる限り答えようとします。

PS読みやすいコードの強調表示については、 dumpz.orgプロジェクトに感謝​​します。

Source: https://habr.com/ru/post/J128914/


All Articles