I am working on Swing for a while now but never had a situation in practice when I had to call setEnabled(false) in JPanel.
Still, I see such code sometimes in some sophisticated gui. But I really don't undarstand why someone wants to use it?
So, please give me some examples of real life common situations when you need to use setEnabled(false) on JPanel.
Also in javadoc it says:
Disabling a component does not disable its children.
actually I had a bug because table inside disabled JPanel didn't show mouse resize cursor when resizing columns. I suspect there are other unpleasant surprises here.
One reason is so that getEnabled() will reflect the correct state.
Consider a case where some event handler wants to flag the panel
as no longer enabled and it is not prudent at the time of the event
to iterate over and disable all child components. Other parts of the
app might need to test the state of the panel via getEnabled() to
determine what to do at different points in the app.
I personally never had to do this but now that you asked and got me
thinking I might use this sometime. Thanks. &&+=1 to the question.
Starter code to enable/disable all components in a container.
JPanel p = new JPanel();
p.setEnabled(state);
setEnabledAll(p, state);
public void setEnabledAll(Object object, boolean state) {
if (object instanceof Container) {
Container c = (Container)object;
Component[] components = c.getComponents();
for (Component component : components) {
setEnabledAll(component, state);
component.setEnabled(state);
}
}
else {
if (object instanceof Component) {
Component component = (Component)object;
component.setEnabled(state);
}
}
}
Related
I'd like to implement a ToolTip in Swing that has customised behaviour: the longer the user hovers over the component, the more detail should be shown in the tooltip (i.e., a few new lines are added after a few seconds of the user hovering over the component). I just need to check whether this is really doable with Swing without things getting too messy. My idea at the moment would probably be:
Extend ToolTipManager
Override mouseEntered to start a timer (maybe use javax.swing.Timer?). Call setToolTipText and createToolTip to refresh the tooltip and add new information at regular intervals
Override mouseExited to reset the timer
Probably use setDismissDelay to set the dismiss delay to something a lot longer (or Integer.MAX_VALUE)
Is such a thing feasible or is this not a good way to work with Swing (sorry, I'm pretty new to it)? Is there a better way of doing this?
[edit] Hmm, just remembered that ToolTipManager is a singleton with a constructor that only has package visibility, so it can't be extended.
[edit 2] I'm trying out a few solutions at the moment. One thing that I forgot to add is that I do need to know which component is being hovered over - which I guess means I'll need to be working with some sort of listener with a mouseEntered() method (or be able to access this information). And no other interactivity with the popup/tooltip is needed - it just needs to display information.
(This may seem a bit confusing so let me know if you need me to clarify let me know and I'll try to show you how I picture the code) I think your idea might work like if you extend it, and also make a private class that extends Threadand then in the run() method you do something like
while(true)
{
sleep(1);
timeElapsed++;
}
And in your class that extends ToolTipManager, create a field for that class that extends Thread and in the mouseEntered(MouseEvent e) instantiate the thing like:
extendsThreadClass = new ExtendsThreadClass();
extendsThreadClass.start();
and then in the mouseExited(MouseEvent e) method do
extendsThreadClass = null;
Then in that mouseEntered(MouseEvent e) method after starting the Thread then you can do what you want to do after the time thing like
if(timeElapsed > 3000)
{
//what you want to do here
}
Sorry it may be confusing, let me know if i can clear it up for you
I thought I should update this with the approach I took before I saw l1zZY's answer (which I think is the better way of doing things - I still had bugs in my code before I moved onto something else, but this might still be helpful to someone). This is what I did:
Extend JToolTip
Use a Swing Timer for timing
Add a MouseMotion listener to the JTree (in my case I wanted the popup to show when a node was hovered over)
Somewhat inelegantly, detect when the mouse moved over a tree node like this:
public void mouseMoved(MouseEvent e) {
int x = (int) e.getX();
int y = (int) e.getY();
TreePath path = getPathForLocation(x, y);
if (path == null) {
tooltip.hide();
} else {
TreeNode node = (TreeNode) path.getLastPathComponent();
tooltip.setHoveredNode(node);
if (!tooltip.isVisible) {
int absX = e.getXOnScreen();
int absY = e.getYOnScreen();
final Popup tooltipContainer = PopupFactory.getSharedInstance().getPopup(PDTreeView.this,
tooltip, absX, absY);
tooltip.setToolTipContainer(tooltipContainer);
tooltip.show();
}
}
}
tooltip.show() refers to how the tooltip was contained in a Popup
in order to show or hide it programmatically. show() shows the
Popup (and therefore tooltip) and also starts the Swing timer.
Timer has a method called actionPerformed() which is called at whatever interval you set. I just had that method call the code that adds new information to the tooltip. in hide(), I reset the tooltip and the timer.
I had issues with the popup or tooltip not resizing to fit the content, but otherwise this seemed ok.
All Swing components in my app (except labels) have tooltips that can be annoying once the user knows what's going on, so I have a Preferences menu that allows turning them off. I could name every component and set its tooltip text to "" [e.g., txtPattern.setToolTipText("");] (and 10 others), but I decided (with SO aid that started awhile back) to write code that would be more elegant (a learning experience):
private void tipsOff(Container container){
Component [] c = container.getComponents();
for (Component cc : c)
((JComponent)cc).setToolTipText("");
}
private void mniPrefTooltipsActionPerformed(java.awt.event.ActionEvent evt) {
if(! mniPrefTooltips.isSelected()){
tipsOff(gui.getContentPane());
tipsOff(gui.pnlLetters);
tipsOff(gui.mbrMenuBar);
}
else{
gui.dispose();
gui = new IO();
gui.setVisible(true);
}
}
I have a problem, which is that the tooltips are NOT turned off for the two large text areas at the bottom of the gui (highlighted in the Navigator pane). The two buttons (marked with green in Nav. pane) ARE processed correctly. These items are supposed to be processed via the first call to tipsOff, which processes gui.getContentPane()).
(I added the two lines bellow to try to rectify the problem. Nope.)
tipsOff(gui.scrOutput);
tipsOff(gui.scrScratch);
(Also tried this. Nope.)
tipsOff(gui.txaOutput);
tipsOff(gui.txaScratchwork);
How can I elegantly (i.e., assume I have many text areas, not just 2) turn off the text area tooltips?
P.S. I get the message Access of private field of another object for all but the first call to tipsOff. I don't care, owing to the nature of the task at hand.
Use ToolTipManager.sharedInstance().setEnabled( false ) to disable all tool tips in your Swing application.
Benefits compared to your approach
It works :-)
You do not clear the tooltips, so it is easy to re-enable them again. For example if you want to offer UI to your user to activate/de-activate the tooltips this approach will work. In your approach, you would have to restore all the tooltips you previously cleared, which would be difficult to do in a generic way.
I'm making a level editor for my java game using Java swing.
One of the features is that there is a a togglable button to turn the game on and off to test the level out. The game runs inside a jpanel, then you click the button again to untoggle it, and it turns the game off.
I only want the user to be able to change stuff or push buttons in the swing application when the game is NOT running, when it is running I set the focus to the game component. The only button in the swing application that they should be able push is the toggle button to turn the game back off.
The problem is, I can't think of a good way to do this. Using a recursive function I could easily loop through and find all components and do setEnabled(false), but when the game is turned back off it has no way to know what the previous enabled state was (along other issues, like other components responding to setEnabled being called on other components)
What I think I really need is just some kind of way to just outright "kill" user input into the swing application when the game is running.. But preferablly with a way to click the toggle button again to return the application's state, and the game which is running inside a Jpanel needs to be able to have focus...
Is there any way to do this sort of thing at all without massive amounts of "organizational" code to manage the components in the swing application?
You can place everything in a map, like this.
class ComponentState {
private JComponent component;
private bool on;
// Getters & Setters
}
private Map<String, ComponentState> components = new HashMap<>();
in order to add a new component to your game:
components.add("startbutton", new ComponentState(new JButton, true));
then to add all components to your screen:
for(String key : components.KeySet()) {
ComponentState comp = components.get(key);
if(comp.isOn()) { this.add(comp.getComponent()) };
}
and to disable/activate a component:
components.get("myActivatedComponent").disable(); // disable is a self defined method
You want a disableAll() method that sets every component to a disabled state, and a resetAll() method that will reset every component state back to its previous state. You need to save the status of every component when you disable it, in order to be able to restore it after. That will take O(n) space.
private final Map<JComponent, Boolean> components = new HashMap<JComponent, Boolean>();
public void disableAll(JComponent root) {
components.put(root, root.isEnabled());
root.setEnabled(false);
for (int i=0, n=root.getComponentCount(); i<n; i++) {
JComponent child = (JComponent) root.getComponentAt(i);
disableAll(child);
}
}
public void resetAll(JComponent root) {
boolean status = components.get(root);
root.setEnabled(status);
for (int i=0, n=root.getComponentCount(); i<n; i++) {
JComponent child = (JComponent) root.getComponentAt(i);
resetAll(child);
}
}
Another option is to use a GlassPane and "grey out" an area of components. You would also have to capture and ignore clicks in the pane for the area you don't want a users clicking.
See more with an example in the Java Tutorial here: http://docs.oracle.com/javase/tutorial/uiswing/components/rootpane.html
This write up could also be helpful:
https://weblogs.java.net/blog/alexfromsun/archive/2006/09/a_wellbehaved_g.html
Is there a way (e.g., via an event?) to determine when a Swing component becomes 'displayable' -- as per the Javadocs for Component.getGraphics?
The reason I'm trying to do this is so that I can then call getGraphics(), and pass that to my 'rendering strategy' for the component.
I've tried adding a ComponentListener, but componentShown doesn't seem to get called. Is there anything else I can try?
Thanks.
And additionally, is it OK to keep hold of the Graphics object I receive? Or is there potential for a new one to be created later in the lifetime of the Component? (e.g., after it is resized/hidden?)
Add a HierarchyListener
public class MyShowingListener {
private JComponent component;
public MyShowingListener(JComponent jc) { component=jc; }
public void hierarchyChanged(HierarchyEvent e) {
if((e.getChangeFlags() & HierarchyEvent.SHOWING_CHANGED)>0 && component.isShowing()) {
System.out.println("Showing");
}
}
}
JTable t = new JTable(...);
t.addHierarchyListener(new MyShowingListener(t));
You can listen for a resize event. When a component is first displayed, it is resized from 0,0 to whatever the layout manager determines (if it has one).
You need to check up the component hierarchy. Check after AncestorListener.ancestorAdded is called.
I've always used Coomponent.addNotify to know when the component is ready to be rendered.Not sure if is the the best way, but it works for me. Of course you must subclass the component.
Component.isDisplayable should be the right answer but I know it didn't worked for me as I thought it will(I don't remember why, but there was something and I switched to addNotify).
Looking in the SUN's source code, I can see addNotify fires a HierarchyEvent.SHOWING_CHANGED so this is the best way to be notified.
Sometimes we encounter an SWT composite that absolutely refuses to lay itself out correctly. Often we encounter this when we have called dispose on a composite, and then replaced it with another; although it does not seem to be strictly limited to this case.
When we run into this problem, about 50 % of the time, we can call pack() and layout() on the offending composite, and all will be well. About 50 % of the time, though, we have to do this:
Point p = c.getSize();
c.setSize(p.x+1, p.y+1);
c.setSize(p);
We've had this happen with just about every combination of layout managers and such.
I wish I had a nice, simple, reproducible case, but I don't. I'm hoping that someone will recognize this problem and say: "Well, duh, you're missing xyz...."
Looks to me like the layout's cache is outdated and needs to be refreshed.
Layouts in SWT support caches, and will usually cache preferred sizes of the Controls, or whatever they like to cache:
public abstract class Layout {
protected abstract Point computeSize (Composite composite, int wHint, int hHint, boolean flushCache);
protected boolean flushCache (Control control) {...}
protected abstract void layout (Composite composite, boolean flushCache);
}
I'm relatively new to SWT programming (former Swing programmer), but encountered similar situations in which the layout wasn't properly updated. I was usually able to resolve them using the other layout methods that will also cause the layout to flush its cache:
layout(boolean changed)
layout(boolean changed, boolean allChildren)
In the meantime I learned a little more about SWT's shortcomings when changing or resizing parts of the control hierarchy at runtime. ScrolledComposites and ExpandBars need also to be updated explicitly when the should adapt their minimal or preferred content sizes.
I wrote a little helper method that revalidates the layout of a control hierarchy for a control that has changed:
public static void revalidateLayout (Control control) {
Control c = control;
do {
if (c instanceof ExpandBar) {
ExpandBar expandBar = (ExpandBar) c;
for (ExpandItem expandItem : expandBar.getItems()) {
expandItem
.setHeight(expandItem.getControl().computeSize(expandBar.getSize().x, SWT.DEFAULT, true).y);
}
}
c = c.getParent();
} while (c != null && c.getParent() != null && !(c instanceof ScrolledComposite));
if (c instanceof ScrolledComposite) {
ScrolledComposite scrolledComposite = (ScrolledComposite) c;
if (scrolledComposite.getExpandHorizontal() || scrolledComposite.getExpandVertical()) {
scrolledComposite
.setMinSize(scrolledComposite.getContent().computeSize(SWT.DEFAULT, SWT.DEFAULT, true));
} else {
scrolledComposite.getContent().pack(true);
}
}
if (c instanceof Composite) {
Composite composite = (Composite) c;
composite.layout(true, true);
}
}
A composite's layout is responsible for laying out the children of that composite. So if the composite's size does not change, but the relative positions and sizes of the children need to be updated, you call layout() on the composite. If, however, the size or position of the composite itself needs to be updated, you will have to call layout() on its parent composite (and so on, until you reach the shell).
A rule of thumb: If you have added or removed a control, or otherwise done something that requires a relayout, walk up the widget hierarchy until you find a composite with scrollbars and call layout() on it. The reason for stopping at the composite with scrollbars is that its size will not change in response to the change - its scrollbars will "absorb" that.
Note that if the change requiring a layout is not a new child, or a removed child, you should call Composite.changed(new Control[] {changedControl}) before calling layout.
I have just become aware of Composite.changed(Control[] children). There is an extensive article which I read a couple years ago:
http://www.eclipse.org/articles/article.php?file=Article-Understanding-Layouts/index.html
This article also mentions to call Composite.layout(boolean changed, boolean all) to update the layout: "Calling layout() is the same as calling layout(true) which tells the ColumnLayout to flush its caches before setting the bounds of the children." That is all correct and what I have been doing ever since. But it is not what one wants, since it basically defeats the benefit of the layout cache when you want to update the layout because one or a few controls have changed requirements.
Imagine you have a bunch of StyledText widgets in a GridLayout and you need to change the size of one of them. Calling computeSize() on StyledText is very expensive. Instead of this:
Wrong:
parent.layout(true);
... which calls computeSize() on all children, even though their requirements have not changed. You should do this:
Right:
parent.changed(new Control[] { theChangedChild });
Then either
rootComposite.layout(false, true);
or
parent.layout(false);
Not very intuitive. The parameter to layout() is just badly named. Instead of calling it "changed" it should have been called "ignoreCache" or something. The intuitive thing is to pass "true" when something changed. Instead you need to pass "false", but invalidate the cache for just the changed Control with changed() before you do...
Note that calling changed() will also recursively invalidate the cache for just the parent Control in its own parent, which totally makes sense. So when you call layout(), you should call it on the root composite (most often the Shell) with all=true, unless you know that the size of the parent of the changed control will or can not change in response.