如何在 JScrollPane 中显示底部组件?

How To Show The Bottom Component In A JScrollPane?

我有一个 Java Swing 应用程序,它有一个 JScrollPane,里面有一些组件 [多个 JPanels ]。这些 JPanel 是在单击 "New" JButton 之后创建的。我的目标是让 JScrollPane 滚动向下滚动到最后创建的 JPanel (即一直向下滚动)。我尝试了以下方法:

JScrollBar vertical = Scroll_Pane.getVerticalScrollBar();
vertical.setValue(vertical.getMaximum() + 40); 

但它没有用,一旦 JScrollPane 中至少有 3 个项目,最后创建的 JPanel 总是丢失。这是我的最小代码,如何解决这个问题?

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.border.*;
import java.io.File;
import java.util.*;
import javax.swing.event.*;

public class Items_Test_Panel extends JPanel
{
  public static final long serialVersionUID=26362862L;
  static Dimension Screen_Size=Toolkit.getDefaultToolkit().getScreenSize();
  static JFrame frame=new JFrame("Items_Test_Panel");
  JScrollPane Scroll_Pane;
  static int W=495,H=110,Max_H=110,Info_TextArea_H=500,Command_Info_Panel_Width=W-23,Command_Info_Panel_Height=33;
  int Item_Count=0;
  Color Title_Background_Color=new Color(150,206,236);
  Insets An_Inset=new Insets(0,0,0,0);
  JPanel Main_Panel;
  static String Dir_Data="C:/Dir_Data/",Current_Item_File_Path;
  static Process Child;
  boolean Show_Password_B=true;
  Vector<Command_Info> Command_Info_Vector;
  JTextArea Info_TextArea=new JTextArea();
  DocumentListener Command_Info_Field_Listener;
  Swing_Robot Robot=new Swing_Robot();
  ButtonGroup Item_Group=new ButtonGroup();                                    // Group the radio buttons.

  static
  {
    if (!new File(Dir_Data).exists()) new File(Dir_Data).mkdirs();
  }

  public Items_Test_Panel(int W,int H)
  {
    FlowLayout Main_Panel_FL=new FlowLayout();
    Main_Panel_FL.setHgap(2);
    Main_Panel_FL.setVgap(2);
    Max_H=H;
    Command_Info_Field_Listener=new DocumentListener()
    {
      public void removeUpdate(DocumentEvent e) { }
      public void insertUpdate(DocumentEvent e) { }
      public void changedUpdate(DocumentEvent e) { }
    };

    Main_Panel=new JPanel(Main_Panel_FL);
    Scroll_Pane=new JScrollPane(Main_Panel);
    Scroll_Pane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);

    JPanel Button_Panel=new JPanel(new FlowLayout(1,36,0));
    Button_Panel.setPreferredSize(new Dimension(Command_Info_Panel_Width+22,Command_Info_Panel_Height-6));
    add(Button_Panel);   

    JButton New_Button=new JButton("New");
    New_Button.setFont(new Font("Times New Roman",0,15));
    New_Button.setForeground(new Color(0,28,128));
    New_Button.setMargin(An_Inset);
    New_Button.setPreferredSize(new Dimension(56,26));
    New_Button.addActionListener(new ActionListener()
    {
      public void actionPerformed(ActionEvent evt)
      {
        Out("Command_Info_Vector.size() = "+Command_Info_Vector.size());
        Create_Command_Info_Panel(++Item_Count+1,null);
        revalidate();
      }
    });
    Button_Panel.add(New_Button);

    JPanel Title_Panel=new JPanel(new FlowLayout(0,1,1));
    add(Title_Panel);

    Title_Panel.setBorder(new EtchedBorder());
    Title_Panel.setPreferredSize(new Dimension(Command_Info_Panel_Width+22,Command_Info_Panel_Height-4));

    JLabel Id_Label=new JLabel(" # ");
    Id_Label.setFont(new Font("Times New Roman",0,15));
    Id_Label.setOpaque(true);
    Id_Label.setBackground(Title_Background_Color);
    Id_Label.setForeground(new Color(0,28,128));
    Id_Label.setHorizontalAlignment(SwingConstants.CENTER);
    Id_Label.setPreferredSize(new Dimension(69,22));
    Title_Panel.add(Id_Label);

    JLabel Command_Label=new JLabel("Result");
    Command_Label.setFont(new Font("Times New Roman",0,15));
    Command_Label.setOpaque(true);
    Command_Label.setBackground(Title_Background_Color);
    Command_Label.setForeground(new Color(0,28,128));
    Command_Label.setHorizontalAlignment(SwingConstants.CENTER);
    Command_Label.setPreferredSize(new Dimension(416,22));
    Title_Panel.add(Command_Label);

    add(Scroll_Pane);

    JScrollPane Info_TextArea_ScrollPane=new JScrollPane(Info_TextArea);
    Info_TextArea_ScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
    Info_TextArea_ScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
    Info_TextArea_ScrollPane.setPreferredSize(new Dimension(W-2,Info_TextArea_H));

    add(Info_TextArea_ScrollPane);

    Load_Data("");
  }

  void Create_Command_Info_Panel(int Id,final Command_Info A_Command_Info)
  {
    FlowLayout Panel_FL=new FlowLayout();
    Panel_FL.setHgap(1);
    Panel_FL.setVgap(1);
    JPanel Command_Info_Panel=new JPanel(Panel_FL);
    Command_Info_Panel.setBorder(new EtchedBorder());
    Command_Info_Panel.setPreferredSize(new Dimension(Command_Info_Panel_Width,Command_Info_Panel_Height));

    JRadioButton Id_Button=new JRadioButton("[ "+(Id==-1?Item_Count+1:Id)+" ]");
    Id_Button.setFont(new Font("Times New Roman",0,15));
    Id_Button.setForeground(new Color(0,28,128));
    Id_Button.setPreferredSize(new Dimension(65,26));
    Item_Group.add(Id_Button);
    Id_Button.setSelected(true);      
    Command_Info_Panel.add(Id_Button);

    JTextField Result_Field=new JTextField(A_Command_Info==null?"":A_Command_Info.Result);
    Result_Field.setPreferredSize(new Dimension(398,27));
    Result_Field.getDocument().addDocumentListener(Command_Info_Field_Listener);
    Command_Info_Panel.add(Result_Field);

    Main_Panel.add(Command_Info_Panel);

    if (A_Command_Info==null) Command_Info_Vector.add(new Command_Info("[ "+(Item_Count)+" ]","","",""));
    Update_Layout();
  }

  void Load_Data(String Items_Dir)
  {
    Command_Info A_Command_Info=null;

    Main_Panel.removeAll();
    Item_Count=0;
    Info_TextArea.setText("");
    Command_Info_Vector=new Vector();
    if (Command_Info_Vector.size()>0)
    {
      Item_Count=Command_Info_Vector.size();
      for (int i=0;i<Command_Info_Vector.size();i++)
      {
        A_Command_Info=Command_Info_Vector.elementAt(i);
        Create_Command_Info_Panel(i+1,A_Command_Info);
      }
      Item_Count--;
      Info_TextArea.setText(A_Command_Info.Info);
    }
    else Create_Command_Info_Panel(-1,null);

    Update_Layout();
  }

  void Update_Layout()
  {
    Main_Panel.setPreferredSize(new Dimension(Command_Info_Panel_Width,Item_Count*(Command_Info_Panel_Height+2)+36));
    if (Item_Count*(Command_Info_Panel_Height+2)+40<Max_H) Scroll_Pane.setPreferredSize(new Dimension(W-2,Item_Count*(Command_Info_Panel_Height+2)+40));
    else Scroll_Pane.setPreferredSize(new Dimension(W-2,Max_H));
    Scroll_Pane.revalidate();
    Scroll_Pane.repaint();
    revalidate();
    repaint();

    JScrollBar vertical=Scroll_Pane.getVerticalScrollBar();
    vertical.setValue(vertical.getMaximum()+40);
    Out(" vertical.getMaximum() = "+vertical.getMaximum()+"  vertical.getMinimum() = "+vertical.getMinimum());
/*
  Online advice of how to adjudt the Scroll_Pane : https://robbamforth.wordpress.com/2015/06/24/java-how-to-scroll-to-a-particular-component-in-jscrollpane-and-gain-focus/
    JPanel comp=(JPanel)Main_Panel.getComponent(Main_Panel.getComponentCount()-1);
//  vertical.setValue(Main_Panel.getParent().getLocation().y+(Main_Panel.getLocation().y+50));
//  JComponent comp=Main_Panel;
//  vertical.setValue(comp.getParent().getLocation().y+(comp.getLocation().y+50));
  vertical.setValue(250);
  comp.requestFocus();
Out(comp.toString());
    vertical.repaint();
    vertical.revalidate();
    */
    if (Item_Count*(Command_Info_Panel_Height+2)+40<Max_H) frame.setPreferredSize(new Dimension(W+17,Item_Count*(Command_Info_Panel_Height+2)+122+Command_Info_Panel_Height+Info_TextArea_H));
    else frame.setPreferredSize(new Dimension(W+17,Max_H+82+Command_Info_Panel_Height+Info_TextArea_H));
    frame.pack();
    frame.revalidate();
    frame.repaint();

  }

  private static void out(String message) { System.out.print(message); }
  private static void Out(String message) { System.out.println(message); }

  // Create the GUI and show it. For thread safety, this method should be invoked from the event-dispatching thread.
  static void Create_And_Show_GUI()
  {
    final Items_Test_Panel demo=new Items_Test_Panel(W,H);

    frame.add(demo);
    frame.addWindowListener( new WindowAdapter()
    {
      public void windowActivated(WindowEvent e) { }
      public void windowClosed(WindowEvent e) { }
      public void windowClosing(WindowEvent e)
      {
        System.exit(0);
        Child.destroy();
      }
      public void windowDeactivated(WindowEvent e)  { }
      public void windowDeiconified(WindowEvent e)  { demo.repaint(); }
      public void windowGainedFocus(WindowEvent e)  { demo.repaint(); }
      public void windowIconified(WindowEvent e)  { }
      public void windowLostFocus(WindowEvent e)  { }
      public void windowOpening(WindowEvent e) { demo.repaint(); }
      public void windowOpened(WindowEvent e)  { }
      public void windowResized(WindowEvent e) { demo.repaint(); }
      public void windowStateChanged(WindowEvent e) { demo.repaint(); }
    });
    frame.pack();
    frame.setLocationRelativeTo(null);
    frame.setVisible(true);
  }

  public static void main(String[] args)
  {
    // Schedule a job for the event-dispatching thread : creating and showing this application's GUI.
    SwingUtilities.invokeLater(new Runnable() { public void run() { Create_And_Show_GUI(); } });
  }
}

class Command_Info
{
  String Id,Date,Result,Info;

  Command_Info(String Id,String Date,String Result,String Info)
  {
    this.Id=Id;
    this.Date=Date;
    this.Result=Result;
    this.Info=Info;
  }

  public String toString() { return "Id = "+Id+"  Date = [ "+Date+" ]  Result = [ "+Result+" ]  Info = [ "+Info+" ]"; }
}

您在 JScrollBar 更新之前分配和更新它。这就是您落后一步的原因(最新的 JRadioButtonJTextField 未显示)。解决方法是先通过 frame.pack(); 更新您的组件,然后使用 vertical.setValue(...); 设置您的值。将您的 void Update_Layout() {...} 更改为以下内容:

void Update_Layout() {
        Main_Panel.setPreferredSize(new Dimension(Command_Info_Panel_Width, Item_Count * (Command_Info_Panel_Height + 2) + 36));
        if (Item_Count * (Command_Info_Panel_Height + 2) + 40 < Max_H) {
            Scroll_Pane.setPreferredSize(new Dimension(W - 2, Item_Count * (Command_Info_Panel_Height + 2) + 40));
        } else {
            Scroll_Pane.setPreferredSize(new Dimension(W - 2, Max_H));
        }

        Scroll_Pane.revalidate();
        Scroll_Pane.repaint();
        revalidate();
        repaint();
        Main_Panel.revalidate();
        Main_Panel.repaint();

        /*
  Online advice of how to adjudt the Scroll_Pane : https://robbamforth.wordpress.com/2015/06/24/java-how-to-scroll-to-a-particular-component-in-jscrollpane-and-gain-focus/
    JPanel comp=(JPanel)Main_Panel.getComponent(Main_Panel.getComponentCount()-1);
//  vertical.setValue(Main_Panel.getParent().getLocation().y+(Main_Panel.getLocation().y+50));
//  JComponent comp=Main_Panel;
//  vertical.setValue(comp.getParent().getLocation().y+(comp.getLocation().y+50));
  vertical.setValue(250);
  comp.requestFocus();
Out(comp.toString());
    vertical.repaint();
    vertical.revalidate();
         */
        if (Item_Count * (Command_Info_Panel_Height + 2) + 40 < Max_H) {
            frame.setPreferredSize(new Dimension(W + 17, Item_Count * (Command_Info_Panel_Height + 2) + 122 + Command_Info_Panel_Height + Info_TextArea_H));
        } else {
            frame.setPreferredSize(new Dimension(W + 17, Max_H + 82 + Command_Info_Panel_Height + Info_TextArea_H));
        }

        //HERE!
        frame.pack();
        JScrollBar vertical = Scroll_Pane.getVerticalScrollBar();
        vertical.setValue(vertical.getMaximum());
        Out(" vertical.getMaximum() = " + vertical.getMaximum() + "  vertical.getMinimum() = " + vertical.getMinimum());
        //frame.pack();//in case you want to pack again, not needed for your fix.
        frame.revalidate();
        frame.repaint();
    }