Disable AND grey out an SWT composite
I have a Composite
that I want to be able to开发者_开发知识库 enable/disable programatically. The Control.setEnabled(boolean enabled)
method works fine, but it does not give any visual information that the widget(s) are disabled.
What I would like to do is to have the disabled state mean the widgets are greyed out. Right now they just enter a weird state where the user is unable to click or perform any action on them.
A Composite is a container control that hold other controls using a layout - you can't see a composite really, you can only see the controls it holds. To disable and visually see then disabled, you'll have to call setEnabled(false)
on all the children, assuming they're not containers too. Basically, to have to enable/disable the leaf widgets and you will see visual indication.
The reason you can't do anything with the widgets when disabling the Composite is because the Composite is eating all the events. Although the child widgets are not getting the events forwarded, they know nothing about the state of their parent, so they aren't greyed out.
The problem was indeed that I was disabling the composite and not the controls inside it. What I ended up doing was something like this:
public void recursiveSetEnabled(Control ctrl, boolean enabled) {
if (ctrl instanceof Composite) {
Composite comp = (Composite) ctrl;
for (Control c : comp.getChildren())
recursiveSetEnabled(c, enabled);
} else {
ctrl.setEnabled(enabled);
}
}
The other solutions posted here are pretty primitive. They have several shortcomings:
- Even if a control is disabled to start with, it will become enabled if its control tree is disabled and then enabled. You probably want to keep such a control disabled.
- Sometimes nested controls should remain enabled when their control tree is disabled.
- It is useful to distinguish between two different disabled states:
- Disabled state, with no information to show. This should be clearly visually indicated to the user.
- Displaying information, but read-only state. It is useful to be able to copy text in text fields in this state.
The code below solves these problems. It is the ultimate enabler/disabler for SWT.
It keeps track of the modified controls by tagging them with Widget.setData
, so that it only enables the contols it previously has disabled. It handles different kinds of controls differently in tree states: DISABLED
, READ_ONLY
and EDITABLE
.
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.forms.widgets.ExpandableComposite;
public class GuiEnabler {
/**
* Used to set the enable state of a tree of controls.
*/
public enum EnableState {
/**
* The control is disabled, for when there is no information to show in
* it. All controls, including labels, are disabled.
*/
DISABLED,
/**
* For when there is information to show in the control, but it should
* be read-only. Controls are disabled, except Text which is
* non-editable, and Lables, which are enabeled.
*/
READ_ONLY,
/**
* All controls are enabled and editable.
*/
EDITABLE
}
private static final String ENABLED_KEY = GuiEnabler.class.getName() + ".disabled";
private static final String EDITABLE_KEY = GuiEnabler.class.getName() + ".read_only";
/**
* Disables or makes read-only {@code control} and all its child controls (recursively).
* Also restores the state of controls previously disabled by this method. The action
* performed on the controls is determined by {@link EnableState enableState}.
*
* @param excluded These controls (and their children) are not modified by
* the method.
*/
public static void recursiveUpdateEnableState(Control control, EnableState enableState, Control... excluded) {
updateEnabledState(control, enableState, new HashSet<>(Arrays.asList(excluded)));
}
/**
* See {@link GuiEnabler#recursiveUpdateEnableState(Control, EnableState, Control...)}.
*/
public static void updateEnabledState(Control control, EnableState enableState, Set<Control> excluded) {
if (excluded.contains(control)) {
return;
} else if (control instanceof ExpandableComposite) {
updateEnabledState(((ExpandableComposite) control).getClient(), enableState, excluded);
} else if (control instanceof Composite && !(control instanceof Combo)) {
for (Control child : ((Composite) control).getChildren()) {
updateEnabledState(child, enableState, excluded);
}
} else {
updateControl(control, enableState);
}
}
/**
* Updates a single control to have its proper state for enableState.
*/
private static void updateControl(Control control, EnableState enableState) {
if (enableState == EnableState.DISABLED) {
makeDisabled(control);
} else if (enableState == EnableState.READ_ONLY) {
if (control instanceof Text) {
makeNonEditable((Text) control);
makeEnabled(control);
} if (control instanceof Label) {
makeEnabled(control);
} else {
makeDisabled(control);
}
} else if (enableState == EnableState.EDITABLE) {
makeEnabled(control);
if (control instanceof Text) makeEditable((Text) control);
}
}
private static void makeEnabled(Control control) {
if (control.getData(ENABLED_KEY) != null) {
control.setData(ENABLED_KEY, null);
control.setEnabled(true);
}
}
private static void makeDisabled(Control control) {
if (control.getEnabled()) {
control.setData(ENABLED_KEY, "marked");
control.setEnabled(false);
}
}
private static void makeEditable(Text text) {
if (text.getData(EDITABLE_KEY) != null) {
text.setData(EDITABLE_KEY, null);
text.setEditable(true);
}
}
private static void makeNonEditable(Text text) {
if (text.getEditable()) {
text.setData(EDITABLE_KEY, "marked");
text.setEditable(false);
}
}
}
One limitation of this is that even in disabled state it is still possible to change the active tab in a TabFolder
control.
In other words, you need to write code like this, given a Composite c
:
for (Control child : c.getChildren())
child.setEnabled(false);
精彩评论