2012-07-20 54 views
4

我有以下情況:在JCombobox中,首選大小是基於最大的項目大小。但是,此計算不考慮爲null提供的值。它只關心模型中的值。所以當渲染空值的文本大於其他元素時,標籤被截斷,並且在末尾有三個點(...)。我想避免這種情況。JComboBox首選大小與空值選擇,但不是在ComboBoxModel

這裏是我談論的一個小的演示:

Truncated null value rendered

import java.awt.Component; 
import java.awt.GridBagLayout; 

import javax.swing.DefaultListCellRenderer; 
import javax.swing.JComboBox; 
import javax.swing.JFrame; 
import javax.swing.JList; 
import javax.swing.JPanel; 
import javax.swing.SwingUtilities; 

public class TestComboBox { 

    protected void initUI() { 
     JFrame frame = new JFrame(TestComboBox.class.getSimpleName()); 
     frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
     JPanel panel = new JPanel(new GridBagLayout()); 
     JComboBox comboBox = new JComboBox(new Object[] { "Something", "Stuff", "Beep" }); 
     comboBox.setRenderer(new DefaultListCellRenderer() { 
      @Override 
      public Component getListCellRendererComponent(JList<?> list, Object value, int index, boolean isSelected, boolean cellHasFocus) { 
       Component comp = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); 
       if (value == null) { 
        setText("No selection"); 
       } 
       return comp; 
      } 
     }); 
     comboBox.setSelectedItem(null); 
     panel.add(comboBox); 
     frame.add(panel); 
     frame.setSize(200, 100); 
     frame.setVisible(true); 
    } 

    public static void main(String[] args) { 
     SwingUtilities.invokeLater(new Runnable() { 

      @Override 
      public void run() { 
       new TestComboBox().initUI(); 
      } 
     }); 
    } 
} 

如果您有任何建議,我想知道。到目前爲止,我的想法是擴展JComboBox,重寫首選大小,同時執行null值的呈現,並將調用的最大維度取爲super.preferredSize和空值呈現之一。但我覺得這有點令人失望。

我真的不喜歡使用prototypeDisplayValue 絕對不是一個選項,因爲我不知道將在該下拉列表中的值

+1

JComboBox.setPrototypeDisplayValue()在需要強制屏幕大小的情況下,爲我檢查Rob的代碼示例 – mKorbel 2012-07-20 15:37:31

+0

@mKorbel,設置原型顯示值與強制首選大小相同,我希望以避免這種情況。我不知道組合框內的值,在某些情況下,它可能會比空值渲染大。我也不希望組合框通過強制首選大小來佔用過多的空間。我只是希望我的JComboBox採用確切的所需空間來顯示所有值,包括null值,儘管null值不在ComboBoxModel中。 – 2012-07-20 15:40:59

+0

'我不知道組合框內的值,這可能會在已經可見的GUI – mKorbel 2012-07-20 17:39:16

回答

1

我沒有代碼測試這一點,但我的辦法是:

  1. 確定的preferredSize之間的差異渲染器返回的Component以及JComboBox的實際preferredSize。不是通過使用任何硬編碼的值,而是通過創建僅包含一個項目和已知渲染器的幕後的JComboBox,並將JComboBox的首選大小與由已知渲染器返回的Component的大小進行比較。每個外觀和手感由收聽者附接到UIManager
  2. 覆蓋實際JComboBoxgetPreferredSize和返回的最大的super.getPreferredSize()寬度和getPreferredSize(rendererComponent) + calculatedDifference
改變時間
  • 重複步驟1

    這應該注意外觀問題,避免不必要的計算,並且您可以輕鬆創建包含此功能的JComboBox擴展。

  • +0

    +1我喜歡所選擇的方法,這使得它可以交叉使用L&F。我會讓你知道結果 – 2012-07-20 19:03:07

    +0

    @GuillaumePolet如果你發佈了結果(可能來的),那麼可以欣賞它。想知道這實際上是否會起作用 – Robin 2012-07-20 19:05:31

    +0

    實際上當我嘗試了你的想法時,我發現有一種更簡單的方法,就是用一個值創建一個JComboBox:'null'並直接獲得它的首選大小。然後在'super.getPreferredSize()'和該組合框的首選大小之間進行聯合並將其作爲首選大小返回。簡單,沒有黑客,並且可以在所有平臺和L&F上工作。我更新了我的答案以證明這一點。 – 2012-07-20 19:17:31

    1

    我要採取的事實,我們知道,從Component返回DefaultListCellRenderer.getListCellRendererComponent優點是DefaultListCellRenderer對象本身,而在於它是JLabel一個實例。

    我還假設您的外觀和感覺以通常的方式計算組合框的首選大小,類似於BasicComboBoxUI

    有了這些信息,這種解決方案也許是醜陋和低效,但它的工作原理:

    comboBox.setRenderer(new DefaultListCellRenderer() { 
    
         @Override 
         public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { 
          Component comp = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); 
          if (value == null) { 
           setText("No selection"); 
          } 
          return comp; 
         } 
    
         @Override 
         public Dimension getPreferredSize() { 
         // this doesn't work: 
         // int minWidth = (new JLabel("No selection").getPreferredSize()).width; 
    
         // this does work: 
         String oldText = getText(); 
         setText("No selection"); 
         int minWidth = (super.getPreferredSize()).width; 
         setText(oldText); 
    
    
         Dimension d = super.getPreferredSize(); 
         if (d.width < minWidth) { 
          return new Dimension(minWidth, d.height); 
         } else { 
          return d; 
         } 
         } 
    
    +0

    首先,感謝您花時間閱讀問題並撰寫答案。儘管這種方法接近我的建議,但這還不夠,也不是很強大。不夠:你沒有考慮到邊界的大小,也沒有考慮右邊的箭頭按鈕,所以尺寸仍然太小。不健壯:跨越不同的外觀和感覺,還有其他一些必須考慮在首選大小的東西。 – 2012-07-20 18:39:30

    +0

    @GuillaumePolet你可以通過要求渲染器爲null值創建一個組件並測量該寬度來克服其中之一。然而,我同意邊界,...(正如我打算髮布或多或少相同的答案,但我遇到了同樣的問題) – Robin 2012-07-20 18:44:27

    +0

    @Robin是的,這是我的想法。看看我發佈的答案。我只是尋找改進我的代碼或更好的方法。歡迎提出建議和評論。謝謝。 – 2012-07-20 18:48:14

    0

    所以這是我這麼遠,但一個主要問題是跨大號&˚F問題。另一種方法是遍歷ComboBox模型的所有值和「無選擇」值,並檢查哪一個是最長的。然後我可以將它設置爲prototypeDisplayValue。問題是我需要一個圖形上下文來衡量每個字符串的邊界。

    以下是我們用@Enwired和@Robin發現的兩種解決方案。感謝他們兩人。

    編輯:在與@羅賓討論後,我發現這個解決方案實際上更簡單,適用於所有平臺和外觀。唯一的缺點是我們需要創建一個額外的JComboBox。

    import java.awt.Component; 
    import java.awt.Dimension; 
    import java.awt.GridBagLayout; 
    
    import javax.swing.DefaultListCellRenderer; 
    import javax.swing.JComboBox; 
    import javax.swing.JFrame; 
    import javax.swing.JList; 
    import javax.swing.JPanel; 
    import javax.swing.SwingUtilities; 
    import javax.swing.UnsupportedLookAndFeelException; 
    
    public class TestComboBox { 
    
        protected void initUI() { 
         JFrame frame = new JFrame(TestComboBox.class.getSimpleName()); 
         frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
         JPanel panel = new JPanel(new GridBagLayout()); 
         JComboBox comboBox = new JComboBox(new Object[] { "Something", "Stuff", "Beep" }) { 
    
          private JComboBox internal; 
    
          private JComboBox getInternalComboBox() { 
           if (internal == null) { 
            internal = new JComboBox(new Object[] { null }); 
           } 
           return internal; 
          } 
    
          @Override 
          public Dimension getPreferredSize() { 
           Dimension preferredSize = super.getPreferredSize(); 
           if (getSelectedItem() == null) { 
            getInternalComboBox().setRenderer(getRenderer()); 
            Dimension nullDimension = getInternalComboBox().getPreferredSize(); 
            preferredSize.width = Math.max(preferredSize.width, nullDimension.width); 
            preferredSize.height = Math.max(preferredSize.height, nullDimension.height); 
           } 
           return preferredSize; 
          } 
    
          @Override 
          public void updateUI() { 
           internal = null; 
           super.updateUI(); 
          } 
         }; 
         comboBox.setRenderer(new DefaultListCellRenderer() { 
          @Override 
          public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { 
           Component comp = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); 
           if (value == null) { 
            setText("No selection"); 
           } 
           return comp; 
          } 
         }); 
         comboBox.setSelectedItem(null); 
         panel.add(comboBox); 
         frame.add(panel); 
         frame.setSize(200, 100); 
         frame.setVisible(true); 
        } 
    
        public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, 
          UnsupportedLookAndFeelException { 
         SwingUtilities.invokeLater(new Runnable() { 
    
          @Override 
          public void run() { 
           new TestComboBox().initUI(); 
          } 
         }); 
        } 
    } 
    

    編輯2:與@Enwired討論在此之後的替代解決方案提出了這是直接重寫ListCellRenderer的getPreferredSize。在這其中,你可以嘗試去通過不同得的L- & F.

    import java.awt.Component; 
    import java.awt.Dimension; 
    import java.awt.GridBagLayout; 
    import java.awt.event.ActionEvent; 
    import java.awt.event.ActionListener; 
    
    import javax.swing.DefaultListCellRenderer; 
    import javax.swing.JComboBox; 
    import javax.swing.JComponent; 
    import javax.swing.JFrame; 
    import javax.swing.JList; 
    import javax.swing.JPanel; 
    import javax.swing.SwingUtilities; 
    import javax.swing.UIManager; 
    import javax.swing.UIManager.LookAndFeelInfo; 
    import javax.swing.UnsupportedLookAndFeelException; 
    
    public class TestComboBox { 
    
        protected void initUI() { 
         final JFrame frame = new JFrame(TestComboBox.class.getSimpleName()); 
         frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
         JPanel panel = new JPanel(new GridBagLayout()); 
         JComboBox comboBox = new JComboBox(new Object[] { "Something", "Stuff", "Beep" }); 
         comboBox.setRenderer(new DefaultListCellRenderer() { 
    
          private Dimension nullDimesion; 
    
          @Override 
          public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { 
           if (value != null && nullDimesion == null) { 
            nullDimesion = ((JComponent) getListCellRendererComponent(list, null, -1, false, false)).getPreferredSize(); 
           } 
           Component comp = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); 
           if (value == null) { 
            setText("No selection"); 
           } 
           return comp; 
          } 
    
          @Override 
          public Dimension getPreferredSize() { 
           Dimension preferredSize = super.getPreferredSize(); 
           if (nullDimesion != null) { 
            preferredSize.width = Math.max(preferredSize.width, nullDimesion.width); 
            preferredSize.height = Math.max(preferredSize.height, nullDimesion.height); 
           } 
           return preferredSize; 
          } 
    
          @Override 
          public void updateUI() { 
           nullDimesion = null; 
           super.updateUI(); 
          } 
         }); 
         comboBox.setSelectedItem(null); 
         final JComboBox uiComboBox = new JComboBox(UIManager.getInstalledLookAndFeels()); 
         uiComboBox.setRenderer(new DefaultListCellRenderer() { 
          @Override 
          public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { 
           Component comp = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); 
           if (value instanceof LookAndFeelInfo) { 
            LookAndFeelInfo info = (LookAndFeelInfo) value; 
            setText(info.getName()); 
           } 
           return comp; 
          } 
         }); 
         uiComboBox.addActionListener(new ActionListener() { 
    
          @Override 
          public void actionPerformed(ActionEvent e) { 
           SwingUtilities.invokeLater(new Runnable() { 
            @Override 
            public void run() { 
             try { 
              UIManager.setLookAndFeel(((LookAndFeelInfo) uiComboBox.getSelectedItem()).getClassName()); 
              SwingUtilities.updateComponentTreeUI(frame); 
             } catch (ClassNotFoundException e1) { 
              // TODO Auto-generated catch block 
              e1.printStackTrace(); 
             } catch (InstantiationException e1) { 
              // TODO Auto-generated catch block 
              e1.printStackTrace(); 
             } catch (IllegalAccessException e1) { 
              // TODO Auto-generated catch block 
              e1.printStackTrace(); 
             } catch (UnsupportedLookAndFeelException e1) { 
              // TODO Auto-generated catch block 
              e1.printStackTrace(); 
             } 
            } 
           }); 
          } 
         }); 
         panel.add(comboBox); 
         panel.add(uiComboBox); 
         frame.add(panel); 
         frame.setSize(300, 100); 
         frame.setVisible(true); 
        } 
    
        public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, 
          UnsupportedLookAndFeelException { 
         SwingUtilities.invokeLater(new Runnable() { 
    
          @Override 
          public void run() { 
           new TestComboBox().initUI(); 
          } 
         }); 
        } 
    } 
    
    +0

    隨時評論或批評的方法,這就是我正在尋找;-) – 2012-07-20 18:46:35

    +0

    我已經在一個答案(太長的評論)放置一個可能的方法,但我必須強調我沒有測試這種方法,我是當然不是改變Swing組件的專家。只需熟悉使用它們 – Robin 2012-07-20 19:01:58

    +0

    在第二個示例中,在更改LAF的ActionListener中,您需要在調用invokeLater()'時包裝它。即使你已經在事件線程中,你也不希望LAF改變,直到組合框完成選擇。 (我通過玩這個例子發現了這種情況,使用第二個組合框上的向上/向下鍵給出了一個例外。) – Enwired 2012-07-20 23:16:11