首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

JComboBox 下拉選單向上彈出實作:自訂 UI 委派

     編輯:Java 相關知識

前言

預設 JComboBox 下拉選單向下彈出,若組件位於視窗底部會被遮擋。本文展示如何透過繼承 BasicComboBoxUI 並重寫 createPopup() 與 createArrowButton() 實現向上彈出效果。

完整實作代碼

package kissJava.JComboBox;

import java.awt.Dimension;
import javax.swing.*;
import javax.swing.plaf.*;
import javax.swing.plaf.basic.*;

public class PopupComboSample extends JFrame {
    private static final String[] LABELS = {
        "Chardonnay", "Sauvignon", "Riesling", "Cabernet",
        "Zinfandel", "Merlot", "Pinot Noir", "Sauvignon Blanc",
        "Syrah", "Gewurztraminer"
    };
    private JComboBox jcb;

    public PopupComboSample() {
        setTitle("下拉框向上彈出示範");
        setLayout(null);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        jcb = new JComboBox<>(LABELS);
        // 關鍵:設定自訂 UI
        jcb.setUI((ComboBoxUI) MyComboBoxUI.createUI(jcb));
        jcb.setBounds(50, 150, 200, 20);
        add(jcb);

        setSize(300, 300);
        setLocationRelativeTo(null);
        setVisible(true);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(PopupComboSample::new);
    }

    static class MyComboBoxUI extends BasicComboBoxUI {
        private int newsBarTitleWidth = 0;

        public static ComponentUI createUI(JComponent c) {
            return new MyComboBoxUI();
        }

        // 1. 改變箭頭方向:向上
        @Override
        protected JButton createArrowButton() {
            return new BasicArrowButton(BasicArrowButton.NORTH);
        }

        // 2. 改變彈出方向:向上
        @Override
        protected ComboPopup createPopup() {
            BasicComboPopup popup = new BasicComboPopup(comboBox) {
                @Override
                public void show() {
                    // 計算彈出選單大小 (寬度同 comboBox,高度 5 行)
                    Dimension popupSize = new Dimension(200 - newsBarTitleWidth - 5, 17 * 5);
                    scroller.setMaximumSize(popupSize);
                    scroller.setPreferredSize(popupSize);
                    scroller.setMinimumSize(popupSize);
                    
                    // 關鍵:y 為負值表示向上彈出
                    // show(invoker, x, y) - y 相對於 invoker 左上角
                    show(comboBox, newsBarTitleWidth, -17 * 5);
                }
            };
            popup.getAccessibleContext().setAccessibleParent(comboBox);
            return popup;
        }
    }
}

效果展示

核心原理解析

1. UI 委派模式

Swing 採用可換裝 Look & Feel 架構,JComboBox 透過 setUI(ComboBoxUI) 設定外觀與行為委派。BasicComboBoxUI 是 Metal/Windows 等 L&F 的基礎實作。

2. 關鍵方法重寫

方法用途本例修改
createArrowButton()建立下拉箭頭按鈕BasicArrowButton.NORTH (向上箭頭)
createPopup()建立彈出選單容器回傳自訂 BasicComboPopup,重寫 show()
show() in Popup控制彈出位置與大小y = -height 實現向上彈出

3. 座標系統說明

show(Component invoker, int x, int y)
// invoker: 參考組件 (這裡是 comboBox)
// x, y: 相對於 invoker 左上角 (0,0) 的偏移
// y > 0: 向下;y < 0: 向上

進階改良:動態計算彈出方向

static class SmartComboBoxUI extends BasicComboBoxUI {
    @Override
    protected ComboPopup createPopup() {
        return new BasicComboPopup(comboBox) {
            @Override
            public void show() {
                // 取得螢幕邊界
                Point loc = comboBox.getLocationOnScreen();
                int screenHeight = Toolkit.getDefaultToolkit().getScreenSize().height;
                int comboHeight = comboBox.getHeight();
                int popupHeight = getPopupHeightForRowCount(comboBox.getMaximumRowCount());
                
                // 判斷向下是否會超出螢幕
                boolean downwardFits = (loc.y + comboHeight + popupHeight) < screenHeight;
                
                Dimension popupSize = new Dimension(comboBox.getWidth(), popupHeight);
                scroller.setMaximumSize(popupSize);
                scroller.setPreferredSize(popupSize);
                scroller.setMinimumSize(popupSize);
                
                int y = downwardFits ? comboHeight : -popupHeight;
                show(comboBox, 0, y);
            }
        };
    }
}

已知問題與修正

原文提到「輸入框沒有線條顯示」,原因在於自訂 UI 未正確繪製邊框。修正方法:

@Override
public void paintCurrentValue(Graphics g, Rectangle bounds, boolean hasFocus) {
    super.paintCurrentValue(g, bounds, hasFocus);
    // 確保邊框繪製
    if (comboBox.isEditable()) {
        // 編輯模式下的編輯器繪製
    } else {
        // 唯讀模式下的渲染器繪製
        ListCellRenderer renderer = comboBox.getRenderer();
        Component c = renderer.getListCellRendererComponent(
            listBox, comboBox.getSelectedItem(), -1, false, false);
        c.setBounds(bounds);
        SwingUtilities.paintComponent(g, c, comboBox, bounds);
    }
}

// 或更簡單:確保安裝預設邊框
@Override
public void installUI(JComponent c) {
    super.installUI(c);
    // 確保有邊框
    if (comboBox.getBorder() == null) {
        comboBox.setBorder(UIManager.getBorder("ComboBox.border"));
    }
}

適用場景

  • 表單底部的下拉選單
  • 工具列、狀態列上的選單
  • 多螢幕環境下的動態定位
  • 觸控裝置優化 (避免手指遮擋)
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved