Skip to main content

ComponentVisitors?

34 replies [Last post]
leouser
Offline
Joined: 2005-12-12

howdy,

I thought Id toss this out here since Im discussing using this as part of the solution to a specific bug. The idea is simple:
1. Add the ability to recursively travel a Component hierarchy with a Visitor.

say for example, the user wants to disable all the widgets in a Hierarchy but also wants to restore them to their enabled state. The user could create a Visitor instance and have it travel the hierarchy turning off each widget but also keeping track of its previous state. Upon deciding that its time to restore things, he configures his Vistor to restore the previous state and sends the Vistor down the hierarchy again.

I think a positive of this capability would be that it would make it simpler to define new operations that need to be performed on groups of components without having to write component tree walking code, or in the case where its seen as something that is standard for the JDK you wouldn't need to add a new method for the enmasse change.

Id think the interface would need to have two methods:
visitDescending(Component c)
visitAscending(Component c)

Descending would be called upon the first contact with the visitor.

Ascending would be called upon after the first contact and after the children have been visited.

This way the user would have control as to when he wants his operations to be performed.

thoughts folks?
leouser

Reply viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
Richard Bair

> I suppose there is an option to do it internally that would require
> the user to configure the Component to get the behavior your
> looking for.
>
> setConstrainedEnabled(boolean value);
> getConstrainedEnabled();

Yes, that's one way. I'm still not sure we can't just change
isEnabled directly without having a compatibility flag, of sorts, but
assuming we have to support the old behavior, then yes this is a good
way to do it. I guess in the end that's Scott's call.

> Ill investigate this path and see if it holds fruit. Though this
> does raise the question of how best to turn it on for a set of
> widgets. Maybe the CPV is the answer for that. :D

I think having a hierarchy walker would be nice, irrespective of
whether it used for this particular problem or not. I don't know if
it belongs in the JDK, exactly, but it would make for a nice set of
utility methods.

Richard

leouser
Offline
Joined: 2005-12-12

'
> Ill investigate this path and see if it holds fruit. Though this
> does raise the question of how best to turn it on for a set of
> widgets. Maybe the CPV is the answer for that. :D

I think having a hierarchy walker would be nice, irrespective of
whether it used for this particular problem or not. I don't know if
it belongs in the JDK, exactly, but it would make for a nice set of
utility methods.

Richard'

that part is hard for me the decide myself. :D It is beneficial in the sense:
1. The user would never have to write tree walking code again, they could just visit. There may be code reduction here for each walk, not sure yet. But it would save the user from having to think about, which may make them more productive.

2. It may enable quick ways to do things. I was able to whip up a "color everything green" piece of code rather quickly by using a CPV. Im sure people who want blinking components would love this thing.

3. Allows user to focus on their operations. From looking at my code I didn't have to think about which widget was next, I could just do the work I wanted to do.

anyway, it needs to pay for itself. Right now the cost would be:
1. new method.
2. new interface.
3. Any utility implementations(maybe 0?).

that's at the minimum.
BH

leouser
Offline
Joined: 2005-12-12

This is from JComponent:
public void setEnabled(boolean enabled) {
boolean oldEnabled = isEnabled();
super.setEnabled(enabled);
firePropertyChange("enabled", oldEnabled, enabled);
if (enabled != oldEnabled) {
repaint();
}
}

now if we have the patch in place where isEnabled reports false because its parent is reporting false and you subsequently call setEnabled(false):

a. The data will component will probably become truly false.

b. firePropertyChange will just return since the Component code doesn't fire an event if things haven't changed. Though the JComponent may really be enabled, it won't be able to discern a difference. This one appears very problematic. I sure would like to know why the blasted JButton is still pressable, this is a possibility. For a user it may be a headache.

c. repaint won't occur. That probably isn't a big problem given that from what Ive seen the visuals are ok.

BH

Richard Bair

> Now, if we swap items 8 and 4 in the list and do changes with
> setEnabled after the ComponentPropertyVisitor has done its work and
> subsequently restore it with ComponentPropertyVisitor, well of
> course the first post-visitor visit setEnabled call isn't going to
> be reflected. That part I don't feel bad about, given that the
> user will need to understand what the ComponentPropertyVisitor does
> when it restores things.

This is my point exactly. Of course the visitor approach can restore
the original state, the question is what happens when the parent
panel is disabled while the original state of the text field is
enabled, and then I want to disable the component such that when the
parent panel is re-enabled the text field stays disabled, as if its
original state had been disabled (wow, how's that for a runon
sentence?). This use case isn't made up, I ran into it while trying
to implement this enablement functionality in SwingX. The problem
goes like this:

Say I have a panel full of components. Each component is bound to
some bit of domain data (like a customer first name, last name, etc
as part of a customer table in the database). The binding framework
*automatically* disables components that are bound to data that is
read only. So, for example, I may have a null value in one of my
columns and in my application that means I want the field to be read
only (these policies are pluggable in the binding framework -- the
point is the binding framework is managing the enabled state of the
component). Now, I kick off some long running task and disable the
panel while the long running task is in process. When the process is
finished, I re-enable the panel and all of the components need to
assume their proper state: perhaps first name and last name are read/
write but "nick name" is now readonly.

In this scenario having a visitor doesn't make any sense. The binding
framework would have to be aware of the visitor in order to tell it
what the "original" state of the components need to be so that when
the parent panel is re-enabled all the components will be "restored"
to their proper states (not necessarily the states they were in
originally when the parent panel was disabled).

This is why I say using a visitor isn't going to work. Sure, it can
be made to work, but it isn't going to be pretty since it would
require that anybody with the above use case is going to have to be
passed a reference to the visitor in order to mess with the
"original" enabled states of the components.

I think you'll be much better off by changing the components
themselves, such that "isEnabled()" returns true iff the component is
enabled and the component's parent is enabled:

public boolean isEnabled() {
Component parent = getParent();
return enabled && (parent == null || parent.isEnabled());
}

Assuming I haven't goofed my boolean logic :-). Naturally, there are
probably nefarious corner cases to be dealt with, but I think this
general approach will be more natural, require fewer additions/
changes to the exiting API, and will work well in any situation.

Richard

leouser
Offline
Joined: 2005-12-12

The problems I have with doing it internally is that it alters a fundamental relationship that has existed forever(to my knowledge). If it was day one, then the problem could be dealt with this way, well I might keep it still this way since the freedom is interesting. There are going to be folks out there exploiting the fact that you can have a enabled component inside of a disabled component. On one hand its a bug/pain for some, for others its a capability.

One of the test cases for a CPV Ive written exploits the fact you can have different values for the enabled property. The CPV visits each Component setting the enabled state to false except for a Component that has been marked as not to be touched. What you end up with is a JFrame with a certain section enabled and the rest disabled. The enabled portion resides within a disabled JPanel and appears to be quite happy with the situation.

Thats just an illustration of what Im writing about. Altering the behavior at this point takes away that freedom. The management of the problem needs to be layered on top of the Components not within.

BH

leouser
Offline
Joined: 2005-12-12

I suppose there is an option to do it internally that would require the user to configure the Component to get the behavior your looking for.

setConstrainedEnabled(boolean value);
getConstrainedEnabled();

when set to true, the Component will consult its parent with isEnabled to see if its parent isEnabled. If false, it returns false.

if the property is set to false then its buisness as usual and the user can keep his backward compatibility. It probably has to be false at the start to keep b.c. .

Ill investigate this path and see if it holds fruit. Though this does raise the question of how best to turn it on for a set of widgets. Maybe the CPV is the answer for that. :D

BH

leouser
Offline
Joined: 2005-12-12

uh-ohh:

this kind of code:
public boolean isEnabled() {
Component parent = getParent();
return enabled && (parent == null || parent.isEnabled());
}

which manifests itself in the Component patch as so:
final boolean isEnabledImpl() {
if(constrainedEnabled && getParent() != null){
return enabled && getParent().isEnabled();
}
else
return enabled;
}

has a nice visual effect in the test. Everything looks turned off. Except, the darn JButtons. When the mouse goes over them they respond. You can press them and they appear to be able to take focus. It appears that saying your not enabled has a effect painting wise, but behaviorally it isn't enough. From looking at JComponent, when the enabled state changes it fires off a property change event. This is probably why the JButtons are not becoming mouseless. There appear to be other problems as well, if the component is reporting that its not enabled and you set it to false, it wont fire a property change event as well which means state is going to get out of synch.

I guess these are some more reasons why changing the fundamentals around is dangerous in this case, even the JDK isn't going to handle enable based off of its parent enable state well.

BH

Richard Bair

> has a nice visual effect in the test. Everything looks turned
> off. Except, the darn JButtons. When the mouse goes over them
> they respond. You can press them and they appear to be able to
> take focus. It appears that saying your not enabled has a effect
> painting wise, but behaviorally it isn't enough. From looking at
> JComponent, when the enabled state changes it fires off a property
> change event. This is probably why the JButtons are not becoming
> mouseless. There appear to be other problems as well, if the
> component is reporting that its not enabled and you set it to
> false, it wont fire a property change event as well which means
> state is going to get out of synch.

Are you firing property change events for "enabled" when the parent's
enabled state changes? That has to be factored in as well, because as
far as external viewers are concerned, the "enabled" property has
changed when the parent changes, even if the internal state hasn't
changed.

So....

1 JPanel panel = new JPanel();
2 JTextField field = new JTextField();
3 panel.add(field);
4 panel.setEnabled(false);
--> At this point, a property change event is fired for panel, and
also for field. This won't happen automatically, you have to add code
to ensure this occurs. Note that according to the component contract,
since isEnabled() for the field returns a new value, a property
change event for the field component must be fired to indicate this
change.

The only way that improper state like you describe can occur (once
proper event notification is in place) is if code is reading the
enabled flag directly instead of going through the accessor method.
This does occur, if I remember correctly, in some AWT code, but
should be changed to go through the accessor.

Richard

leouser
Offline
Joined: 2005-12-12

Im skeptical about the change in property causing the weird mouse behavior. Ive put together a ComponentVisitor that fires the property change on each of the widgets. No affect there. This doesn't mean it shouldn't occur.

Im unsure if something is occuring in awt that is allowing the event to flow through. My new theory is that the fact that each widgets peer isn't being called is causing the problem. The enable and disable call on the peer happens when the enabled state changes. Im going to do some diggin g in the awt to see if there is the possibility of something bypassing the accessor.

BH

leouser
Offline
Joined: 2005-12-12

ok, scrap the whole AWT is the problem thing. :D

The problem with the Buttons is that in AbstractButton setEnabled is overriden so that it calls its models setEnabled to put it in sync with the JButton. So we are seeing a widget that looks disabled but its model which controls how it behaves with the mouse is still alive and well. :(

So even if we cover the fact of firing property events Im not sure how to cover Components that override setEnabled to do something vital when disabled. We could try and change this behavior so its property based but that doesn't help with the unknown widgets that do something similiar.

BH

Richard Bair

I know Amy looked into this whole problem space 5 years ago or
thereabouts, and couldn't see an easy solution either. We just have
to find all these edge cases and think it through. It could be that
there isn't any non-painful way to do it.

Richard

On May 30, 2006, at 3:02 PM, swing@javadesktop.org wrote:

> ok, scrap the whole AWT is the problem thing. :D
>
> The problem with the Buttons is that in AbstractButton setEnabled
> is overriden so that it calls its models setEnabled to put it in
> sync with the JButton. So we are seeing a widget that looks
> disabled but its model which controls how it behaves with the mouse
> is still alive and well. :(
>
> So even if we cover the fact of firing property events Im not
> sure how to cover Components that override setEnabled to do
> something vital when disabled. We could try and change this
> behavior so its property based but that doesn't help with the
> unknown widgets that do something similiar.
>
> BH
> [Message sent by forum member 'leouser' (leouser)]
>
> http://forums.java.net/jive/thread.jspa?messageID=117624

leouser
Offline
Joined: 2005-12-12

JMenuItem looks like it has some special processing within setEnabled as well. Not sure of its purpose, but it appears to have something to do with turning off navigation.

---------------
On an earlier note, from doing some investigation it appears doable to deal with this sequence:
Widgets: A, B, C (A holds B, B holds C):

B.setEnabled(false);
A.visit(cpv);//disables everything.
B.setEnabled(true);
cpv.restoringMode(true);
A.visit(cpv);
B.isEnabled() == true //doesn't revert to false because the mementos were updated

the key is to keep track of changes to the state. The memento holder is a PropertyChangeListener that is responsible for keeping track of the restore values. It gets its initial values from the first visit and subsequently updates itself when the watched property changes. When restoration occurs, it uses the stashed mementos + updates to restore the state. Ive got a modified test up using this. Anyway, the killer sequence appears overcomable. Too bad Component doesn't have a weaklyAddPropertyChangeListener, life would be very good in this case.

BH

leouser
Offline
Joined: 2005-12-12

JScrollBar overrides setEnabled as well. Its override just enables/disables its child components. So it appears not as necessary to ensure it gets called.

BH

leouser
Offline
Joined: 2005-12-12

ok, todays new idea is a synthesis of everything! :D

#1 We do not require the user to do any special processing on setEnabled. It is done internally. This should ease the pain of the user. Everything hinges on one component having setConstrainedEnable set to true.

#2. Internally if a widget is constrained, it will fire a visitor off that either:
1. Disables all the widgets
2. Restores the widgets enabled state to its field "resetValue".

"resetValue" is the value that is:
a. The enabled state of the widget before it was disabled in step 1.
b. The enabled state of the widget as it was changed the last time by setEnabled.

So for the user, all he has to do is decide if a widget should have its disabled state propogated to its children by calling:
setConstrainedEnabled(true);

then latter on everything is reduced to:
setEnabled(false);

and

setEnabled(true);

So in this sequence:
JPanel jp = new JPanel();
jp.setConstrainedEnable(false);
JButton a = new JButton();
JButton b = new JButton();
b.setEnabled(false);
jp.add(a); jp.add(b);
JPanel jp2 = new JPanel();
JButton c = new JButton();
jp2.add(c)
jp.add(jp2);
c.setEnabled(false);
jp.setEnabled(false);
c.setEnabled(true);
jp.setEnabled(true);

What is the value of "c"? "c" will be true. "b" on the other hand will retain its disabled status.

Maybe setConstrainedEnable is the wrong method name, in this scheme. Maybe something along the lines of the bug report title:
setPropagateDisable()
whose contract will something like:
"When set to true, will disable all components in its hierarchy when disabled. When the component is enabled:
1. The enabled state of the widget before it was disabled by this components setEnable will be restored.
2. The value of the enabled property set after the intial disabled call will be used instead of the previous value. Calling setEnabled on a sub component overwrites the history."
---------------------------------
This scheme is good in that it:
1. Doesn't require any modifcations to random widgets, given that it just enhances the setEnabled call.
2. The user only has to think about setEnabled and one previous configuration call to setPropagateDisable.

----------------------------------
Here is the modified setEnabled:
public void setEnabled(boolean b) {
boolean oldValue = isEnabled();
enable(b);
if(constrainedEnabled && constraining != true){
constraining = true;
if(b != true){
ComponentVisitor cv = new ComponentVisitorAdapter(){
public void descendingVisit(Component c){
if(c != Component.this)
c.setEnabled(false);
}
};
visit(cv);
}
else{
ComponentVisitor cv = new ComponentVisitorAdapter(){
public void descendingVisit(Component c){
if(c != Component.this)
c.setEnabled(c.resetValue);
}
};
visit(cv);
}
constraining = false;
}

if(constraining) resetValue = oldValue;
else resetValue = b;
}

boolean resetValue;
static boolean constraining;

public void setConstrainedEnabled(boolean b){
constrainedEnabled = b;
}

public boolean isConstrainedEnabled(){
return constrainedEnabled;
}
--------------------------
swap out the visit stuff for any tree walking code and the result should be the same. The test case I have together appears works just dandy as well. :)

BH

leouser
Offline
Joined: 2005-12-12

The contract should probably state something along the lines:
When propagating a disable and a subcomponent is encountered that also is configured to propagate the disable, the encountered widget's propagation mechanism will not be engaged.

This will help folks reason out what happens when a disable is propagated.

BH

Richard Bair

I think you're missing my point. Look again at this code sequence:

1 JPanel panel = new JPanel();
2 JTextField text = new JTextField();
3 panel.add(text);
4 panel.setEnabled(false);
5 text.setEnabled(false);
6 panel.setEnabled(true);

First: The developer disables the panel which also disables the text
field. We store the text fields original state so we can restore it
later

Second: The developer manually disables the text field. When the
panel is restored, this text field must remain disabled

Third: The developer restores the panel. Remember we had stored off
the original state of the text field as true, but in the second step
the developer stated that the text field should be false. So, when
the panel is re-enabled, the text component must be "restored" to false.

This code:

> Component c = ....something...
> ComponentVisitors.ComponentPropertyVisitor cpv = new
> ComponentVisitors.ComponentPropertyVisitor("enabled",
> false, true);
> c.visit(cpv);
> ///.....late on time to restore....
> cpv.setRestoring(true);
> c.visit(cpv);

Doesn't appear to address this concern.

Richard

leouser
Offline
Joined: 2005-12-12

the code does that already. If you have a component which is disabled and it is restored to its previous state it will be set as disabled.

1 JPanel panel = new JPanel();
2 JTextField text = new JTextField();
3 panel.add(text);
4 panel.setEnabled(false);
5 text.setEnabled(false);
6 panel.setEnabled(true);

7.ComponentPropertyVisitor cpv =
new ComponentPropertyVisitor("enabled", false, false);
8. panel.visit(cpv)
//--> at this point everything from the JPanel on downwards has had its enabled state set to false. Inside the ComponentVisitor instance, the previous state is stored.
9. cpv.setRestoring(true)
10. panel.visit(cpv)
// --> at this point we are back to the point where the text is disabled and the panel is enabled. The state has been restored.

One thing you may not be seeing is that on the initial visit, the ComponentVisitor keeps track of the state before changing it. When you set it to restoring mode and visit again, the state before the initial visit is restored. So your concern in #3 is that the JTextField will be not be restored to false is incorrect. Keeping track of the intitial state and being able to restore is part of the ComponentPropertyVisitor's job.

Now, if we swap items 8 and 4 in the list and do changes with setEnabled after the ComponentPropertyVisitor has done its work and subsequently restore it with ComponentPropertyVisitor, well of course the first post-visitor visit setEnabled call isn't going to be reflected. That part I don't feel bad about, given that the user will need to understand what the ComponentPropertyVisitor does when it restores things. If he really needs to he can command the ComponentPropertyVisitor to ignore the JTextField before the restoration. I am contemplating taking a larger snapshot at the time of the first visit. Instead of just stashing the state, stash the state and what its being changed to. During restoration, the code can then check what it has been changed to and possibly not restore if there is a discrepency.

BH

leouser
Offline
Joined: 2005-12-12

1 JPanel panel = new JPanel();
2 JTextField text = new JTextField();
3 panel.add(text);
4 MyEnablementVisitor v = new MyEnablementVisitor();
5 v.setValue(false);
6 v.visit(panel);
7 v.setValue(text, false); //ensures that the text stays disabled
when re-enablement occurs
8 v.setRestoring(true);
9 v.setValue(true);
10 v.visit(panel);
-----------------------------------
ok, in the current code I have together, 4 and 5 are one item. I am assuming that the text field is disabled before the visit. #9 isn't necessary, since the Visitor is in restoring mode it will only restore the widgets it has state for. At item 10, the widgets will be restored to their enabled state that existed during the first visitation. This will happen because the visitor is in restorative mode. Now if the user's intent is to just turn everything on, which is a very real possibility, #8 can be omitted and #9 kept. Upon the next visit all the widgets that are visitable will have their enabled property set to true. This is because:
a. The visitor is not in restorative mode.
b. The value has been set to true.

What I like about this flexibility is that it will allow the user to have at least 2 strategies when they change the state again:
1. Restore everything.
2. Reset the state again to something else.

BH

Here's the current state of the ComponentPropertyVisitor class:
package java.awt;

import java.beans.*;
import java.util.*;
import java.lang.reflect.*;

/**
* Class that contains standard visitors to use on Components.
*/
public final class ComponentVisitors{

public static class ComponentPropertyVisitor implements ComponentVisitor{

boolean restoring;
boolean ascend;
T value;
Map state;
Map alternativeVisitor;
Map, ComponentVisitor> alternativeVisitor2;
Method read;
Method write;
public ComponentPropertyVisitor(String property, boolean ascend, T value){
if(property == null)
throw new IllegalArgumentException("property is null");
else if(value == null)
throw new IllegalArgumentException("value is null");
try{
PropertyDescriptor pd = new PropertyDescriptor(property, Component.class);
this.read = pd.getReadMethod();
if(read != null){
Class rt = read.getReturnType();
if(rt == null || (!rt.isAssignableFrom(value.getClass()) && !rt.isPrimitive()))
throw new IllegalArgumentException("value wrong type for property");
else if(rt.isPrimitive()){
Class vclass = value.getClass();
if(rt == boolean.class && vclass == Boolean.class);
else if(rt == int.class && vclass == Integer.class);
else if(rt == long.class && vclass == Long.class);
else if(rt == char.class && vclass == Character.class);
else if(rt == double.class && vclass == Double.class);
else if(rt == float.class && vclass == Float.class);
else if(rt == short.class && vclass == Short.class);
else if(rt == byte.class && vclass == Byte.class);
else throw new IllegalArgumentException(vclass + " does not match " + rt);
}
}
this.write = pd.getWriteMethod();
}
catch(IntrospectionException ie){}
if(read == null || write == null){
String s = null;
if(read == null && write == null)
s = "readable or writable";
else if(read == null)
s = "readable";
else
s = "writable";
throw new IllegalArgumentException("property not " + s);
}
this.ascend = ascend;
this.value = value;
state = new WeakHashMap();
alternativeVisitor = new WeakHashMap();
alternativeVisitor2 = new WeakHashMap, ComponentVisitor>();
restoring = false;
}

public void setValue(T value){
this.value = value;
}

public T getValue(){
return value;
}

public void setAlternativeVisitor(Component c, ComponentVisitor cv){
alternativeVisitor.put(c, cv);
}

public void setAlternativeVisitor(Class c, ComponentVisitor cv){
alternativeVisitor2.put(c, cv);
}

public void removeAlternativeVisitor(Component c){
alternativeVisitor.remove(c);
}

public void removeAlternativeVisitor(Class c){
alternativeVisitor2.remove(c);
}

public void setRestoring(boolean restore){
restoring = restore;
}

public boolean getRestoring(){
return restoring;
}

public void ascendingVisit(Component c){
if(ascend)
visit(c);
}

public boolean visitationComplete(Component c){
ComponentVisitor cv = null;
if(alternativeVisitor.containsKey(c)){
cv = (ComponentVisitor)alternativeVisitor.get(c);
if(cv == null) return true;
}
else if(alternativeVisitor2.containsKey(c.getClass())){
cv = (ComponentVisitor)alternativeVisitor2.get(c);
if(cv == null) return true;
}

if(cv != null){
c.visit(cv);
return true;
}
return false;
}

public void descendingVisit(Component c){
if(!ascend)
visit(c);
}

public void visit(Component c){
if(restoring){
if(state.containsKey(c)){
//c.setEnabled(state.get(c));
try{
write.invoke(c, state.get(c));
}
catch(IllegalAccessException iae){}
catch(InvocationTargetException ite){}
}
}
else{
try{
state.put(c, (T)read.invoke(c));
write.invoke(c, value);
}
catch(IllegalAccessException iae){}
catch(InvocationTargetException ite){}
}

}

}
}

leouser
Offline
Joined: 2005-12-12

I should probably note, that #7 in the above sequence isn't necessary if the text field had been disabled before hand.

so if step 2.5 is:
2.5 text.setEnabled(false);

then upon the restoring visit, it will remain false.

The pre-change state capturing happens with this code:
state.put(c, (T)read.invoke(c)); //stashed...
write.invoke(c, value); //now we change...

here the state is stashed, and then it is changed. Upon restoration the stashed state is reinstated. This works in the test case where I have widgets that:
1. Are enabled.
2. Are disabled.
3. Are to be ignored by the visit

upon the first visitation in the test, 1 and 2 widgets states are stashed. #3 I configure to be ignored by the visitation, so they keep their state no matter what happens during the visit. Upon restoring, 1 and 2 return to their former enabled or disabled glory. Of course if the user just wants to flip a set of widgets from enabled to disabled and disabled to enabled regardless of their current state or past states, that's possible too. :)

BH

leouser
Offline
Joined: 2005-12-12

Repost from collab forum:

I thought it'd be a good idea to test what else this can be
used for. Attached is a test that manipulates the
foreground color of a Component hierarchy. Pressing the
color/restore button:
1. Sets the foreground color of all the widgets to green.
2. Upon pressing it again, restores the color.

Its apparent that for the user it is remarkable simple to
just switch the colors around. He does not have to write
any code to traverse the hierarchy, it happens
automatically. With the predefined
ComponentPropertyVisitor, he doesn't have to fiddle with
calls to setForeground. It just happens for him. All the
user has to worry about is orchestrating the calls right.
Which from the test case is pretty simple:
visit and Color if Color != green.
visit and Restore if Color == green.

BH

CODE:
///----------------------------
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import static java.awt.ComponentVisitors.*;

public class ColoringTest{

ComponentPropertyVisitor cpv = new ComponentPropertyVisitor("foreground", false, Color.green);
JPanel jp;

public ColoringTest(){
JFrame jf = new JFrame();
jp = new JPanel();
JButton jb1 = new JButton("One");
jb1.setForeground(Color.red);
jp.add(jb1);
JLabel jl = new JLabel("Two");
jl.setForeground(Color.orange);
jp.add(jl);
JTextField jtf = new JTextField("Hi");
jtf.setForeground(Color.blue);
jp.add(jtf);
jf.add(jp);
JButton jb = new JButton("Colorise/Restore");
jf.add(jb, BorderLayout.SOUTH);
jb.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e){
if(jp.getForeground() != Color.green)
jp.visit(cpv);
else{
cpv.setRestoring(true);
jp.visit(cpv);
cpv.setRestoring(false);
}

}

});
jf.pack();
jf.setLocationRelativeTo(null);
jf.setVisible(true);

}

public static void main(String ... args){
Runnable run = new Runnable(){
public void run(){
new ColoringTest();
}
};
SwingUtilities.invokeLater(run);
}
}

leouser
Offline
Joined: 2005-12-12

The ComponentPropertyVisitor class appears to integrate well with the javax.swing.undo package. I have a test up over in the collab forum where the code wraps ComponentPropertyVisitor instances within an UndoableEdit. The user of the test can alter a set of widgets foreground color over and over. Each change is stored in the undo manager and since the CPV is storing a memento of each widgets property it is a simple matter of reapplying those properties with the undoer and also redoing them if necessary.

BH

Richard Bair

I don't know if I personally like the idea of maintaining this kind
of logic in the visitor. In order to handle the situation I outlined,
you'd have to do something like:

1 JPanel panel = new JPanel();
2 JTextField text = new JTextField();
3 panel.add(text);
4 MyEnablementVisitor v = new MyEnablementVisitor();
5 v.setValue(false);
6 v.visit(panel);
7 v.setValue(text, false); //ensures that the text stays disabled
when re-enablement occurs
8 v.setRestoring(true);
9 v.setValue(true);
10 v.visit(panel);

Even if you could reduce some of the steps, you'll always have to
have something like step #7 in there to solve the use case I
previously outlined. And that's always going to look ugly. I think
you have to work with the components directly. I don't think any
"outsider" mechanism like visitors will solve this problem cleanly.

Richard

On May 26, 2006, at 11:27 AM, swing@javadesktop.org wrote:

> Intriguingly Ive developed a Visitor that tracks the enabled
> widgets before they are changed and allows the user to restore that
> state when he wants to re-enable it. Or if he wants, he can just
> reset the total state again. Its quite capable of restoring things
> to what they were. I like the flexibility it offers, in that the
> user is not tied to one strategy for mass enable/disable.
>
> In your particular case, if you wanted to turn off a tree of
> widgets but keep the fact that JTextField is turned off before
> hand, it can do this. Or if you change your mind and want to turn
> everything back on regardless of the large change, this is possible
> as well. The developer will be free to enact either policy, or
> even develop one we aren't even thinking of here.
>
> BH
> [Message sent by forum member 'leouser' (leouser)]
>
> http://forums.java.net/jive/thread.jspa?messageID=116611

leouser
Offline
Joined: 2005-12-12

your code doesn't look right. The only step the user would have to do to do restorative logic is to call setRestoring(true) and that's it.

Component c = ....something...
ComponentVisitors.ComponentPropertyVisitor cpv = new ComponentVisitors.ComponentPropertyVisitor("enabled", false, true);
c.visit(cpv);
///.....late on time to restore....
cpv.setRestoring(true);
c.visit(cpv);

actually, pretty simple. :)

BH

leouser
Offline
Joined: 2005-12-12

whoops, this call:
ComponentVisitors.ComponentPropertyVisitor cpv = new ComponentVisitors.ComponentPropertyVisitor("enabled", false, true);

should be:
ComponentVisitors.ComponentPropertyVisitor cpv = new ComponentVisitors.ComponentPropertyVisitor("enabled", false, false);

if your aiming to disable.

We can probably reduce it down to just ComponentPropertyVisitor with a static import.

ComponentPropertyVisitor cpv = new ComponentPropertyVisitor("enabled", false, false);

BH

pietschy
Offline
Joined: 2003-06-10

You should probably define the state in a model of some kind and doing something equivalent to..

Binder.bind("textField.enabled", "panelModel.enabled && textFieldModel.enabled");

The above is a piece of fiction, you'd have to do it manually with property change listeners or the binding framework of your choice.

Cheers
Andrew

leouser
Offline
Joined: 2005-12-12

Do you mean storing the restorative state?

BH

leouser
Offline
Joined: 2005-12-12

That could be an interesting way of doing the visitation/setting/storing of state. Instead of using just the enabled state as the target, allow the user to target other properties of the widget or maybe sets of properties. Then the Enabler could become the PropertySettingVisitor or something like that.

BH

Richard Bair

Hey,

Personally I've never really liked the visitor pattern. :-)

As for this feature you are working on, I don't think this approach
will really work -- but that's for another thread.

Richard

On May 26, 2006, at 10:13 AM, swing@javadesktop.org wrote:

> howdy,
>
> I thought Id toss this out here since Im discussing using this as
> part of the solution to a specific bug. The idea is simple:
> 1. Add the ability to recursively travel a Component hierarchy with
> a Visitor.
>
> say for example, the user wants to disable all the widgets in a
> Hierarchy but also wants to restore them to their enabled state.
> The user could create a Visitor instance and have it travel the
> hierarchy turning off each widget but also keeping track of its
> previous state. Upon deciding that its time to restore things, he
> configures his Vistor to restore the previous state and sends the
> Vistor down the hierarchy again.
>
> I think a positive of this capability would be that it would make
> it simpler to define new operations that need to be performed on
> groups of components without having to write component tree walking
> code, or in the case where its seen as something that is standard
> for the JDK you wouldn't need to add a new method for the enmasse
> change.
>
>
> Id think the interface would need to have two methods:
> visitDescending(Component c)
> visitAscending(Component c)
>
> Descending would be called upon the first contact with the visitor.
>
> Ascending would be called upon after the first contact and after
> the children have been visited.
>
> This way the user would have control as to when he wants his
> operations to be performed.
>
> thoughts folks?
> leouser
> [Message sent by forum member 'leouser' (leouser)]
>
> http://forums.java.net/jive/thread.jspa?messageID=116566

leouser
Offline
Joined: 2005-12-12

well, thanks that helps! :D

What do you see as the problem with using a Visitor to solve the particular problem?

leouser

Richard Bair

Hi

> well, thanks that helps! :D

I try :-)

> What do you see as the problem with using a Visitor to solve the
> particular problem?

This is the code sequence:

1 JPanel panel = new JPanel();
2 JTextField text = new JTextField();
3 panel.add(text);
4 panel.setEnabled(false);
5 text.setEnabled(false);
6 panel.setEnabled(true);

What should the enabled status of "text" be? My contention is that
when panel.setEnabled(true) is called, text should remain disabled
because the developer explicitly set it to be disabled on line 5.
This means that you can't use a visitor or some other "outsider" to
remember the original state of the text field in order to restore it
later. The JTextField itself has to be keeping track of its enabled
state, so that it is "restored" to the proper original state.

So, I don't think the visitor pattern will help you out here at all
in any case. You're better off exploring what can be done within the
components themselves.

leouser
Offline
Joined: 2005-12-12

Intriguingly Ive developed a Visitor that tracks the enabled widgets before they are changed and allows the user to restore that state when he wants to re-enable it. Or if he wants, he can just reset the total state again. Its quite capable of restoring things to what they were. I like the flexibility it offers, in that the user is not tied to one strategy for mass enable/disable.

In your particular case, if you wanted to turn off a tree of widgets but keep the fact that JTextField is turned off before hand, it can do this. Or if you change your mind and want to turn everything back on regardless of the large change, this is possible as well. The developer will be free to enact either policy, or even develop one we aren't even thinking of here.

BH

leouser
Offline
Joined: 2005-12-12

here's the visitor I was writing about:
package java.awt;

import java.util.*;

public class StandardVisitors{

public static class Enabler implements ComponentVisitor{

boolean restoring;
boolean ascend;
boolean value;
Map state;
public Enabler(boolean ascend, boolean value){
state = new WeakHashMap();
ascend = ascend;
restoring = false;
}

public void setValue(boolean value){
this.value = value;
}

public boolean getValue(boolean value){
return value;
}

public void setRestoring(boolean restore){
restoring = restore;
}

public boolean getRestoring(){
return restoring;
}

public void ascendingVisit(Component c){
if(ascend)
visit(c);
}

public void descendingVisit(Component c){
if(!ascend)
visit(c);
}

public void visit(Component c){
if(restoring){
if(state.containsKey(c)){
c.setEnabled(state.get(c));
}
}
else{
state.put(c, c.isEnabled());
c.setEnabled(false);
}

}

}

}
---------------
In the most recent test case Ive put together we have a set of widgets. 2 of them are disabled at the start. Pressing a particular button executes the disabling visitation. Pressing this particular button again, executes the restorative visitation.

BH

leouser
Offline
Joined: 2005-12-12

opps, this code:
else{
state.put(c, c.isEnabled());
c.setEnabled(false);
}

shoudl be:
else{
state.put(c, c.isEnabled());
c.setEnabled(value);
}

leouser
Offline
Joined: 2005-12-12

one method that appears important to add to the ComponentVisitor interface is:
public boolean visitationComplete(Component c)

this allows the opportunity for alternative visitation processing to occur, or even to have the Component not be visited at all. The most recent test case utilises this to ignore a JPanel and its associated children from being visted. Hence they are effectively ignored in the disable/restore sequence, they remain enabled.

BH