This question already has answers here:
Create a autocompleting textbox in Java with a dropdown list
(7 answers)
Closed 10 years ago.
Algorithm
Start
Input a city name - partial or complete
If the user hits enter , take the text from JTextField
Begin brute force search.
If the matches are found, put them in a Vector and put it in a JList
If no match is found, add a String "No Match Found" in Vector
Display JWindow to user containing the results
Stop
Code:
package test;
import javax.swing.*;
import java.awt.Dimension;
import java.awt.event.*;
import java.util.Vector;
public class AutoCompleteTest extends JFrame{
JTextField city = new JTextField(10);
String enteredName = null;
String[] cities = {"new jersey","new hampshire",
"sussex","essex","london","delhi","new york"};
JList list = new JList();
JScrollPane pane = new JScrollPane();
ResultWindow r = new ResultWindow();
//------------------------------------------------------------------------------
public static void main(String[] args) {
new AutoCompleteTest();
}
//------------------------------------------------------------------------------
public AutoCompleteTest(){
setLayout(new java.awt.FlowLayout());
setVisible(true);
add(city);
// add(pane);
pack();
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
city.addKeyListener(new TextHandler());
}
//------------------------------------------------------------------------------
public void initiateSearch(String lookFor){
Vector<String> matches = new Vector<>();
lookFor = lookFor.toLowerCase();
for(String each : cities){
if(each.contains(lookFor)){
matches.add(each);
System.out.println("Match: " + each);
}
}
this.repaint();
if(matches.size()!=0){
list.setListData(matches);
r.searchResult = list;
r.pane = pane;
r.initiateDisplay();
}else{
matches.add("No Match Found");
list.setListData(matches);
r.searchResult = list;
r.pane = pane;
r.initiateDisplay();
}
}
//------------------------------------------------------------------------------
public class ResultWindow extends JWindow{
public JScrollPane pane;
public JList searchResult;
//------------------------------------------------------------------------------
public ResultWindow(){
}
//------------------------------------------------------------------------------
public void initiateDisplay(){
pane.setViewportView(searchResult);
add(pane);
pack();
this.setLocation(AutoCompleteTest.this.getX() + 2,
AutoCompleteTest.this.getY()+
AutoCompleteTest.this.getHeight());
// this.setPreferredSize(city.getPreferredSize());
this.setVisible(true);
}
}
//------------------------------------------------------------------------------
class TextHandler implements KeyListener{
#Override
public void keyTyped(KeyEvent e){
}
#Override
public void keyPressed(KeyEvent e){
if(r.isVisible()){
r.setVisible(false);
}
if(e.getKeyChar() == '\n'){
initiateSearch(city.getText());
}
}
#Override
public void keyReleased(KeyEvent e){
}
}
//------------------------------------------------------------------------------
}
Output
Problem
The size of the JWindow displaying the results (which is a JList in a JScrollPane) changes based on the results - if the city name is small, JWindow is small, if the city name is big, JWindow is big.
I have tried every possible combination. I tried using setPreferredDimension() of the JWindow, the JList and JScrollPane but the issue won't go.
I want it to match the size of the decorated JFrame no matter what
JList or JComboBox doesn't returns proper PreferredSize, have to set this value, use JList.setPrototypeCellValue() with pack() for JWindow (must be packed after any changes) and or with JList.setVisibleRowCount(), then value returns getPreferredScrollableViewportSize() for JList in JScrollPane
don't to use KeyListener, use DocumentListener (chars can be inserted from system clipboard) for JTextComponents
don't to reinvent the wheel, use AutoComplete JComboBox / JTextField, you can to redirect / returns result from matches to the popup JWindow / undecorated JDialog(quite the best workaround for popup recycle)
EDIT
Anyways so basically I will have to manually create a list of all the
cities that are to be supported right ?? bx #Little Child
this idea could be quite easy, you can to put JTable to the JWindow
with one Column,
without JTableHeader
add there RowSorter (see code example in tutorial)
then every steps are done :-), nothing else is required there (maybe bonus to change Background of JTextField in the case that RowFilter returns no matches, add setVisible for popup window from DocumentListener (be sure to test for !isVisible))
You should use JComboBox, and for the autocompletion, read this article.
You need to use the width of the the JFrame every time you initiate the search and use it calculate the width of the list.
Just change the initiateSearch() function like this:
public void initiateSearch(String lookFor){
//add the following two statements to set the width of the list.
int newWidth = AutoCompleteTest.this.getSize().width;
list.setPreferredSize(new Dimension(newWidth, list.getPreferredSize().height));
Vector<String> matches = new Vector<String>();
lookFor = lookFor.toLowerCase();
for(String each : cities){
if(each.contains(lookFor)){
matches.add(each);
System.out.println("Match: " + each);
}
}
this.repaint();
if(matches.size()!=0){
list.setListData(matches);
r.searchResult = list;
r.pane = pane;
r.initiateDisplay();
}else{
matches.add("No Match Found");
list.setListData(matches);
r.searchResult = list;
r.pane = pane;
r.initiateDisplay();
}
}
Here is a sample output:
and
PS: Just for better aesthetics try using some layout to make the text field fill the entire width.
One solution would be to change initiateDisplay() to this:
public void initiateDisplay()
{
this.pane.setViewportView(this.searchResult);
this.add(this.pane);
this.pack();
this.setLocation(AutoCompleteTest.this.getX() + 2, AutoCompleteTest.this.getY()
+ AutoCompleteTest.this.getHeight());
int padding = 5;
int height = this.searchResult.getModel().getSize()
* AutoCompleteTest.this.city.getSize().height;
int windowWidth = AutoCompleteTest.this.getSize().width;
this.setSize(windowWidth, height);
this.setVisible(true);
}
Related
I'm trying to create a personality quiz. The idea is that a JPanel will show up with the first question and then once the user selects one of the radio buttons, the second JPanel with the second question will show up.
Since I have 5 questions each with 3 answer choices I thought it would be faster and more efficient to create a method that creates radio buttons and adds an ActionListener but I'm having trouble getting the listener to work. Right now to see if it works I'm just trying to change the button text when it is selected.
I tried adding the listener to the button in the createButton method but I haven't had luck. Originally I had it as a parameter in the method but that didn't get the expected result so I tried to create it without the listener as a parameter. Inserting the listener as a parameter didn't change the text.
import java.awt.Container;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.BoxLayout;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.WindowConstants;
public class UserInterface extends ClickListener implements Runnable
{
private ActionListener listeners;
private JFrame frame;
public UserInterface(){
}
#Override
public void run() {
frame = new JFrame("title");
frame.setPreferredSize(new Dimension(1000, 1000));
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
createComponents(frame.getContentPane());
frame.pack();
frame.setVisible(true);
}
public JFrame getFrame(){
return frame;
}
private void createComponents(Container container){
BoxLayout layout = new BoxLayout(container, BoxLayout.Y_AXIS);
container.setLayout(layout);
container.add(QuizIntro());
container.add(QuestionOne());
container.add(QuestionOneGroup());
}
public JLabel QuizIntro(){
JLabel text = new JLabel("Intro text");
return text;
}
public JLabel QuestionOne(){
JLabel text = new JLabel("1. this is the first question");
return text;
}
public JPanel QuestionOneGroup(){
JRadioButton int1 = createButton("This button was created with my createButton method");
JRadioButton e1 = new JRadioButton("This button was created without that method");
JPanel panel = new JPanel();
panel.add(int1);
panel.add(e1);
return panel;
}
public JRadioButton createButton(String text){
JRadioButton b = new JRadioButton(text);
b.addActionListener(listeners);
return b;
}
}
here's my action listener
public class ClickListener implements ActionListener {
private UserInterface ui;
private JRadioButton b;
#Override
public void actionPerformed(ActionEvent ae) {
if (b.isSelected()){
b.setText("this works");
}
}
}
The actual result is that the button is selected but the text does not change. I'm having trouble figuring out if I'm running the wrong test to see if my listener works or if my listener just does not work.
Your main problem is here:
private ActionListener listeners;
You create an ActionListener but you never call it.
You're also inheriting from ClickListener but never using it.
public class UserInterface extends ClickListener implements Runnable
In your code you call:
b.addActionListener(listeners); //Listeners is null!
So, you're telling your JRadioButton to have a listener that is null.
So, here you have 2 approaches:
Remove private ActionListener listeners; from the top of your program and change:
b.addActionListener(listeners);
To:
b.addActionListener(this);
Remove extends ClickListener from your class definition and keep:
b.addActionListener(listeners);
But adding
listeners = new ClickListener();
Before
createComponents(frame.getContentPane());
IMO I would take the 2nd approach.
BTW for your ActionListener to actually change the text you don't need private variables but rather get the source and cast it. For example:
public void actionPerformed(ActionEvent ae) {
JRadioButton b = (JRadioButton) ae.getSource();
b.setText("this works");
}
Forgot to mention, please follow Java naming conventions so that your program is more readable and understandable by everyone who reads it.
firstWordLowerCaseVariable
firstWordLowerCaseMethod()
FirstWordUpperCaseClass
ALL_WORDS_UPPER_CASE_CONSTANT
And indent your code correctly as well :)
First of all the GUI should not extend the listener class. Not good. Better to keep them separate and to pass references where needed. For example, if the listener needs a reference to the GUI, pass it in as a paramter
Also, you apparently want to do is to respond to JRadioButton selection immediately with useful behavior. I would use an ItemListener not an ActionListener, since the ItemListener will tell you if the radio button has been selected. Calling getSource() on the ItemEvent will give you the current JRadioButton that was selected.
e.g.,
import java.awt.BorderLayout;
import java.awt.CardLayout;
import java.awt.Color;
import java.awt.GridLayout;
import java.awt.event.*;
import java.util.ArrayList;
import java.util.List;
import javax.swing.*;
#SuppressWarnings("serial")
public class UserInterface2 extends JPanel {
public static final Object QUESTION = "question";
// fill label with blank text to expand it
private JLabel resultLabel = new JLabel(String.format("%150s", " "));
// CardLayout to allow swapping of question panels
private CardLayout cardLayout = new CardLayout();
private JPanel centerPanel = new JPanel(cardLayout);
public UserInterface2(List<Question> questions) {
centerPanel.setBorder(BorderFactory.createLineBorder(Color.BLUE));
for (Question question : questions) {
centerPanel.add(createQPanel(question), question.getQuestion());
}
JPanel bottomPanel = new JPanel(new BorderLayout());
// add button that allows swapping question panels
bottomPanel.add(new JButton(new AbstractAction("Next") {
#Override
public void actionPerformed(ActionEvent e) {
cardLayout.next(centerPanel);
}
}), BorderLayout.LINE_START);
bottomPanel.add(resultLabel);
setLayout(new BorderLayout());
add(bottomPanel, BorderLayout.PAGE_END);
add(centerPanel);
}
private JPanel createQPanel(Question question) {
JPanel radioPanel = new JPanel(new GridLayout(0, 1));
radioPanel.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));
ButtonGroup buttonGroup = new ButtonGroup();
ItemListener myItemListener = new MyItemListener(this);
for (String answer : question.getAnswers()) {
JRadioButton answerButton = new JRadioButton(answer);
// this is present in case you want to extract the Question
// object from the JRadioButton, useful for if you want to
// test if the selected answer is correct
answerButton.putClientProperty(QUESTION, question);
// add our listener to the JRadioButton
answerButton.addItemListener(myItemListener);
// add to button group so only one can be selected
buttonGroup.add(answerButton);
// add to JPanel for display
radioPanel.add(answerButton);
}
JPanel qPanel = new JPanel(new BorderLayout());
qPanel.add(new JLabel(question.getQuestion()), BorderLayout.PAGE_START);
qPanel.add(radioPanel);
return qPanel;
}
// public method that the item listener will use to display selection
public void displayResult(String selectedText) {
resultLabel.setText(selectedText);
}
private static void createAndShowGui() {
// create mock questions
// likely this information will be in a text file
List<Question> questions = new ArrayList<>();
for (int i = 0; i < 10; i++) {
String question = "Question " + i;
List<String> answers = new ArrayList<>();
for (int j = 0; j < 4; j++) {
answers.add(String.format("Answer [%d %d]", i, j));
}
int correctIndex = (int) (Math.random() * answers.size());
// future iteration will also need correctIndex int
questions.add(new Question(question, answers, correctIndex));
}
UserInterface2 mainPanel = new UserInterface2(questions);
JFrame frame = new JFrame("User Interface");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> createAndShowGui());
}
}
class MyItemListener implements ItemListener {
private UserInterface2 ui;
public MyItemListener(UserInterface2 ui) {
this.ui = ui;
}
#Override
public void itemStateChanged(ItemEvent e) {
JRadioButton source = (JRadioButton) e.getSource();
String selected = "The JRadioButton " + source.getText();
selected += e.getStateChange() == ItemEvent.SELECTED ? " has been selected"
: " has been unselected";
// to get the actual Question object get the client property:
Question question = (Question) source.getClientProperty(UserInterface2.QUESTION);
// now can get answer Strings and check if correct one selected
System.out.println(question);
String correctAnswer = question.getAnswers().get(question.getCorrectIndex());;
if (source.getText().equals(correctAnswer)) {
selected += " and is correct";
} else {
selected += " and is incorrect";
}
// tell the GUI to display the result
ui.displayResult(selected);
}
}
class Question {
private String question;
private List<String> answers;
private int correctIndex;
public Question(String question, List<String> answers, int correctIndex) {
this.question = question;
this.answers = answers;
this.correctIndex = correctIndex;
}
public String getQuestion() {
return question;
}
public List<String> getAnswers() {
return answers;
}
public int getCorrectIndex() {
return correctIndex;
}
#Override
public String toString() {
return "Question [question=" + question + ", correctIndex="
+ correctIndex + "]";
}
}
Since there are many things to be improved in your code, I thought I'll write a sample program to show you how this kind of a thing should be done in Swing. Try it and see.
(There are few things we can improve in this sample code as well. But I just wanted to keep things simple and address only the key points.)
import javax.swing.*;
import java.awt.event.*;
public class Questions {
public static void main(String[] args) {
JFrame f = new JFrame("Questions");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.getContentPane().add(new QuestionPanel());
f.setBounds(300, 200, 400, 300);
f.setVisible(true);
}
}
class QuestionPanel extends JPanel implements ActionListener {
private static final String ANSWER_1_TEXT = "Answer 1";
private static final String ANSWER_2_TEXT = "Answer 2";
private static final String ANSWER_3_TEXT = "Answer 3";
private JRadioButton answer1;
private JRadioButton answer2;
private JRadioButton answer3;
QuestionPanel() {
answer1 = new JRadioButton(ANSWER_1_TEXT);
answer2 = new JRadioButton(ANSWER_2_TEXT);
answer3 = new JRadioButton(ANSWER_3_TEXT);
answer1.addActionListener(this);
answer2.addActionListener(this);
answer3.addActionListener(this);
ButtonGroup buttonGroup = new ButtonGroup();
buttonGroup.add(answer1);
buttonGroup.add(answer2);
buttonGroup.add(answer3);
setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
add(answer1);
add(answer2);
add(answer3);
}
#Override
public void actionPerformed(ActionEvent e) {
JRadioButton selectedAnswer = (JRadioButton) e.getSource();
if (selectedAnswer == answer1) {
answer1.setText(ANSWER_1_TEXT + " (selected)");
answer2.setText(ANSWER_2_TEXT);
answer3.setText(ANSWER_3_TEXT);
}
else if (selectedAnswer == answer2) {
answer1.setText(ANSWER_1_TEXT);
answer2.setText(ANSWER_2_TEXT + " (selected)");
answer3.setText(ANSWER_3_TEXT);
}
else if (selectedAnswer == answer3) {
answer1.setText(ANSWER_1_TEXT);
answer2.setText(ANSWER_2_TEXT);
answer3.setText(ANSWER_3_TEXT + " (selected)");
}
}
}
How many programmers do you need to change a lightbulb? 76, 1 to change it and 75 to say how they could've done it better.
You do have bad code practices, but thats usually because of a shaky understanding of the fundamental concepts behind the language design. So I won't comment on how your code is bad for this or that, just explain the basics you should know.
To simplify, an ActionListener is an object that will react on an ActionPerformedEvent. Lets define one, a class called Observer:
Observer has no idea who generated the event, so lets tell him, and if it is a JRadioButton, lets use it as one
public class Observer implements ActionListener{
#Override
public void ActionPerformed(ActionEvent ae){
Object source = ae.getSource();
if (source instanceof JRadioButton)
((JRadioButton) source).setText("this works");
}
Any of your JRadioButton are objects that generates ActionEvents constantly, but nobody we care about is usually watching, when we add an ActionListener we are basically saying: make this ActionListener object observe my behaviour.
So we need someone to be observed and an observer, lets make those in your UI (or a simplified version of it):
Since you are trying to use a global listener, you need to make sure there is one there, observer (our ActionListener) is null for now. Lets instantiate it This time, we know that observer is not null
public class UserInterface implements runnable{
private ActionListener observer new Observer();
//...
public void someMethodToCreateButtons(){
JRadioButton observableButton = new JRadioButton("Created here");
observableButton.addActionListener(observer);
}
And thats it, when you select observableButton, it will change its text to "this works".
Those are the basics, now, I used those names observableButton and Observer for a reason, ActionListeners are based on what is known as the observer design pattern you could pick up a book regarding designs patterns, you wont regret it.
Finally, it seems that you are making things too difficult for yourself, try to simplify your logic. Perhaps making a JPanel containing different sets of buttons and displaying it whenever a condition is true? just spitballing here, but try to keep it simple, hope it helps, good luck!
I have a JTable for which the renderer returns a JPanel composed of multiple JLabel instances. One of those JLabels can contain HTML used among other things to split the output over multiple lines using <br/> tags.
To show the multiple lines in the table, the renderer calls in the getTableCellRendererComponent method
table.setRowHeight(row, componentToReturn.getPreferredSize().height);
to dynamically update the row height, based on the contents. This only works correctly if componentToReturn indicates a correct preferred size.
It looks however that the getPreferredSize returns bogus values. The preferred height of the returned component is smaller than the sum of the heights of the labels inside the component.
Here is a little program illustrating this behaviour (without using a JTable)
import java.awt.*;
import javax.swing.*;
public class SwingLabelTest {
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
LabelPanel renderer = new LabelPanel();
Component component = renderer.getComponent(false);
//asking for a bigger component will not
//update the preferred size of the returned component
component = renderer.getComponent(true);
}
});
}
private static class LabelPanel {
private final JPanel compositePanel;
private final JLabel titleLabel = new JLabel();
private final JLabel propertyLabel = new JLabel();
public LabelPanel() {
JPanel labelPanel = new JPanel();
labelPanel.setLayout(new BoxLayout(labelPanel, BoxLayout.PAGE_AXIS));
labelPanel.add(titleLabel);
labelPanel.add(propertyLabel);
compositePanel = new JPanel(new BorderLayout());
//normally it contains more components,
//but that is not needed to illustrate the problem
compositePanel.add(labelPanel, BorderLayout.CENTER);
}
public Component getComponent( boolean aMultiLineProperty ) {
titleLabel.setText("Title");
if ( aMultiLineProperty ){
propertyLabel.setText("<html>First line<br/>Property: value</html>");
} else {
propertyLabel.setText("Property: value");
}
int titleLabelHeight = titleLabel.getPreferredSize().height;
int propertyLabelHeight = propertyLabel.getPreferredSize().height;
int compositePanelHeight = compositePanel.getPreferredSize().height;
if ( compositePanelHeight < titleLabelHeight + propertyLabelHeight){
throw new RuntimeException("Preferred size of the component returned "
+ "by the renderer is incorrect");
}
return compositePanel;
}
}
}
As I am aware that the previous example is a bit far-fetched, here an example which includes a JTable
import java.awt.*;
import javax.swing.*;
import javax.swing.table.*;
public class SwingTableTest {
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
DefaultTableModel tableModel = new DefaultTableModel(0, 1);
JTable table = new JTable(tableModel);
table.setDefaultRenderer(Object.class, new DataResultRenderer());
tableModel.addRow(new Object[]{new Object()});
tableModel.addRow(new Object[]{new Object()});
tableModel.addRow(new Object[]{new Object()});
JFrame testFrame = new JFrame("TestFrame");
testFrame.getContentPane().add(new JScrollPane(table));
testFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
testFrame.setSize(new Dimension(300, testFrame.getPreferredSize().height));
testFrame.setVisible(true);
}
});
}
private static class DataResultRenderer implements TableCellRenderer {
private final JPanel compositePanel;
private final JLabel titleLabel = new JLabel();
private final JLabel propertyLabel = new JLabel();
public DataResultRenderer() {
JPanel labelPanel = new JPanel();
labelPanel.setOpaque(false);
labelPanel.setLayout(new BoxLayout(labelPanel, BoxLayout.PAGE_AXIS));
labelPanel.add(titleLabel);
labelPanel.add(propertyLabel);
compositePanel = new JPanel(new BorderLayout());
//normally it contains more components,
//but that is not needed to illustrate the problem
compositePanel.add(labelPanel, BorderLayout.CENTER);
}
#Override
public Component getTableCellRendererComponent(
JTable table, Object value, boolean isSelected,
boolean hasFocus, int row, int column) {
titleLabel.setText("Title");
if ( row == 2 ){
propertyLabel.setText("<html>Single property: value</html>");
} else {
String text = "<html>";
text += "First property<br/>";
text += "Second property<br/>";
text += "Third property:value";
text += "</html>";
propertyLabel.setText(text);
}
int titleLabelHeight = titleLabel.getPreferredSize().height;
int propertyLabelHeight = propertyLabel.getPreferredSize().height;
int compositePanelHeight = compositePanel.getPreferredSize().height;
if ( compositePanelHeight < titleLabelHeight + propertyLabelHeight){
throw new RuntimeException("Preferred size of the component returned "
+ "by the renderer is incorrect");
}
table.setRowHeight(row, compositePanel.getPreferredSize().height);
return compositePanel;
}
}
}
I am looking for a way to update the row height of the table to ensure that the multi-line content is completely visible, without knowing up front how many lines each row will contain.
So either I need a solution to retrieve the correct preferred size, or my approach is completely wrong and then I need a better one.
Note that the above examples are simplified. In the real code, the "renderer" (the code responsible for creating the component) is decorated a few times. This means that the outer renderer is the only with access to the JTable, and it has no knowledge about what kind of Component the inner code returns.
Because setRowHeight() "Sets the height, in pixels, of all cells to rowHeight, revalidates, and repaints," the approach is unsound. Absent throwing an exception, profiling shows 100% CPU usage as an endless cascade of repaints tries to change the row height repeatedly. Moreover, row selection becomes unreliable.
Some alternatives include these:
Use TablePopupEditor to display multi-line content on request from a TableCellEditor.
Update an adjacent multi-line panel from a TableModelListener, as shown here.
Very new to Java, but I am slowly picking my way through things. So please be kind. I understand most things I've tried so far, and built a version of the following that uses console output, but now I'm trying to make a GUI. I tried the netbeans GUI maker, but it created so much new code that when I tried to pick through it, I got lost. I'm much better at learning by piecing new things together myself, not having an IDE generate a ton of code and then attempt to find where I want to work.
I am trying to build an window that has a list with three choices on the left side, a button in the middle that confirms your choice, and an answer output on the right. Once the button is pressed, the input from the list is read and is converted into a corresponding answer. As of right now, all I get is "We recommend... null" after selecting an option in the list. The button appears to do nothing at the moment.
I have used tutorials, hacked up others' code from online, and referenced a few books, but I'm stuck.
Here is what I have:
package diffguidegui;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
public class DiffGuideGUI extends JPanel implements ListSelectionListener {
private JList resultsTabList;
private DefaultListModel listModel;
private static final String recommendString = "Recommend a Option";
private JButton recommendButton;
private String recommendOutput;
final JLabel output = new JLabel("We recommend..." + recommendOutput);
//build list
public DiffGuideGUI () {
super(new BorderLayout());
listModel = new DefaultListModel();
listModel.addElement("A");
listModel.addElement("B");
//create the list and put it in the scroll pane
resultsTabList = new JList(listModel);
resultsTabList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
resultsTabList.setSelectedIndex(0);
//listener for user input
resultsTabList.addListSelectionListener(this);
resultsTabList.setVisibleRowCount(2);
JScrollPane listScrollPane = new JScrollPane(resultsTabList);
//build the button at the bottom to fire overall behavior
recommendButton = new JButton(recommendString);
recommendButton.setActionCommand(recommendString);
recommendButton.addActionListener(new RecommendListener());
//create a panel that uses Boxlayout for the button
JPanel buttonPane = new JPanel();
buttonPane.setLayout(new BoxLayout(buttonPane, BoxLayout.LINE_AXIS));
buttonPane.add(recommendButton);
//create a panel that uses Boxlayout for the label
JPanel outputPane = new JPanel();
outputPane.setLayout(new BoxLayout(outputPane, BoxLayout.LINE_AXIS));
outputPane.add(output);
add(listScrollPane, BorderLayout.WEST);
add(buttonPane, BorderLayout.CENTER);
add(outputPane, BorderLayout.EAST);
}
//build listener class
class RecommendListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
//build in logic for choice made here
String resultsTabChoice;
resultsTabChoice = (String)resultsTabList.getSelectedValue();
if( resultsTabChoice.equals("A")) {
recommendOutput = "One";}
else {recommendOutput = "Two";}
}
}
public void valueChanged(ListSelectionEvent e) {
if(e.getValueIsAdjusting() == false) {
if(resultsTabList.getSelectedIndex() == -1) {
recommendButton.setEnabled(false);
} else {
recommendButton.setEnabled(true);
}
}
}
//Create GUI and show it
private static void createAndShowGUI() {
JFrame frame = new JFrame("Recommend Window");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//create and set up content pane
JComponent newContentPane = new DiffGuideGUI();
newContentPane.setOpaque(true);
frame.setContentPane(newContentPane);
//display the window
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args) {
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGUI();
}
});
}
}
The button appears to do nothing at the moment.
It does something. It calculates the value for your recommendOutput varable. But you never output this value.
try the following:
//build listener class
class RecommendListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
//build in logic for choice made here
String resultsTabChoice;
resultsTabChoice = (String)resultsTabList.getSelectedValue();
if( resultsTabChoice.equals("A")) {
recommendOutput = "One";}
else {recommendOutput = "Two";}
System.out.println(recommendOutput); // <-###################
}
}
This should print the value to stdout
To put the value into your label try this instead:
output.setText(recommendOutput);
where do you set the text for the JLabel? It says "We recommend NULL" because recommenedOutput is null when the object is created. I dont see
output.setText("We recommend "+value) anywhere. You probably need output.invalidate() also. Try putting setText(String text)/invalidate() in the RecommendListener.actionPerformed() method.
output.setText("We recommend A");
output.invalidate();
edit: now solved, but can't mark as accepted for two days
In my class I have a JScrollPanel and that has a JPanel inside of it too.
My code resembles something like this:
import java.awt.Color;
import java.awt.Container;
import javax.swing.*;
public class MyClass {
private JPanel p;
private JScrollPane s;
private Container contentPane;
public MyClass(Container contentPane) {
this.contentPane = contentPane;
this.p = new JPanel();
this.p.setBackground(Color.WHITE);
BoxLayout boxLayout = new BoxLayout(this.p, BoxLayout.Y_AXIS);
this.p.setLayout(boxLayout);
this.s = new JScrollPane(this.p);
this.s.setSize(400, 364);
this.contentPane.add(this.s);
}
public final JLabel makeJLabel(String message) {
JLabel jLabel = new JLabel("<html><p style=\"padding-left:15px;padding-right:15px;width:280px;\">" + message.replaceAll("(\r\n|\n)", "<br />") + "</p></html>");
/*
some stuff here to calculate pref/max size and add an imageicon
*/
p.add(jLabel);
this.p.revalidate();
this.s.revalidate(); //just added because the above line made no effect
scrollToBottom();
return jLabel;
}
public void scrollToBottom() {
JScrollBar vertical = s.getVerticalScrollBar();
vertical.setValue(vertical.getMaximum());
}
}
Elsewhere in my class I have a method which adds a JLabel to the JPanel. This actual method is quite long, so I wont post it all, but this is the code which adds it to the panel: p.add(jLabel1);
All of these JLabels are added in a vertical fashion thanks to the Box Layout.
After the JLabel has been added to the JPanel I want the JScrollPane to scroll to the bottom. But this can't be done until after the JPanel has actually been drawn (painted?) onto JPanel. Otherwise I get this result:
So what I want to do is add some form of listener to the JPanel which detects when my JLabel has been painted to it, so that I can tell my JScrollPane to scroll to the bottom. I have already written a method which scrolls the pane to the bottom, but I don't have anywhere suitable to call it from yet.
Does anyone have any ideas on this please? Thanks.
I'm assuming you just want the label to be visible in the scrollpane so I would gues you should be able to do something like:
panel.add( label );
panel.revalidate();
label.scrollRectToVisible( label.getBounds() );
Or if you really do want to just scroll bo the bottom then you would do something like:
panel.revalidate();
scrollPane.getVerticalScrollBar().setValue( getVerticalScrollBar().getMaximum() );
Both of these answers assume the GUI is already visible.
The first part of Rob's answer is the way to go - the missing piece is to wrap the scrollRectToVisible into SwingUtilities.invokeLater. Doing so delays the scrolling until all pending events are processed, that is until all internal state is updated. A code snippet (in swingx test support speak, simply replace the frame creation and scrollpane wrapping with manually created code)
final JComponent panel = new JPanel();
panel.setLayout(new BoxLayout(panel, BoxLayout.PAGE_AXIS));
for(int i = 0; i < 10; i++)
panel.add(new JLabel("initial message " + i));
JXFrame frame = wrapWithScrollingInFrame(panel, "scroll to bottom");
Action action = new AbstractAction("addMessage") {
int count;
#Override
public void actionPerformed(ActionEvent e) {
final JLabel label = new JLabel("added message " + count++);
panel.add(label);
panel.revalidate();
SwingUtilities.invokeLater(new Runnable() {
public void run() {
label.scrollRectToVisible(label.getBounds());
}
});
}
};
frame.add(new JButton(action), BorderLayout.SOUTH);
show(frame);
Scratched my head for ages over this, but after asking the question I finally figured it out.
To solve my problem all I need to do was listen for a change in value in the JScrollPane's scroll bar. If it changed, done some calculations, and scroll to the bottom if necessary.
Care has to be taken to ensure that you're not overriding the user moving the scroll bar however.
In particular you're looking at the track which is an AdjustmentEvent. This event is also fired when a user moves the scroll bar.
In order to allow the user to scroll without forcibly scroll it to the bottom, I always keep track of the maximum scroll bar value. If when track is fired the new max value is higher than the current one then a new item has been added and we should think about scrolling to the bottom. If the values are equal then the user is scrolling the scroll bar and we do nothing.
The event listeners can be found on this website and can be make to work very easily: Listening for Scrollbar Value Changes in a JScrollPane Container
import java.awt.Color;
import java.awt.Container;
import java.awt.event.AdjustmentEvent;
import java.awt.event.AdjustmentListener;
import javax.swing.*;
public class MyClass {
private JPanel p;
private JScrollPane s;
private Container contentPane;
private int scrollBarMax;
public MyClass(Container contentPane) {
this.contentPane = contentPane;
this.p = new JPanel();
this.p.setBackground(Color.WHITE);
BoxLayout boxLayout = new BoxLayout(this.p, BoxLayout.Y_AXIS);
this.p.setLayout(boxLayout);
this.s = new JScrollPane(this.p);
this.s.setSize(400, 364);
this.contentPane.add(this.s);
this.s.getVerticalScrollBar().addAdjustmentListener(new AdjustmentListener(){
#Override
public void adjustmentValueChanged(AdjustmentEvent evt) {
if (evt.getValueIsAdjusting()) {
return;
}
if (evt.getAdjustmentType() == AdjustmentEvent.TRACK) {
if (scrollBarMax < s.getVerticalScrollBar().getMaximum()) {
if ((s.getVerticalScrollBar().getValue() + s.getVerticalScrollBar().getSize().height) == scrollBarMax) {
//scroll bar is at the bottom, show the last added JLabel
scrollBarMax = s.getVerticalScrollBar().getMaximum();
scrollToBottom();
} else {
//scroll bar is not at the bottom, user has moved it
scrollBarMax = s.getVerticalScrollBar().getMaximum();
}
}
}
}
});
scrollBarMax = s.getVerticalScrollBar().getMaximum();
}
public final JLabel makeJLabel(String message) {
JLabel jLabel = new JLabel("<html><p style=\"padding-left:15px;padding-right:15px;width:280px;\">" + message.replaceAll("(\r\n|\n)", "<br />") + "</p></html>");
/*
some stuff here to calculate pref/max size and add an imageicon
*/
p.add(jLabel);
scrollBarMax = s.getVerticalScrollBar().getMaximum();
return jLabel;
}
public void scrollToBottom() {
JScrollBar vertical = s.getVerticalScrollBar();
vertical.setValue(vertical.getMaximum());
}
}
I am working on a viewer, which uses a JList to show thumbnails of the pages of a document. The user can open a page by selecting it through in the JList, or throught other mechanisms, like entering the number in a text box.
When using the latter alternative, I want that the JList also selects the page. I do this using setSelectedIndex(), but this triggers an event, which causes the page to be loaded again, as if the user had clicked and selected the page in the JList, and this is causing me some problems.
How I see it, the index should be set some way (perhaps in the model) so that only the UI of the JList updates, without firing an event that the index has changed.
Is this possible? Or is there a better way to solve my issue?
You can remove all ListSelectionListener from the list, make a selection and then add them again.
You can create your own ListSelectionModel with a method that doesn't throw the event and set it as a selection model to your JList, and then use getSelectionModel().yourSelectIndexMethod(index).
You can also divert all your other methods of selection to the list, just find the corresponding entry if selecting the page by other means and select the item in the list. This way the item is selected and the page is loaded once.
Code for option 2:
public class ListTest extends JPanel{
private static final String[] items = new String[]{"1", "2", "3"};
private JList mylist;
private JComboBox myCombo;
private JTextArea myTA;
public ListTest() {
setLayout(new BorderLayout());
myCombo = new JComboBox(items);
myCombo.addActionListener(new ActionListener(){
#Override
public void actionPerformed(ActionEvent e){
valueSelectedCombo(myCombo.getSelectedIndex());
}
});
JPanel pn = new JPanel();
pn.setLayout(new BoxLayout(pn, BoxLayout.X_AXIS));
pn.add(myCombo);
pn.add(Box.createHorizontalGlue());
pn.add(new JButton(new AbstractAction("Clear"){
#Override
public void actionPerformed(ActionEvent e){
myTA.setText("");
}
}));
add(pn, BorderLayout.NORTH);
add(new JScrollPane(getJList()), BorderLayout.WEST);
add(new JScrollPane(myTA = new JTextArea()), BorderLayout.CENTER);
}
private void valueSelectedList(int index){
myTA.setText(myTA.getText() + "\n" + items[index]);
}
private void valueSelectedCombo(int index){
myTA.setText(myTA.getText() + "\n" + items[index]);
((CustomSelectionModel)mylist.getSelectionModel()).setSelectionSilent(index);
}
private JList getJList(){
if (mylist == null){
mylist = new JList(items);
mylist.setSelectionModel(new CustomSelectionModel());
mylist.addListSelectionListener(new ListSelectionListener(){
#Override
public void valueChanged(ListSelectionEvent e){
if (!e.getValueIsAdjusting()){
valueSelectedList(mylist.getSelectedIndex());
}
}
});
mylist.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
mylist.setPreferredSize(new Dimension(100, 106));
}
return mylist;
}
private static class CustomSelectionModel extends DefaultListSelectionModel{
private boolean isSilent = false;
public void setSelectionSilent(int firstIndex){
isSilent = true;
setSelectionInterval(firstIndex, firstIndex);
isSilent = false;
}
protected void fireValueChanged(int firstIndex, int lastIndex, boolean isAdjusting){
if (isSilent){
return;
}
super.fireValueChanged(firstIndex, lastIndex, isAdjusting);
}
}
public static void main(String[] args){
JFrame frame = new JFrame("test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// Add content to the window.
frame.add(new ListTest());
// Display the window.
frame.pack();
frame.setSize(300, 200);
frame.setVisible(true);
}
}
It looks like setSelectedIndex() is just a convenient way to set the selection in the ListSelectionModel. Maybe your ListModel could flag or cache the result so it won't get loaded a second time.