Swing: restrict keyboard focus traversal to specific widgets - java

I have a JFrame containing the usual assortment of panels and widgets, and I have a JPanel which I'm using as that JFrame's glassPane. I'd like to be able to restrict keyboard focus traversal to the components in the glassPane when it's visible.
My problem may or may not be compounded by the fact that a background thread launches a process which results in a progress dialog appearing and subsequently disappearing, which steals the focus from a widget in my glassPane but returns it to some widget beneath my glassPane.
I've tried setting the JFrame's focus traversal policy to one which only allows the glassPane to be focused, but that didn't seem to have any effect. (maybe I did it incorrectly?)
Any help would be appreciated.

As mentioned in my comment, I would probably go for a J/X/Layer - but here's a solution if indeed a custom FTP is the only missing piece in your context.
To exclude components from focus traversal, reject them in the accept method of a custom FTP. Below is an example which rejects all components which are not children of the glassPane if the glassPane is visible (note: this is overly simplistic, as it handles direct children only, real-world code must walk up the parent chain until it either hit the glasspane or not)
public static class FTP extends LayoutFocusTraversalPolicy {
#Override
protected boolean accept(Component comp) {
JFrame window = (JFrame) SwingUtilities.windowForComponent(comp);
if (hasVisibleGlassPane(window)) {
return comp.getParent() == window.getGlassPane();
}
return super.accept(comp);
}
private boolean hasVisibleGlassPane(JFrame window) {
return window != null && window.getGlassPane() != null
&& window.getGlassPane().isVisible();
}
}

I'm going to make my comment an answer as I believe that it is the solution to your problem:
Perhaps a better option is to use a CardLayout to simply swap views rather than a glass pane or dialog since this situation does not seem the best suited for glass pane use. If you use CardLayout, you don't have to come up with a kludge (fiddle with focus traversal), to fix the side effects of another kludge (using glass pane for things it wasn't intended to be used for).
If you're not familiar with it, a CardLayout will allow you to easily swap components in a GUI, and is often used to swap JPanels that hold complex GUI's that occurs when the user goes from one major program state to another. I think that it would be perfect for your purposes and would prevent you from having to worry about your focus issue.

Try calling .setFocusable(false) on the JFrame you want to ignore (or at worst, on all the components within it) while the glassPane is visible.

When I had discussion with client he gave me the same requirement. So I decided to use the JLayer and LayeredPane in my project and with all of these components as a simple solution I implemented following code, may be it will help you in your project.
public YourConstructor() {
yes.addFocusListener(new FocusAdapter() {
public void focusLost(FocusEvent fe) {
if (!no.hasFocus()) {
no.requestFocusInWindow();
}
}
});
no.addFocusListener(new FocusAdapter() {
public void focusLost(FocusEvent fe) {
if (!yes.hasFocus()) {
yes.requestFocusInWindow();
}
}
});
}
#Override
public void setVisible(boolean visibility) {
super.setVisible(visibility);
if (visibility) {
yes.requestFocusInWindow();
}
}

To build upon kleopatra's answer and to help others in a similar situation; I had a bunch of custom swing components that I didn't want focused (sometimes only when they weren't editable). I ended up with this:
/**
* A custom focus traversal policy to make focus traversal inside a container to ignore some swing components.<br /><br />
*
* <b>Ignored components:</b><br />
* - <code>CustomComponent1</code> components<br />
* - <code>CustomComponent2</code> components that are not editable<br /><br />
*
* <b>Usage:</b><br /><br />
* <code>Container.setFocusTraversalPolicy(new CustomFocusTraversalPolicy());</code>
*/
public class CustomFocusTraversalPolicy extends LayoutFocusTraversalPolicy {
private static final long serialVersionUID = 1L;
protected boolean accept(Component c) {
if(c instanceof CustomComponent1) {
return false;
}
if(c instanceof CustomComponent2) {
CustomComponent2 t = (CustomComponent2) c;
if(!t.isEditable()) {
return false;
}
}
return super.accept(c);
}
}
Note that the policy needs to be set for each Container (I did it for each Window I was creating):
Window window = new JFrame(); // Or JDialog; both subclasses of Container and Window
window.setFocusTraversalPolicy(new CustomFocusTraversalPolicy());

Related

Panel background not being changed by UI manager

In my program I would like the user to be able to change the colour scheme of the program. I have a method which passes in a colour to set the background of all the panels using UIManager.
public void changeColourScheme(Color c) {
UIManager.put("Panel.background", c);
SwingUtilities.updateComponentTreeUI(this);
}
However the issue I'm running into is that it is not changing the colour of the panels. This method is located in the class for the JFrame.
Copied directly from Swing API Docs.
public static void updateComponentTreeUI(Component c)
A simple minded look and feel change: ask each node in the tree
to updateUI() -- that is, to initialize its UI property with the
current look and feel.
Notice the emphasize. Your code doesn't work because you are passing this as the argument to updateComponentTreeUI(). Since you are passing your panel as the argument, only the components inside the panel and the panel itself will get their UI updated. You have to pass the container that holds all other panels in your program, that is your JFrame
public void changeColourScheme(Color c) {
UIManager.put("Panel.background", c);
SwingUtilities.updateComponentTreeUI(frame); //instace of your frame
}
simply because the UIDefaults changes is only effective for the newly created components
for your feature to work you have to make the app restart then before starting the gui change the background in the defaults in the UIManager , or you will have to do it manually (panel by panel).
if you do it manually recursion will help a lot ,like this
private static void loopForPanel(Container c, Color col) {
synchronized (c.getTreeLock()) {
for (Component com : c.getComponents()) {
if (com instanceof JPanel) {
com.setBackground(col);
}
if (com instanceof Container) {
loopForPanel((Container) com,col);
}
}
}
}
then in your changeColourScheme method pass the frame it self for the loopForPanel method with your desired color.

Wicket AjaxIndicatorAppender across panels

I have 2 panels on one page and in first panel I call Ajax link and do some back end operations with long duration. In another panel I want show AjaxIndicatorAppender.
If I use AjaxIndicatorAppender with listener in the first panel, everything work fine, but if I use it in panel 2, it does nothing. Any help how call Ajax in panel 1 and show the Ajax indicator in panel 2? I am lost.
In panel1 (menu panel):
modal1.setWindowClosedCallback(new ModalWindow.WindowClosedCallback()
{
#Override
public void onClose(AjaxRequestTarget target)
{
some code.....
send(getPage(), Broadcast.DEPTH, new ContentUpdate(target));
etc...
in panel2 (content panel)
#Override
public void onEvent(IEvent<?> event) {
super.onEvent(event);
if (event.getPayload() instanceof ContentUpdate) {
AjaxRequestTarget target = ((ContentUpdate)event.getPayload()).getTarget();
ContentSection section = new ContentSection("content0",contentView.getCustomSectionList().get(0));
listItems.get("content0").replaceWith(section);
listItems.get("pagingNavigator0").replaceWith(new PagingNavigator("pagingNavigator0", section.getDataView()));
target.add(wmc);
}
}
How can I trigger listener in panel 2?
Thank you
I fixed this problem. My parent page can trigger IAjaxIndicatorAware too. I move this interface to my parent page and I call IndicatorMarkupId from the second panel.
Thank for help :)
The AjaxLink should implement IAjaxIndicatorAware interface and #getAjaxIndicatorMarkupId() should return the markup id of the HTML element with the loading indicator in Panel2.
But this way you would couple Panel1 with Panel2. Since you use Wicket Event mechanism I guess you try to keep them decoupled.
Probably you need to show a page-wide loading indicator instead.

Finding and retrieving a component from its container

In my swing-based UI, I have a JMenuBar which contains a a series of JMenu and JMenuItem objects. One of the menu-item objects also happens to be a JCheckBoxMenuItem.
Now, while the user can click on this JCheckBoxMenuItem in order to toggle the state of an application level setting, the user (in my application) also has access to a command line API to change the application setting. The details of this command line API are not relevant.
My question is this: When the user goes through the command line API and toggles the state of the setting (a static property / setting that applies to all open instances of my application), I would like to update the "checked / unchecked" property on the JCheckBoxMenuItem. To do this, I can either:
Store a reference to the checkboxmenuitem.
Traverse the JMenu container hierarchy to find the checkboxmenuitem.
I don't want to use method 1 because in the future, if I have more of these checkboxmenuitems, then i'll have to hang on to a reference to each one.
Method 2 seems cumbersome because I need to do:
Component[] childComponents = menu.getComponents();
for(Component c:childComponents)
{
if(c.getName().equals("nameOfTheCheckBoxMenuItem"))
{
componentFound = c;
}
}
Is there a better / more efficient way to find a component in a component hierarchy? Is there maybe a better way to solve this problem in general (changing the state of the jcheckboxmenuitem when the value of a property in my application changes), using say, a PropertyChangeListener (Although my understanding is that these only work on "beans").
1) I'd suggest to use CardLayout for nicest and easiest workaround for multi_JPanel application
2) then you can imlements
add Action / ActionListener
ActionListener al = new ActionListener() {
public void actionPerformed(ActionEvent ae) {
if (myCheckBox.isSelected()) {
// something
} else {
// something
}
}
};
add ItemListener
ItemListener itemListener = new ItemListener() {
public void itemStateChanged(ItemEvent itemEvent) {
if (Whatever) {
// something
}
}
};

Set focus on a component with Apache Wicket?

How do you set focus on a component with Apache Wicket? Searching leads to very little information, mostly on setting the default field. I do not want to set a default field, rather I am looking to set focus when, for example, a specific radio button is selected.
I suggest using the native org.apache.wicket.ajax.AjaxRequestTarget#focusComponent(). For example:
/**
* Sets the focus in the browser to the given component. The markup id must be set. If
* the component is null the focus will not be set to any component.
*
* #param component
* The component to get the focus or null.
*/
org.apache.wicket.ajax.AjaxRequestTarget#focusComponent(Component component)
Once you create your behavior to set the focus, you should be able to add it to the component on any event, just make sure that component is part of the AjaxRequestTarget. I don't see why this wouldn't work...
myRadioButton.add(new AjaxEventBehavior("onchange") {
#Override
protected void onEvent(AjaxRequestTarget target) {
myOtherComponent.add(new DefaultFocusBehavior());
target.addComponent(myForm);
}
});
Here's a link that shows how to create the default focus behavior if you do not have one already:
http://javathoughts.capesugarbird.com/2009/01/wicket-and-default-focus-behavior.html
If you only want to setFocus through javascript and don't want to reload a form or a component, you can use the following code:
import org.apache.wicket.Component;
public class JavascriptUtils {
private JavascriptUtils() {
}
public static String getFocusScript(Component component) {
return "document.getElementById('" + component.getMarkupId() + "').focus();";
}
}
And then in any Ajax Method you can use:
target.appendJavascript(JavascriptUtils.getFocusScript(componentToFocus));
For a pop-up like modalWindow my workaround solution was to use the attribute "autofocus" on the first input tag.
An easy solution is to add it to the html directly.
<input ..... autofocus>
Another solution is to add it to the modalWindow itself:
#Override
public void show(AjaxRequestTarget target) {
super.show(target);
setUpFocus();
}
protected void setUpFocus() {
DeepChildFirstVisitor visitor = new DeepChildFirstVisitor() {
#Override
public void component(Component component, IVisit<Void> iVisit) {
if (isAutofocusable(component)) {
component.add(new AttributeAppender("autofocus", ""));
iVisit.stop();
}
}
#Override
public boolean preCheck(Component component) {
return false;
}
};
this.visitChildren(FormComponent.class, visitor);
}
protected boolean isAutofocusable(Component component) {
if (component instanceof TextArea ||
component instanceof DropDownChoice ||
// component instanceof RadioChoice ||
component instanceof AjaxCheckBox ||
component instanceof AjaxButton ||
component instanceof TextField) {
return true;
}
return false;
}
RadioChoice is commented out because this solution is not working on that. For RadioChoice i would recommend to implement a FocusedRadioChoice:
public class FocusedRadioChoice<T> extends RadioChoice<T> {
//constructors...
#Override
protected IValueMap getAdditionalAttributes(int index, T choice) {
super.getAdditionalAttributes(0, choice);
AttributeMap am = new AttributeMap();
am.put("autofocus", "");
return am;
}
}
Is there a way to achieve the same without JavaScript?
(I am implementing a form with a feedback-Panel that only comes up when Javascript is turned off, so it would not make sense to depend on JavaScript there...,-)
I could only find answers which use JS .focs()... maybe Wicket 1.5 will provide a method Component.setFocus()...
If you happen to be using an Ajax button, you can simply call target.focusComponent(myComponent); in the button's onSubmit method.
#martin-g 's solution was the only solution that got it working for my scenario - a modal/pop up.
Note:
I think autofocus embedded explicitly in HTML only works on page load, not modal load so any efforts to skillfully set the autofocus attribute in the HTML of a modal just fail miserably - always.
Here I lay out the steps for setting the focus on an input field called 'myInput' using the full power of Wicket (no JS!):
In onInitialize:
// Make sure the field has an ID in markup
myInput.setOutoutMarkupId(true);
Provide an overridden show method where you call the focusComponent method:
public void show(AjaxRequestTarget target)
{
// Make sure you call the super method first!
super.show(target);
target.focusComponent(myInput);
}
This does require that your component is an attribute of your modal content class so that you can access it in the show method. To avoid creating a class attribute for your input component you could blend this solution with the solution from BlondCode by replacing that solution's
component.add(new AttributeAppender("autofocus", ""));
with
target.focusComponent(component);
This also works!

What Java Swing event can be used to know when application has finished starting?

I'm looking for a place to hook some code to programmatically create, size and position a JPanel after the application has finished loading.
I'm just starting with Java. I'm using NetBeans 6.5.1 with jdk1.6.0_13. I've used the new project wizard to create a basic Java/Swing desktop application. This is a SingleFrameApplication that uses a FrameView with a central main JPanel where all the UI elements are placed.
I first tried my code in the FrameView constructor but when I try to arrange my JPanel based on the bounding rectangle of one of the design time controls I added to the UI, that control has not yet finished being positioned and sized so I'm getting all zeros for the coordinates.
I've verified my code works as expected by calling it from a click event after the application has loaded so my problem is finding a way to know when everything is finished being sized and arranged.
I also tried the componentShown event from the main JPanel but I later read that is only fired if setVisible is explicitly called which apparently doesn't happen during normal application startup.
Can anyone provide some pointers? Thanks.
Update:
In addition to what I mention in my answer below, I also read about the Application.ready() method. This would also be a point in time of interest for knowing when the UI part of an application is finished doing everything it needs to do. Communicating to my view from the application seemed a bit messy though.
The solution I went with was actually a combination of the answers from Charles Marin and JRL (I upvoted both of your answers for credit, thanks).
I had my FrameView class implement WindowListener.
...
public class MyView extends FrameView implements WindowListener
...
and in my FrameView constructor I added a listener to the application's main frame.
...
getFrame().addWindowListener((WindowListener) this);
...
Then in my implementation of windowActivated I could call the code I had to arrange and size a control on the main JPanel based on the location and size of other controls.
public void windowActivated(WindowEvent e)
{
// The application should now be finished doing its startup stuff.
// Position and size a control based on other UI controls here
}
I think you want WindowActivated. Have a look at this part of the tutorial.
I'd try using getFrame().isValid()
I assume this is the WYSIWYG editor thing. I'm looking at NetBeans 6.1, so your experiences may vary.
The traditional way to layout Swing components is by using a LayoutManager (or LayoutManager2). According to the NetBeans help, the visual editor supports these so long as they don't require support for constraints.
The procedure goes something like this:
Create a new JavaBean and have it implement LayoutManager (a BeanInfo is required too for palette support - you can create one by right-clicking the bean class)
Build the project
Right-click the bean and choose Tools > Add to Palette... and add it
Right-click the panel for which you want to set the layout and select Set Layout > Your Bean Name
You may find the design-time experience somewhat lacking.
A sample layout implementation:
public class StepLayoutBean extends Object implements Serializable, LayoutManager {
public void addLayoutComponent(String name, Component component) {
}
public void layoutContainer(Container container) {
Dimension space = container.getSize();
int xoffset = 0;
int yoffset = 0;
for (Component kid : container.getComponents()) {
Dimension prefSize = kid.getPreferredSize();
if (prefSize.width + xoffset > space.width) {
xoffset = 0;
}
Rectangle bounds = new Rectangle(xoffset, yoffset, prefSize.width, prefSize.height);
kid.setBounds(bounds);
xoffset += prefSize.width;
yoffset += prefSize.height;
}
}
public Dimension minimumLayoutSize(Container container) {
Dimension size = new Dimension();
for (Component kid : container.getComponents()) {
Dimension minSize = kid.getMinimumSize();
size.width = minSize.width > size.width ? minSize.width : size.width;
size.height += minSize.height;
}
return size;
}
public Dimension preferredLayoutSize(Container container) {
Dimension size = new Dimension();
for (Component kid : container.getComponents()) {
Dimension prefSize = kid.getPreferredSize();
size.width += prefSize.width;
size.height += prefSize.height;
}
return size;
}
public void removeLayoutComponent(Component component) {
}
}
If a custom layout doesn't fit the bill, have a look at the event bindings under the component's properties panel - though resizing that way might be a recipe for some kind of recursive event storm.

Categories