How to set JSplitPane-Divider collapse/expand State? - java

I have a JFrame with a JSplitPane that is OneTouchExpandable.
I want to remember the last Divider position of the JSplitPane on JFrame dispose and restore the Position if the JFrame is reopened.
It works well, but if the User expand one Side via the oneTouchExpandable UI-Widget then
I store only the 'int'-Position on dispose and set the 'int'-Position back again with the consequence on JFrame-resizing the JSplitPane-Divider jumps to the collapsed Component preferredSize.
How can I get/set the collapse/expand State?
EDIT
Now: the resize-Behavior is OK, but it is not exactly the same behavior like the first-time-open - cause now I have no MinimumDividerLocation. I wanted the SnapIn but further the collapsedState.
public class SplitPaneState {
public static void main( String[] args ) {
SwingUtilities.invokeLater( new Runnable() {
#Override
public void run() {
new SplitPaneState().createAndSowGUI();
}
});
}
private int position = -1;
private Dimension size = new Dimension( 500, 300 );
private void createAndSowGUI() {
final JFrame frame = new JFrame("frame");
frame.setSize( 200, 100 );
frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
frame.setLocationRelativeTo( null );
frame.getContentPane().add( new JButton( new AbstractAction(){
{
putValue( Action.NAME, "Open Dialog" );
}
#Override
public void actionPerformed( ActionEvent e ) {
final JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, new JLabel( "left Component" ), new JLabel( "right Component" ));
splitPane.setContinuousLayout( true );
splitPane.setOneTouchExpandable( true );
if(position != -1) {
boolean LeftIsCollapsed = position < splitPane.getMinimumDividerLocation();
if(LeftIsCollapsed) {
splitPane.getLeftComponent().setMinimumSize(new Dimension()); // fix by Martijn Courteaux
splitPane.setDividerLocation(0.0d); // fix by Martijn Courteaux
}else {
splitPane.setDividerLocation(position);
}
}
JDialog dialog = new JDialog(frame,"dialog"){
#Override
public void dispose() {
position = splitPane.getDividerLocation();
size = this.getSize();
super.dispose();
}
};
dialog.setSize( size );
dialog.setDefaultCloseOperation( JFrame.DISPOSE_ON_CLOSE );
dialog.setLocationRelativeTo( frame );
dialog.getContentPane().add( splitPane );
dialog.setVisible( true );
}
}
));
frame.setVisible( true );
}
}

I found that it is possible to collapse one side of the splitpane by setting the minimum size of the component to new Dimension() and then set the divider location:
// Hide left or top
pane.getLeftComponent().setMinimumSize(new Dimension());
pane.setDividerLocation(0.0d);
// Hide right or bottom
pane.getRightComponent().setMinimumSize(new Dimension());
pane.setDividerLocation(1.0d);
You can play with these settings to store and restore the collapse/expand state.

Forcing the divider position to a very small/large value to hide the top/bottom component works, but is defeated when the split pane gets resized, because of the component minimum size. Setting that size to 0 (as proposed in the accepted answer) works, but there are cases when you cannot/don't want to override that.
After looking into the BasicSplitPaneUI and associated classes, it turns out the "one touch expanding" buttons are calling BasicSPlitPaneUI.setKeepHidden(true), so the divider will stick to either end when resized.
Unfortunately, that method is package-private but setting the associated keepHidden field can be done using introspection, as shown in another answer:
sp.setDividerLocation(<0 or 999999>); // Divider is positioned at the top/bottom
Field m = BasicSplitPaneUI.class.getDeclaredField("keepHidden");
m.setAccessible(true);
m.set(sp.getUI(), true); // Divider position will stick

I improved version of toggle function:
/**
* toggle JSplitPane
* #param sp - splitpane to toggle
* #param upLeft - is it left or top component to collapse? or button or right
* #param collapse - true component should be collapsed
*/
public void toggle(JSplitPane sp, boolean upLeft, boolean collapse) {
try {
//get divider object
BasicSplitPaneDivider bspd = ((BasicSplitPaneUI) sp.getUI()).getDivider();
Field buttonField;
//get field button from divider
if (upLeft) {
if (collapse != (sp.getDividerLocation() < sp.getMinimumDividerLocation())) {
return;
}
buttonField = BasicSplitPaneDivider.class.getDeclaredField(collapse ? "rightButton" : "leftButton");
} else {
if (collapse != (sp.getDividerLocation() > sp.getMaximumDividerLocation())) {
return;
}
buttonField = BasicSplitPaneDivider.class.getDeclaredField(collapse ? "leftButton" : "rightButton");
}
//allow access
buttonField.setAccessible(true);
//get instance of button to click at
JButton button = (JButton) buttonField.get(((BasicSplitPaneUI) sp.getUI()).getDivider());
//click it
button.doClick();
//if you manage more dividers at same time before returning from event,
//you should update layout and ui, otherwise nothing happens on some dividers:
sp.updateUI();
sp.doLayout();
} catch (Exception e) {
e.printStackTrace();
}
}

Is hiding your dialog/frame an option?
// Create the dialog/frame which contains the JSplitPane
final JFrame frame = new JFrame("JSplitPane Problem");
frame.setCloseOperation(JFrame.HIDE_ON_CLOSE);
// ...
myButton.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent ae)
{
if (!frame.isVisible())
frame.setVisible(true);
}
});

http://docs.oracle.com/javase/7/docs/api/javax/swing/JSplitPane.html
void javax.swing.JSplitPane.setDividerLocation(double proportionalLocation)
Sets the divider location as a percentage of the
JSplitPane's size. This method is implemented in terms of
setDividerLocation(int). This method immediately changes the size of
the split pane based on its current size. If the split pane is not
correctly realized and on screen, this method will have no effect (new
divider location will become (current size * proportionalLocation)
which is 0).
So basically you need to have created your whole UI, called .pack() on the main JFrame
AND after that you can use JSplitPane.setDividerLocation(double). If you do it before UI layouting and all that stuff is done, the method will just do nothing as it states in the documentation and you already experienced.

JSplitPane has a method setDividerLocation(double), which sets the divider location as a percentage of the JSplitPane's size.
I tried to create similar functionality some time ago. I had the same problem. But even when I use the setDividerLocation(double) method, it didn't work properly. I believe that it's just JSplitPane bug.

public static void toggle(JSplitPane sp, boolean collapse) {
try {
BasicSplitPaneDivider bspd = ((BasicSplitPaneUI) sp.getUI()).getDivider();
Field buttonField = BasicSplitPaneDivider.class.
getDeclaredField(collapse ? "rightButton" : "leftButton");
buttonField.setAccessible(true);
JButton button = (JButton) buttonField.get(((BasicSplitPaneUI) sp.getUI()).getDivider());
button.getActionListeners()[0].actionPerformed(new ActionEvent(bspd, MouseEvent.MOUSE_CLICKED,
"bum"));
} catch (Exception e) {
e.printStackTrace();
}
}

Related

Get visible state of Node in JavaFX 8

I need to detect if a node is currently displaying.
I.e. if my Node is in a TabPane, I need to know if it is in a selected tab or not.
In the example, I want to know when the HBox is displaying.The visibleProperty and managedProperty of Node, does not seem to help me:
public class VisibleTest extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
TabPane tabpane = new TabPane();
tabpane.getTabs().add(new Tab("Tab1", new Label("Label1")));
HBox hbox = new HBox(new Label("Label2"));
hbox.setStyle("-fx-background-color: aquamarine;");
hbox.visibleProperty().addListener((observable, oldValue, newValue) -> {
System.out.println("Hbox visible changed. newValue: " + newValue);
});
hbox.managedProperty().addListener((observable, oldValue, newValue) -> {
System.out.println("Hbox managed changed. newValue: " + newValue);
});
Tab tab2 = new Tab("tab2", hbox);
tabpane.getTabs().add(tab2);
primaryStage.setScene(new Scene(tabpane));
primaryStage.setWidth(600);
primaryStage.setHeight(500);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
I know, it is possible to listen on the selectedProperty state of the tab, but this does not solve my real problem.
Node.impl_isTreeVisible() does what I want, but this is depricated API.
Any ideas?
------------------------------------ update--------------------
I realize the code example above does not explain well what I'm trying to accomplish. Below is some Swing code that kind of demonstrates what I am trying to accomplish in JavaFX. Detect if the JComponent/Node is visible/shown, and based on that state, start or stop background processes. How would the constructor look like if it was a javaFX class.
public class SwingVisible extends JComponent {
String instanceNR;
Thread instanceThread;
boolean doExpensiveStuff = false;
public SwingVisible(String instanceNR) {
this.instanceNR = instanceNR;
this.setLayout(new FlowLayout());
this.add(new JLabel(instanceNR));
instanceThread = new Thread(new Runnable() {
#Override
public void run() {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (doExpensiveStuff) {
/*
* do expensive stuff.
*/
System.out.println(instanceNR + " is visible " + isVisible());
}
}
}
});
/*
* How to do this in FX?
*/
addComponentListener(new ComponentAdapter() {
#Override
public void componentShown(ComponentEvent e) {
if (!instanceThread.isAlive()) {
instanceThread.start();
}
doExpensiveStuff = true;
}
#Override
public void componentHidden(ComponentEvent e) {
doExpensiveStuff = false;
}
});
}
public static void main(String[] args) {
/*
* This block represents code that is external to my library. End user
* can put instances of SwingVisible in JTabbedPanes, JFrames, JWindows,
* or other JComponents. How many instances there will bee is not in my
* control.
*/
JTabbedPane jtp = new JTabbedPane();
jtp.add("tab1", new SwingVisible("1"));
jtp.add("tab2", new SwingVisible("2"));
jtp.add("tab3", new SwingVisible("3"));
JFrame f = new JFrame("test");
f.setContentPane(jtp);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setSize(300, 300);
f.setLocationRelativeTo(null);
f.setVisible(true);
}
}
Output when tab1 is selected:
1 is visible true
1 is visible true
1 is visible true
...
Output when tab2 is selected:
2 is visible true
2 is visible true
2 is visible true
...
You can use Tab's selectedProperty to know if it is selected or not, and by extension if its content is visible or not. It is a boolean property.
I've converted your Swing code to JavaFX based on your initial JavaFX example:
public class VisibleTest extends Application {
public class FXVisible extends Tab {
FXVisible(String id) {
super(id, new Label(id));
Timeline thread = new Timeline(
new KeyFrame(Duration.ZERO, e -> {
if (isSelected()) {
// do expensive stuff
System.out.println(id + " is visible");
}
}),
new KeyFrame(Duration.seconds(1))
);
thread.setCycleCount(Timeline.INDEFINITE);
selectedProperty().addListener((selectedProperty, wasSelected, isSelected) -> {
if (isSelected) {
if (thread.getStatus() != Status.RUNNING) {
System.out.println(id + " starting thread");
thread.play();
}
}
// else, it is not selected -> content not shown
});
}
}
#Override
public void start(Stage primaryStage) throws Exception {
TabPane tabpane = new TabPane();
tabpane.getTabs().add(new FXVisible("1"));
tabpane.getTabs().add(new FXVisible("2"));
tabpane.getTabs().add(new FXVisible("3"));
// add as many as you want
primaryStage.setScene(new Scene(tabpane));
primaryStage.setWidth(600);
primaryStage.setHeight(500);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
I replaced your thread with a JavaFX Timeline. Your question is not about this topic so I won't go into details here, though it's self explanatory.
I don't understand why in the Swing example you have a listener changing a boolean that indicates if the component is visible or not when you can just call isVisible() directly in the thread (see comments below for a note about threading). This is why in my code above I took the approach of checking isSelected() directly with no self-declared boolean. If you need to revert to your design it's rather straightforward. Just noting this for clarity.
The ComponentListener can be replaced with a change listener on selectedProperty() and querying the new value. Just be sure that your example does what it's supposed to do: the first time the tab is selected the thread/timer starts. After that the thread/timer does nothing. You might have wanted to pause the computation for non-displaying content. Again, just noting it because it seemed like a potential mistake to me, otherwise you're fine.
Updated answer.
tab2.getContent().isVisible();
It seems to me that my original answer is correct. If not, you need to ask your question in a better way. You want to know when the hbox is visible(meaning you can see the hbox on the screen).
tabpane.getSelectionModel().selectedItemProperty().addListener((obsVal, oldTab, newTab)->{
System.out.println(newTab.getText());
if(newTab.getText().equals("tab2"))
{
//You can use this code to set the hbox visibility, that way you can force the behavior you are looking for.
hbox.setVisible(true);
System.out.println("hbox is visible!");
}
else
{
//You can use this code to set the hbox visibility, that way you can force the behavior you are looking for.
hbox.setVisible(false);
System.out.println("hbox is not visible!");
}
});
From quick checking this seemed to work for both checking the window is showing and that the tab it is in is displaying. I have also checked and it seems to work as expected for titled panes too that are collapsible.
public static boolean detectVisible( Node node )
{
Node current = node;
while( current != null ) {
if( !current.isVisible() ) {
return false;
}
current = current.getParent();
}
Window window = Optional.of( node ).map( Node::getScene ).map( Scene::getWindow ).orElse( null );
if( window == null ) {
return false;
}
if( window instanceof Stage && ( (Stage) window ).isIconified() ) {
return false;
}
return window.isShowing();
}

How do I return a swing event if it not used?

The texts are inside JPanels and they are all inside a JScrollPane. I have set to catch the MouseWheelEvent (scrolling) so when the Ctrl key is pressed, and mouse wheel is turned, the texts would zoom in/out (font size is increased/decreased). This has lead to not being able to scroll through the list of fonts anymore unless I point the mouse cursor directly on the scrollbar. I want to know if there is a way to return the event when the Ctrl key is not held down. Is that possible? I looked through the MouseWheelEvent api and didn't find anything useful. Googling didn't give me anything either.
If I did understood you correctly the answer you are looking for should be like this the main idea to create to different mouse wheel event for your form and your scrollpane. Watch the entire animation you will see your problem is fixed , you can make your manipulations based on this idea
you should add below to your fields ;
public int defaultTextSize = 12;
boolean ctrlcheck =false;
this is jLabel4 to display CTRL is not pressed in constructor
jLabel4.setText("CTRL IS NOT PRESSED");
your scrollpane mouse event should be like this
private void jScrollPane1MouseWheelMoved(java.awt.event.MouseWheelEvent evt) {
int notches = evt.getWheelRotation();
if (notches < 0) {
if (ctrlcheck == true) {
defaultTextSize = defaultTextSize + 3;
Font zoomfont = new Font("Monospaced", Font.PLAIN, defaultTextSize);
jLabel1.setFont(zoomfont);
jLabel2.setFont(zoomfont);
jLabel3.setFont(zoomfont);
jLabel1.repaint();
jLabel2.repaint();
jLabel3.repaint();
} else {
System.out.println("CTRL IS NOT PRESSED");
}
} else {
if (ctrlcheck == true) {
defaultTextSize = defaultTextSize - 3;
Font zoomfont2 = new Font("Monospaced", Font.ITALIC, defaultTextSize);
jLabel1.setFont(zoomfont2);
jLabel2.setFont(zoomfont2);
jLabel3.setFont(zoomfont2);
jLabel1.repaint();
jLabel2.repaint();
jLabel3.repaint();
}
}
}
You need FORM Key Released Event Like this
private void formKeyReleased(java.awt.event.KeyEvent evt) {
jLabel4.setText("CTRL IS NOT PRESSED");
ctrlcheck = false;
}
You need separate event for formMouseWheelMoved
private void formMouseWheelMoved(java.awt.event.MouseWheelEvent evt) {
int notches = evt.getWheelRotation();
if (notches < 0) {
if (ctrlcheck == true) {
defaultTextSize = defaultTextSize + 3;
Font zoomfont = new Font("Monospaced", Font.PLAIN, defaultTextSize);
jLabel1.setFont(zoomfont);
jLabel2.setFont(zoomfont);
jLabel3.setFont(zoomfont);
jLabel1.repaint();
jLabel2.repaint();
jLabel3.repaint();
} else {
System.out.println("CTRL IS NOT PRESSED");
}
} else {
if (ctrlcheck == true) {
defaultTextSize = defaultTextSize - 3;
Font zoomfont2 = new Font("Monospaced", Font.ITALIC, defaultTextSize);
jLabel1.setFont(zoomfont2);
jLabel2.setFont(zoomfont2);
jLabel3.setFont(zoomfont2);
jLabel1.repaint();
jLabel2.repaint();
jLabel3.repaint();
}
}
}
You need Form Keypressed Event as well to check key is still pressed
private void formKeyPressed(java.awt.event.KeyEvent evt) {
int key = evt.getKeyCode();
if (key == KeyEvent.VK_CONTROL) {
jLabel4.setText("CTRL IS PRESSED");
ctrlcheck = true;
}
}
I used #MadProgrammer 's comment and got some idea from #okrman 's answer and came up with a better way of doing this. In my solution, I don't need to add two separate event listeners, namely, one to the JLabel and the other one to the JScrollPane. I use only one event listener on the JLabel. The main key here is Component#dispatchEvent as mentioned by #MadProgrammer. I just found (rather remembered from my foggy memory) an excellent way to get a hold of the parent JScrollPane inside JLabel.
Of course, the example I have given here is just an example. I have not set the widths or heights for any of the components. The soul purpose of this example is just to show how the problem is solved.
public class ParentScrollPane extends JScrollPane {
public ParentScrollPane() {
JPanel textContainer = new JPanel();
// I have multiple labels in a for loop, this is
// just an example
JLabel sLabel = new JLabel("SOME LABEL TEXT");
sLabel.setParent(thisPane);
textContainer.add(sLabel);
setViewportView(textContainer);
}
// this interface is important and key to the solution
public interface ParentSetter {
public void setParent(Component p);
}
private JScrollPane thisPane;
}
public class CustomLabel extends JLabel implements ParentScrollPane.ParentSetter {
public CustomLabel(String text) {
super(text);
// one event listener is suffice
addMouseWheelListener(new MouseWheelListener() {
#Override
public void mouseWheelMoved(MouseWheelEvent e) {
// if the Ctrl key is down
if (e.isControlDown()) {
// then zoom the font in the JLabel
} else {
// then scroll the parent scroll pane
labelParent.dispatchEvent(e);
}
}
});
}
// pay very close attention to this and
// how the parent scroll pane is set here
#Override
public void setParent(Component lParent) {
labelParent = lParent;
}
private Component labelParent;
}

Java - Adding tab to JTabbedPane causing StackOverflowError (Disabling a firing ChangeEvent?)

Hey StackOverflow community, I'm having a StackOverflow problem.
I'm having difficulty adding a new tab to my GUI's JTabbedPane container, when the [+] tab is selected. So far, whenever I click the [+] tab, new tabs are appended until a StackOverflowError occurs.
A new tab is added to the JTabbedPane when the following condition is true.
if(songPanel.getSelectedIndex()==songPanel.getTabCount()-1){
...
}
I've tried to revert back to the previously selected tab to avoid tabs being added repeatedly to the JTabbedPane, to no avail. When the ChangeEvent actuator is fired, does it stay on indefinitely? I haven't come across anything useful in the SE7 API.
Relevant code (non-compilable, excerpt from larger program. May be missing brackets, only because I copy-pasted excerpts of the code, and liable to make mistakes)
#Override
public void init(){
setLayout(new GridLayout(MAIN_LAYOUT_ROWS, MAIN_LAYOUT_COLUMNS));
add(renderPanel = new JScrollPane());
add(controlPanel = new JPanel());
add(colourPanel = new JPanel());
add(songPanel = new JTabbedPane());
//songPanel options
songPanel = new JTabbedPane();
songPanel.setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT);
songPanel.addTab("#1", new JTextArea());
songPanel.addTab("+", null, new JLabel(), "+");
Container cp = getContentPane();
cp.add(BorderLayout.SOUTH, songPanel);
//integrate songPanel changeListener
songPanel.addChangeListener(new ChangeListener(){
#Override //Method called when selected tab changes
public void stateChanged(ChangeEvent e){
try {
if(songPanel.getSelectedIndex()==songPanel.getTabCount()-1){
addTab("songPanel");
}
} catch (StackOverflowError soe){soe.printStackTrace();}
}
});
//*************************************************************************
#Override
public void start(){
}
//*************************************************************************
private void addTab(String panelName){
System.out.println("ADDING TAB");
if(panelName.equals("songPanel")){
String tabName = ("#" + Integer.toString(songPanel.getTabCount()-1));
songPanel.insertTab(tabName, null, new JTextField(), tabName, songPanel.getTabCount()-2);
}
}
}
//**************************************************************************
}
I've tried:
Setting a revert index in the addTab() method, so the newest tab is selected (still results in StackOverflowError)
Note this line:
songPanel.getSelectedIndex()==songPanel.getTabCount()-1)
Both "songPanel.getSelectedIndex()" and "songPanel.getTabCount()-1)" are always equal, so condition is always true (causing the StackOverflowError)
Error message:
java.lang.StackOverflowError
at javax.swing.text.StyleContext$SmallAttributeSet.getAttributeNames(StyleContext.java:947)
at javax.swing.text.StyleContext$SmallAttributeSet.containsAttributes(StyleContext.java:973)
at javax.swing.text.StyleContext$SmallAttributeSet.equals(StyleContext.java:852)
at java.util.WeakHashMap.eq(WeakHashMap.java:282)
at java.util.WeakHashMap.get(WeakHashMap.java:379)
at java.util.Collections$SynchronizedMap.get(Collections.java:2031)
at javax.swing.text.StyleContext.getImmutableUniqueSet(StyleContext.java:520)
at javax.swing.text.StyleContext.addAttributes(StyleContext.java:340)
at javax.swing.text.AbstractDocument$AbstractElement.addAttributes(AbstractDocument.java:1985)
at javax.swing.text.AbstractDocument$AbstractElement.<init>(AbstractDocument.java:1777)
at javax.swing.text.AbstractDocument$LeafElement.<init>(AbstractDocument.java:2502)
at javax.swing.text.AbstractDocument$BidiElement.<init>(AbstractDocument.java:2674)
at javax.swing.text.AbstractDocument.<init>(AbstractDocument.java:149)
at javax.swing.text.AbstractDocument.<init>(AbstractDocument.java:109)
at javax.swing.text.PlainDocument.<init>(PlainDocument.java:90)
at javax.swing.text.PlainDocument.<init>(PlainDocument.java:80)
at javax.swing.text.DefaultEditorKit.createDefaultDocument(DefaultEditorKit.java:130)
at javax.swing.plaf.basic.BasicTextUI.installUI(BasicTextUI.java:799)
at javax.swing.JComponent.setUI(JComponent.java:655)
at javax.swing.text.JTextComponent.setUI(JTextComponent.java:338)
at javax.swing.text.JTextComponent.updateUI(JTextComponent.java:348)
at javax.swing.text.JTextComponent.<init>(JTextComponent.java:322)
at javax.swing.JTextField.<init>(JTextField.java:231)
at javax.swing.JTextField.<init>(JTextField.java:172)
at application.Analyzer.addTab(Analyzer.java:133)
at application.Analyzer.access$100(Analyzer.java:24)
at application.Analyzer$1.stateChanged(Analyzer.java:101)
at javax.swing.JTabbedPane.fireStateChanged(JTabbedPane.java:416)
at javax.swing.JTabbedPane$ModelListener.stateChanged(JTabbedPane.java:270)
at javax.swing.DefaultSingleSelectionModel.fireStateChanged(DefaultSingleSelectionModel.java:132)
at javax.swing.DefaultSingleSelectionModel.setSelectedIndex(DefaultSingleSelectionModel.java:67)
at javax.swing.JTabbedPane.setSelectedIndexImpl(JTabbedPane.java:616)
at javax.swing.JTabbedPane.insertTab(JTabbedPane.java:735)
at application.Analyzer.addTab(Analyzer.java:133)
at application.Analyzer.access$100(Analyzer.java:24)
.
.
.
Do you have any suggestions? I know it's kind of vague, but I'm really not sure what is going wrong.
Any suggestions?
Thanks.
Tyler
StackOverflow identifies an infinite recursion. So first thing is to find that recursion. In your case these are the lines of the stacktrace that identifies that recursion:
at application.Analyzer.addTab(Analyzer.java:133) at
application.Analyzer.access$100(Analyzer.java:24) at
application.Analyzer$1.stateChanged(Analyzer.java:101) at
javax.swing.JTabbedPane.fireStateChanged(JTabbedPane.java:416) at
javax.swing.JTabbedPane$ModelListener.stateChanged(JTabbedPane.java:270)
at
javax.swing.DefaultSingleSelectionModel.fireStateChanged(DefaultSingleSelectionModel.java:132)
at
javax.swing.DefaultSingleSelectionModel.setSelectedIndex(DefaultSingleSelectionModel.java:67)
at javax.swing.JTabbedPane.setSelectedIndexImpl(JTabbedPane.java:616)
at javax.swing.JTabbedPane.insertTab(JTabbedPane.java:735) at
application.Analyzer.addTab(Analyzer.java:133)
So when you insert a tab, it automatically triggers a change of selected tab which in turns calls your ChangeEventListener which will trigger the insertion of a tab etc...
So you have two simple solutions:
Use a flag (a boolean) that is set to true before you add the new tab and that you set back to false when you are done. In your condition to test if you need to add a tab, you also check that this flag is not true.
You remove your change listener from the JTabbedPane before you insert the tab and you put it back afterwards.
In both case, use a try/finally block to make sure to return to a consistent state.
UPDATED SOLUTION
Sorry, the previous solution is not working as expected. Here my updated one:
public class TabbedPaneTest {
private final static JButton ADD_NEW_TAB_BUTTON = new JButton();
private JFrame mainFrame;
private JTabbedPane tabbedPane;
public void run() {
mainFrame = new JFrame("Test JTabbedPane");
mainFrame.setSize(300, 400);
mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
tabbedPane = new JTabbedPane();
tabbedPane.addTab("default new tab", new JLabel("this is a default tab"));
addNewTabButton();
tabbedPane.addChangeListener(new ChangeListener() {
#Override
public void stateChanged(ChangeEvent e) {
if (tabbedPane.getSelectedComponent() == ADD_NEW_TAB_BUTTON) {
tabbedPane.addTab("new tab", new JLabel("new tab label"));
addNewTabButton();
}
}
});
mainFrame.getContentPane().add(tabbedPane);
mainFrame.setVisible(true);
}
private void addNewTabButton() {
tabbedPane.remove(ADD_NEW_TAB_BUTTON);
tabbedPane.addTab("[+]", ADD_NEW_TAB_BUTTON);
}
public static void main(String[] params) {
TabbedPaneTest test = new TabbedPaneTest();
test.run();
}
}
The problem is that the changeListener is called after the adding once again with the + tab as the selected tab, causing the creation of a new tab, and so on.
a pretty simple solution could be just to add a bool flag as Guillaume Polet stated :
songPanel.addChangeListener(new ChangeListener(){
#Override //Method called when selected tab changes
public void stateChanged(ChangeEvent e){
try {
if(songPanel.getSelectedIndex()==songPanel.getTabCount()-1 && !adding){
adding = true;
addTab("songPanel");
adding = false;
}
} catch (StackOverflowError soe){soe.printStackTrace();}
}
});
The adding flag is a class field initialized to false, and indicates whether or not you are in progress of adding a tab.
Minor changes to the addTab to get everything to work:
private void addTab(String panelName){
System.out.println("ADDING TAB");
if(panelName.equals("songPanel")){
String tabName = ("#" + Integer.toString(songPanel.getTabCount()));
int index = songPanel.getTabCount()-1;
songPanel.insertTab(tabName, null, new JTextField(), tabName, index);
songPanel.setSelectedIndex(index);
}
}
I made a little change in code, making the active tab the newly created one, and the indexes where a bit off.
Hope this helps :)

How to call a function when I click on a jPanel (Java)?

I'm working with Netbeans IDE in Java.
I've a form with one JPanel.
Each JPanel has a gridLayout 3x3 and in each place there is an image representing a number[0,1,2,3,4,5,6,7,8](the image is created used a custom class,not just fitting the image in a lab).
I want to be able to exchange two images in the panel when the user click them (First click: no action , second click: switch the two images fitted in the jPanel Components).
I already created a function exchangeComponents and with a test code (like:
exchangeComponents (0,8,jPanel1)
it exchanges correctly the images located in position1 (1st row,1st column) and in position2 (3rd row,3rd column).
The function a creted is the following:
public void exchangeComponents(int component1,int component2,JPanel jpanel){
try{
Component aux1 = jpanel.getComponent(component1);
Point aux1Loc = aux1.getLocation();
Component aux2 = jpanel.getComponent(component2);
Point aux2Loc = aux2.getLocation();
aux1.setLocation(aux2Loc);
aux2.setLocation(aux1Loc);
}
catch (java.lang.ArrayIndexOutOfBoundsException ex){ /* error! bad input to the function*/
System.exit(1);
}
}
I suppose I neeed to have an event that call the function exchangeComponents() when the user click on one of the images on the jPanel1 but how should I do it? and how to check what components (images) the user has selected?
I just know that when I create a Button if a click on it (from the IDE) an event like
private void button1ActionPerformed(java.awt.event.ActionEvent evt) {
// some code..
}
is created and the code I fill in is executed.
Thank you in advance for any hint.
You need to add the same mouse listener to all you JLabels or whatever container you have for your images, like:
img1.addMouseListener(this);
img2.addMouseListener(this);
etc., then detect which Jlabel you clicked with MouseEvent.getSource(); , like this
boolean hasclicked1=false;
JLabel click1label=null;
public void mouseClicked(MouseEvent me){
if(!hasclicked1){ //clicked first pic
hasclicked1 = true;
click1label = (JLabel) me.getSource();
} else { //clicked second pic
hasclicked1 = false;
exchangeComponents(click1label, (JLabel) me.getSource(), /*your jpanel here*/);
}
//now change exchangeComponents so it uses JLabels as parameters
public void exchangeComponents(JLabel component1, JLabel component2, JPanel jpanel){
try{
Component aux1 = component1;
Point aux1Loc = aux1.getLocation();
Component aux2 = component2;
Point aux2Loc = aux2.getLocation();
aux1.setLocation(aux2Loc);
aux2.setLocation(aux1Loc);
} catch (java.lang.ArrayIndexOutOfBoundsException ex) { /* error! bad input to the function*/
System.exit(1);
}
}
If you are not using JLabels for the images though, replace JLabel in the code with whatever you are using...
EDIT: Sorry, I don't think I made this unclear, but your class with the method exchangeComponents has to implement MouseListener. Then, in the mouseClicked event put the code I gave for it. Make sure to include the variables hasclicked1 and click1label in your class. Make you class something like this
public class ComponentExchanger implements MouseListener {
boolean hasclicked1=false;
JLabel click1label=null;
JPanel mainPanel;
public ComponentExchanger(){
//create JFrame, JPanel, etc.
JFrame f=new JFrame();
//etc.
mainPanel=new JPanel();
f.add(mainPanel);
//set layout of panel, etc.
for(int i=0;i<9;i++){
JLabel l=new JLabel(/*label image here*/);
Point loc=new Point(/*coordinates here*/);
l.setLocation(loc);
mainPanel.add(l);
/*more code*/
f.setVisible(true);
}
}
public static void main(String args[]){
new ComponentExchanger();
}
public void mouseClicked(MouseEvent me){
if(!hasclicked1){ //clicked first pic
hasclicked1 = true;
click1label = (JLabel) me.getSource();
} else { //clicked second pic
hasclicked1 = false;
exchangeComponents(click1label, (JLabel) me.getSource(), mainPanel);
}
//now change exchangeComponents so it uses JLabels as parameters
public void exchangeComponents(JLabel component1, JLabel component2, JPanel jpanel){
try{
Component aux1 = component1;
Point aux1Loc = aux1.getLocation();
Component aux2 = component2;
Point aux2Loc = aux2.getLocation();
aux1.setLocation(aux2Loc);
aux2.setLocation(aux1Loc);
} catch (java.lang.ArrayIndexOutOfBoundsException ex) { /* error! bad input to the function*/
System.exit(1);
}
}
//Also, you will need to include the other mouselistener implemented methods, just
//leave them empty
}
First of all, to be technical it's methods not functions.
There are a couple of ways you could do this. You could go ahead with actionListener, but then you would probably need buttons or something.
Or you could use MouseListener, and detect clicks over a certain region of the panel.
For the switching algorithm, perhaps an array of 2 images. There is a variable that increases by 1 every click. When the variable is 2, it resets back to 0.
clicks++; //every time the mouse is clicked; clicks starts out at 0
if(clicks == 2){
clicks = 0; //at the end of the listener method
}
On the first click the clicked image goes into the first array slot, because the user has clicked once.
clickImage = imageArray[clicks];
On the second click, the other clicked image goes to the second array slot, because 2 clicks have been detected. In this case, your exchangeComponents method would go at the end of the listener method, with the arguments being imageArray[1], imageArray[2], .
You can apply this to ints or whatever, just save the value in an array and use an incrementing and resetting variable.

How to make JTextPane autoscroll only when scroll bar is at bottom and scroll lock is off?

How to make JTextPane autoscroll only when scroll bar is at bottom and scroll lock is off? This shouldn't have anything to do with caret, which is what I seem to be finding all over Google. :(
I think my program below meets your requirements exactly, with one possible caveat: you're not allowed to type in the text area. So this would be good for a log viewer, but not an interactive console. The code runs a little long because I have made it into a ready-to-run demo of the approach. I suggest running the program as-is and checking out the behavior. If the behavior works well for you, then invest a little time in studying the code. I have included comments in the code to highlight some of the more important sections.
Update 2013-07-17: You may also want to check out random dude's solution in his separate answer farther down the page. His approach is more elegant than mine.
Also see Swing: Scroll to bottom of JScrollPane, conditional on current viewport location for a potential solution that does not interfere with the caret position.
SCCE source code follows:
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import java.util.Timer;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.text.*;
public class ScrollingJTextAreaExample extends JFrame {
// Worker thread to help periodically append example messages to JTextArea
Timer timer = new Timer();
// Merely informative counter, will be displayed with the example messages
int messageCounter = 0;
// GUI components
JScrollPane jScrollPane;
JTextArea jTextArea;
public ScrollingJTextAreaExample() {
initComponents(); // Boiler plate GUI construction and layout
// Configure JTextArea to not update the cursor position after
// inserting or appending text to the JTextArea. This disables the
// JTextArea's usual behavior of scrolling automatically whenever
// inserting or appending text into the JTextArea: we want scrolling
// to only occur at our discretion, not blindly. NOTE that this
// breaks normal typing into the JTextArea. This approach assumes
// that all updates to the ScrollingJTextArea are programmatic.
DefaultCaret caret = (DefaultCaret) jTextArea.getCaret();
caret.setUpdatePolicy(DefaultCaret.NEVER_UPDATE);
// Schedule a task to periodically append example messages to jTextArea
timer.schedule(new TextGeneratorTask(), 250, 250);
// This DocumentListener takes care of re-scrolling when appropriate
Document document = jTextArea.getDocument();
document.addDocumentListener(new ScrollingDocumentListener());
}
// Boring, vanilla GUI construction and layout code
private void initComponents() {
jScrollPane = new javax.swing.JScrollPane();
jTextArea = new javax.swing.JTextArea();
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
jScrollPane.setViewportView(jTextArea);
getContentPane().add(jScrollPane, java.awt.BorderLayout.CENTER);
setSize(320, 240);
setLocationRelativeTo(null);
}
// ScrollingDocumentListener takes care of re-scrolling when appropriate
class ScrollingDocumentListener implements DocumentListener {
public void changedUpdate(DocumentEvent e) {
maybeScrollToBottom();
}
public void insertUpdate(DocumentEvent e) {
maybeScrollToBottom();
}
public void removeUpdate(DocumentEvent e) {
maybeScrollToBottom();
}
private void maybeScrollToBottom() {
JScrollBar scrollBar = jScrollPane.getVerticalScrollBar();
boolean scrollBarAtBottom = isScrollBarFullyExtended(scrollBar);
boolean scrollLock = Toolkit.getDefaultToolkit()
.getLockingKeyState(KeyEvent.VK_SCROLL_LOCK);
if (scrollBarAtBottom && !scrollLock) {
// Push the call to "scrollToBottom" back TWO PLACES on the
// AWT-EDT queue so that it runs *after* Swing has had an
// opportunity to "react" to the appending of new text:
// this ensures that we "scrollToBottom" only after a new
// bottom has been recalculated during the natural
// revalidation of the GUI that occurs after having
// appending new text to the JTextArea.
EventQueue.invokeLater(new Runnable() {
public void run() {
EventQueue.invokeLater(new Runnable() {
public void run() {
scrollToBottom(jTextArea);
}
});
}
});
}
}
}
class TextGeneratorTask extends TimerTask {
public void run() {
EventQueue.invokeLater(new Runnable() {
public void run() {
String message = (++messageCounter)
+ " Lorem ipsum dolor sit amet, consectetur"
+ " adipisicing elit, sed do eiusmod tempor"
+ " incididunt ut labore et dolore magna aliqua.\n";
jTextArea.append(message);
}
});
}
}
public static boolean isScrollBarFullyExtended(JScrollBar vScrollBar) {
BoundedRangeModel model = vScrollBar.getModel();
return (model.getExtent() + model.getValue()) == model.getMaximum();
}
public static void scrollToBottom(JComponent component) {
Rectangle visibleRect = component.getVisibleRect();
visibleRect.y = component.getHeight() - visibleRect.height;
component.scrollRectToVisible(visibleRect);
}
public static void main(String args[]) {
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
new ScrollingJTextAreaExample().setVisible(true);
}
});
}
}
Little late to this question, but I came up with this solution.
conversationPane = new JTextPane();
final JScrollPane conversationScrollPane = new JScrollPane(conversationPane);
conversationScrollPane.getVerticalScrollBar().addAdjustmentListener(new AdjustmentListener() {
BoundedRangeModel brm = conversationScrollPane.getVerticalScrollBar().getModel();
boolean wasAtBottom = true;
public void adjustmentValueChanged(AdjustmentEvent e) {
if (!brm.getValueIsAdjusting()) {
if (wasAtBottom)
brm.setValue(brm.getMaximum());
} else
wasAtBottom = ((brm.getValue() + brm.getExtent()) == brm.getMaximum());
}
});
Seems to work perfectly for my needs. Little explanation: Essentially if the scroll bar is not being moved by a person and the bar was last at the maximum/bottom then reset it to the maximum. If it's being manually adjusted, then check to see if it was adjusted to be at the bottom.
Text Area Scrolling may be of interest.
I have no idea how the scroll lock key affects it. I found the following from the Wikipedia page on Scroll Lock:
Therefore, Scroll Lock can be regarded as a defunct feature in almost all modern programs and operating systems.
So I wouldn't worry about it.
I needed to do the same for a logging text area. The solutions I found on the web did not worked for me (they either stop auto scrolling when logging to much messages quickly, or they blocked the scrollbar at bottom even if you scroll up whith your mouse wheel).
I did it this way :
public static void makeTextAreaAutoScroll(JTextArea textArea) {
// Get the text area's scroll pane :
final JScrollPane scrollPane = (JScrollPane) (textArea.getParent().getParent());
// Disable the auto scroll :
((DefaultCaret)textArea.getCaret()).setUpdatePolicy(DefaultCaret.NEVER_UPDATE);
// Add a listener to the vertical scroll bar :
scrollPane.getVerticalScrollBar().addAdjustmentListener(new AdjustmentListener() {
private int _val = 0;
private int _ext = 0;
private int _max = 0;
private final BoundedRangeModel _model = scrollPane.getVerticalScrollBar().getModel();
#Override
public void adjustmentValueChanged(AdjustmentEvent e) {
// Get the new max :
int newMax = _model.getMaximum();
// If the new max has changed and if we were scrolled to bottom :
if (newMax != _max && (_val + _ext == _max) ) {
// Scroll to bottom :
_model.setValue(_model.getMaximum() - _model.getExtent());
}
// Save the new values :
_val = _model.getValue();
_ext = _model.getExtent();
_max = _model.getMaximum();
}
});
}
Just use it this way :
makeTextAreaAutoScroll(yourTextArea);
You can test with this piece of code :
new Timer().schedule(new TimerTask() {
#Override
public void run() {
javax.swing.SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
String line = "test " + Math.random();
yourTextArea.append(yourTextArea.getText().isEmpty() ? line : "\n" + line);
}
});
}
}, 0, 5);
Now your text area should auto scroll if the scroll bar is at bottom, stop auto scrolling if you move the scroll bar (by dragging the bar or by using the wheel), and auto scroll again if you put the scroll bar at bottom again.
Try this :
JTextArea txt = new JTextArea();
JScrollPane jsp = new JScrollPane(history, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
txt.setCaretPosition(txt.getDocument().getLength()); // do this afeter any event
Hope that helps you
After I read Mike Clark and random dude's solution, I end up with below snippet code.
private boolean doAutoScroll = true;
private JTextPane textPane;
private JScrollPane scrollPane;
public void setup() {
/* Left Panel */
textPane = new JTextPane();
textPane.setPreferredSize(new Dimension(600, 400)); // width, height
/*
* Not update the cursor position after inserting or appending text to the JTextPane.
* [NOTE]
* This breaks normal typing into the JTextPane.
* This approach assumes that all updates to the JTextPane are programmatic.
*/
DefaultCaret caret = (DefaultCaret) textPane.getCaret();
caret.setUpdatePolicy(DefaultCaret.NEVER_UPDATE);
scrollPane = new JScrollPane(textPane);
scrollPane.getVerticalScrollBar().addAdjustmentListener(new AdjustmentListener() {
BoundedRangeModel brm = scrollPane.getVerticalScrollBar().getModel();
#Override
public void adjustmentValueChanged(AdjustmentEvent e) {
// Invoked when user select and move the cursor of scroll by mouse explicitly.
if (!brm.getValueIsAdjusting()) {
if (doAutoScroll) brm.setValue(brm. getMaximum());
} else {
// doAutoScroll will be set to true when user reaches at the bottom of document.
doAutoScroll = ((brm.getValue() + brm.getExtent()) == brm.getMaximum());
}
}
});
scrollPane.addMouseWheelListener(new MouseWheelListener() {
BoundedRangeModel brm = scrollPane.getVerticalScrollBar().getModel();
#Override
public void mouseWheelMoved(MouseWheelEvent e) {
// Invoked when user use mouse wheel to scroll
if (e.getWheelRotation() < 0) {
// If user trying to scroll up, doAutoScroll should be false.
doAutoScroll = false;
} else {
// doAutoScroll will be set to true when user reaches at the bottom of document.
doAutoScroll = ((brm.getValue() + brm.getExtent()) == brm.getMaximum());
}
}
});
}
The difference is that it is additionally using MouseWheelListener to update doAutoScroll flag even if user uses mouse wheel to scroll up and down.

Categories