Consistent setIsEnabled()

Just a little feature request for you to ponder :slight_smile:

I would like to see setIsEnabled() become a function of Element.

I had the need to ‘disable’ a ScrollPanel. This is currently rather involved, requiring all of the controls used by the scrollpane are accessable, and disabling has some effect Each control must be found individually and have its state changed.

Instead, maybe something like the following in Element …

[java]

// For calling by user
public void setEnabled(boolean enabled) {
if(enabled != this.enabled) {
this.enabled = enabled;
onEnableStateChanged();
}
}

public boolean isEnabled() {
return enabled;
}

// This one is called internally when determining if the control is actually disable, or
// any of its parents
public boolean isEnabledInTree() {
Element parent = getElementParent();
return enabled && ( parent == null || parent.isEnabledInTree() );
}

// Subclasses can override and update their appearance here, maybe add another hook method for this
protected final void configureEnableState() {
if(isEnabledInTree()) {
// Un grey control, enable events or whatever
}
else {
// Grey out control somehow, disable events whatever
}
for(Element el : getElements()) {
el.configureEnableState();
}
}
[/java]

By doing this or similar, disabling an entire hierarchy with a single call would be possible. Current API usage is also preserved.

@rockfire said: Just a little feature request for you to ponder :)

I would like to see setIsEnabled() become a function of Element.

I had the need to ‘disable’ a ScrollPanel. This is currently rather involved, requiring all of the controls used by the scrollpane are accessable, and disabling has some effect Each control must be found individually and have its state changed.

Instead, maybe something like the following in Element …

[java]

// For calling by user
public void setEnabled(boolean enabled) {
if(enabled != this.enabled) {
this.enabled = enabled;
onEnableStateChanged();
}
}

public boolean isEnabled() {
return enabled;
}

// This one is called internally when determining if the control is actually disable, or
// any of its parents
public boolean isEnabledInTree() {
Element parent = getElementParent();
return enabled && ( parent == null || parent.isEnabledInTree() );
}

// Subclasses can override and update their appearance here, maybe add another hook method for this
protected final void configureEnableState() {
if(isEnabledInTree()) {
// Un grey control, enable events or whatever
}
else {
// Grey out control somehow, disable events whatever
}
for(Element el : getElements()) {
el.configureEnableState();
}
}
[/java]

By doing this or similar, disabling an entire hierarchy with a single call would be possible. Current API usage is also preserved.

This is a must. It should have been this way to begin with, but enable/disable were a feature request for Button way back when and semi-migrated to a few other controls.

This one will definitely make the high-priority list as it should have been done already… if not for my short-sightedness :wink:

Awesome, glad it’s on the plan! I’m not sure what sort of visual effect you should get on all the other controls. I think in most case some kind of darkening would suffice. I notice with Button, this is actually the “pressed” effect. I always thought this was a little odd. You can see cases where you might want buttons to “light up” when pressed, but then that means a disabled button would be highlighted.

While on the subject, there is another oddity with enabling disabling that maybe can be addressed at the same time. If you disable a button during that buttons onButtonMouseLeftUp (and others probably), it sort of goes a bit haywire. I can’t exactly describe the behavior, so will try and get a test case together. I think it’s something to do with the effects.

@rockfire said: While on the subject, there is another oddity with enabling disabling that maybe can be addressed at the same time. If you disable a button during that buttons onButtonMouseLeftUp (and others probably), it sort of goes a bit haywire. I can't exactly describe the behavior, so will try and get a test case together. I think it's something to do with the effects.

I think I know why this would happen. I’ll make sure this gets resolved with the update.

@rockfire
Need to discuss the recursive searches for enabled/disabled settings. This doesn’t quite work as expected once you nest controls… or perhaps it does. It’s the recursive search through the parent chain that I was hoping you could confirm and post a quickrundown on what you expect to have happen when you:

  • create a window
  • add a scroll panel
  • add buttons, etc to the panel…

If I disable the window… everything under it should be disabled? Or is it still movable?

Anyways… I think I’ll need a bit of help deciding on the default behavior of each control in this case… as I can think of multiple scenarios for each control that contradict the default behavior no matter what I choose as that behavior.

@rockfire
Actually, I’m thinking the recursive search upward is not necessary. If isEnabled is set recursively from the element down, everything will be enabled/disabled by simply calling window.setIsEnabled(true); It should never be necessary to query the parent elements, as the parent type could be anything and should have @Override setIsEnabled if the behavior of it’s child elements have some special case.

The important thing is determining if resize and movement is effected by this. And if so, the ability to override each of these on a per control basis. For instance:

I call setIsEnabled(false); on a window
I would like the dragbar to still be movable, but resize of the window should no longer function
But in most cases, moveable should no longer work either.

Anyways… bare with the random thought posts. Just don’t want to forget.

That a good question wrt Windows. In a Swing app, I believe this would disable all the content, not the window, as they are under control of the OS in general.

The recursive search should work fine with nested controls, but only once they are parented. This implies that if you have to run some code to do the actual disabling (probably changing visuals somehow), then that code would have to get run when the control is added to it’s parents (do you already have hooks for this?). Maybe some nicer code than mine would illustrate it better (it’s actually where I stole the pattern above from more or less). I use the Wicket web framework quite a lot, and this has setEnabled() in its top level “Component”.

Here are the relevant methods (i have stripped out some Wicket specific stuff) …

[java]

/**
 * @return true if this component is authorized to be enabled, false otherwise
 */
public final boolean isEnableAllowed()
{
	return isActionAuthorized(ENABLE);
}

/**
 * Gets whether this component is enabled. Specific components may decide to implement special
 * behavior that uses this property, like web form components that add a disabled='disabled'
 * attribute when enabled is false.
 * 
 * @return Whether this component is enabled.
 */
public boolean isEnabled()
{
	return getFlag(FLAG_ENABLED);
}

     /**
 * Sets whether this component is enabled. Specific components may decide to implement special
 * behavior that uses this property, like web form components that add a disabled='disabled'
 * attribute when enabled is false. If it is not enabled, it will not be allowed to call any
 * listener method on it (e.g. Link.onClick) and the model object will be protected (for the
 * common use cases, not for programmer's misuse)
 * 
 * @param enabled
 *            whether this component is enabled
 * @return This
 */
public final Component setEnabled(final boolean enabled)
{
	// Is new enabled state a change?
	if (enabled != getFlag(FLAG_ENABLED))
	{
		// Tell the page that this component's enabled was changed
		if (isVersioned())
		{
			final Page page = findPage();
			if (page != null)
			{
				addStateChange();
			}
		}

		// Change visibility
		setFlag(FLAG_ENABLED, enabled);
		onEnabledStateChanged();
	}
	return this;
}

void clearEnabledInHierarchyCache()
{
	setRequestFlag(RFLAG_ENABLED_IN_HIERARCHY_SET, false);
}

void onEnabledStateChanged()
{
	clearEnabledInHierarchyCache();
}

/**
* Calculates enabled state of the component taking its hierarchy into account. A component is
* enabled iff it is itself enabled ({@link #isEnabled()} and {@link #isEnableAllowed()} both
* return true), and all of its parents are enabled.
*
* @return true if this component is enabled
*/
public final boolean isEnabledInHierarchy()
{
if (getRequestFlag(RFLAG_ENABLED_IN_HIERARCHY_SET))
{
return getRequestFlag(RFLAG_ENABLED_IN_HIERARCHY_VALUE);
}

	final boolean state;
	Component parent = getParent();
	if (parent != null && !parent.isEnabledInHierarchy())
	{
		state = false;
	}
	else
	{
		state = isEnabled() && isEnableAllowed();
	}

	setRequestFlag(RFLAG_ENABLED_IN_HIERARCHY_SET, true);
	setRequestFlag(RFLAG_ENABLED_IN_HIERARCHY_VALUE, state);
	return state;
}

[/java]

This actually does some other funky stuff like caching the calculated enabled state. Wicket is slightly different in the the enabled state will mostly likely get pulled at the time of rendering, rather than earlier as would be likely with Tonegod.

It also has the concept of ‘isEnabledAllowed’. This is because in Wicket you often override the “isEnabled” method to calculate the state. The isEnableAllowed allows external code to override these overrides. I don’t think anything like that will be needed here.

I think you are right though, actual behavior should be taken case by case. In general, all input should always be blocked as soon as a control or any of it’s parents are disabled. With Swing, not all components actually visually change themselves when disabled. For example, disabling a Table stops you doing anything with it, but it still looks the same. I can’t think of a good reason for this though, nearly everything else “greys” itself out.

The upwards search is there so that if say …

  1. You have a parent that is enabled, but ONE of its children is set to disabled for other reasons.
  2. Another action then disables the parent.
  3. Another action then enables the parent.
  4. Without that backward search technique, the child will now be enabled too

I would prefer the framework to deal with this, rather than having to override behaviour each time. But if that’s not possible, could live with it :slight_smile:

Regarding movement … I confess I haven’t really considered this. I think I agree in that it should stop movement. Currently disabling the buttons in a VScrollBar doesn’t stop the slider being moved, so yeh … that would make sense

@rockfire said: I would prefer the framework to deal with this, rather than having to override behaviour each time. But if that's not possible, could live with it :)

I would as well! Soooo…

Not sure I totally follow the 4 use cases. The one that did click with me would be adding a control and making sure it’s enable state is that of it’s parent. But since enable/disable has a cascading effect, this would only need it’s direct parent.

Anyways, sorry for not understanding this fully yet. Could you expand on the 4 use cases so I can wrap my brain around them and avoid setting this up the wrong way? =)

Oh, no, that wasn’t 4 separate things, that was a whole process. I’ll expand on it a bit, although as I read this thread I think you have already said the same thing

[java]

Element container = new Element(screen);
CheckBox checkBox1 = new CheckBox(screen);
container.addChild(checkBox1);
CheckBox checkBox2 = new CheckBox(screen);
container.addChild(checkBox2);

// At this point everything is enabled.
// Now let’s disable checkBox2.

checkBox2.setIsEnabled(false);

// At this point, everything is fine. The single
// checkbox is disabled. Now lets disable the
// container

container.setIsEnabled(false);

// Everthing is still fine. The single checkbox
// is disabled, and so is the container. Now
// let’s re-enable the container.

container.setIsEnabled(true);

// Now checkBox2 has lost it’s state and is
// renabled. This means we have to manually
// disable it again.

checkBox1.setIsEnabled(false);
[/java]

So this is all about stopping the child control losing its disabled state when it is cascaded.

EDIT:

I just checked Swing behaviour, and it works the same. From the Javadocs …

"Sets whether or not this component is enabled. A component that is enabled may respond to user input, while a component that is not enabled cannot respond to user input. Some components may alter their visual representation when they are disabled in order to provide feedback to the user that they cannot take input.

Disabling a component does not disable its children"