Best way to stop a JTree selection change from hap

2019-02-15 19:09发布

I have a dialog where each entry in a JTree has its corresponding options in a different panel, which is updated when the selection changes. If options for one of the entries is set to an invalid state, when the user attempts to change to a different entry in the tree, I want there to be an error dialog and have the selection not change.

I tried doing this with a valueChangeListener on the JTree, but currently then have to have the valueChanged method call "setSelectionRow" to the old selection if there is an error. So that I don't get a StackOverflow, I set a boolean "isError" to true before I do this so that I can ignore the new valueChanged event. Somehow I have the gut feeling this is not the best solution. ;-)

How would I go about it instead? Is there a good design pattern for situations like this?

7条回答
甜甜的少女心
2楼-- · 2019-02-15 19:28

Set a TreeSelectionModel which implements the appropriate semantics.

查看更多
祖国的老花朵
3楼-- · 2019-02-15 19:33

To prevent selection I just subclassed DefaultTreeSelectionModel and overrode all the methods to check for objects that I didn't want to be selected (instances of "DisplayRepoOwner" in my example below). If the object was OK to be selected, I called the super method; otherwise I didn't. I set my JTree's selection model to an instance of that subclass.

public class MainTreeSelectionModel extends DefaultTreeSelectionModel {
public void addSelectionPath(TreePath path) {
    if (path.getLastPathComponent() instanceof DisplayRepoOwner) {
        return;
    }
    super.addSelectionPath(path);
}
public void addSelectionPaths(TreePath[] paths) {
    for (TreePath tp : paths) {
        if (tp.getLastPathComponent() instanceof DisplayRepoOwner) {
            return;
        }
    }
    super.addSelectionPaths(paths);
}
public void setSelectionPath(TreePath path) {
    if (path.getLastPathComponent() instanceof DisplayRepoOwner) {
        return;
    }
    super.setSelectionPath(path);
}
public void setSelectionPaths(TreePath[] paths) {
    for (TreePath tp : paths) {
        if (tp.getLastPathComponent() instanceof DisplayRepoOwner) {
            return;
        }
    }
    super.setSelectionPaths(paths);
}

}

查看更多
一纸荒年 Trace。
4楼-- · 2019-02-15 19:38

Here is my solution.

In a JTree subclass:

protected void processMouseEvent(MouseEvent e) {
        TreePath selPath = getPathForLocation(e.getX(), e.getY());
        try {
            fireVetoableChange(LEAD_SELECTION_PATH_PROPERTY, getLeadSelectionPath(), selPath);
        }
        catch (PropertyVetoException ex) {
            // OK, we do not want change to happen
            return;
        }

        super.processMouseEvent(e);
}

Then in the tree using class:

VetoableChangeListener vcl = new VetoableChangeListener() {

        public void vetoableChange(PropertyChangeEvent evt) throws PropertyVetoException {
            if ( evt.getPropertyName().equals(JTree.LEAD_SELECTION_PATH_PROPERTY) ) {
                try {
                    <some code logic that has to be satisfied>
                } catch (InvalidInputException e) {
                    throw new PropertyVetoException("", evt);
                }

            }
        }
    };
    tree.addVetoableChangeListener(vcl);

The mechanism starts at the earliest possible place. Mouse action intercepted, the path to-be-selected is advertised to VetoableChangeListeners. In the concrete VCL the changing property is examined and if it is the lead selection, veto logic is checked. If vetoing is needed, the VCL throws PropertyVetoException, otherwise mouse event handling goes as usual and selection happens. In short, this makes lead selection property become a constrained property.

查看更多
家丑人穷心不美
5楼-- · 2019-02-15 19:41

Stumbled across this thread while investigating a solution for the same problem. First, let me tell you things that didn't work. I attempted to register MouseListeners and all of that with the tree. The problem was that the TreeUI's mouse listeners were getting to the process the event before my JTree did, meaning it was too late to set a flag or anything like that. Besides that this solution produced some ugly code and I would generally avoid it.

So now for the actual solution!
After using a few Thread.dumpStack() calls to get a stack dump, I found the method I was looking to override. I extended the BasicTreeUI and overrode the "protected void selectPathForEvent(TreePath path, MouseEvent event)".

This will give you access to the mouse event that caused the selection before the selection actually occurs. You can then use whatever logic you need to either event.consume() and return if you want to stop the selection, do whatever selection you want or pass it up for default processing by calling super.selectPathForEvent(path, event);

Just remember to set the UI you created in JTree. That mistake wasted a few minuets of my life ;-)

查看更多
放我归山
6楼-- · 2019-02-15 19:47

I did not find a better way, but this approach works fine for me. I know in Delphi it was a very convenient event: "before changing selection" where you could very easily stop changing selection.

here is my java code with prevention of infinite recursion problem

    navTree.addTreeSelectionListener(new TreeSelectionListener() {

        boolean treeSelectionListenerEnabled = true;

        public void valueChanged(TreeSelectionEvent e) {
            if (treeSelectionListenerEnabled) {
                if (ok to change selection...) {
                    ...
                } else {
                    TreePath treePath = e.getOldLeadSelectionPath();
                    treeSelectionListenerEnabled = false;
                    try {
                        // prevent from leaving the last visited node
                        navTree.setSelectionPath(treePath);
                    } finally {
                        treeSelectionListenerEnabled = true;
                    }
                }
            }
        }
    });

always remember to remove all listeners you added, to prevent memory leaks.

here is another approach:

private class VetoableTreeSelectionModel extends DefaultTreeSelectionModel {
    public void setSelectionPath(TreePath path){
        if (allow selection change?) {
            super.setSelectionPath(path);
        }
    }
}
{
    navTree.setSelectionModel(new VetoableTreeSelectionModel());
}
查看更多
戒情不戒烟
7楼-- · 2019-02-15 19:49

Not sure it's best practice, but maybe you could put a FocusListener on the component(s) you want to validate... call your validation when the event is called and then consume then event if you don't want the focus to be moved because the validation fails?

Later Edit:

At least with java 8 (I didn't check earlier versions) this solution won't work, because the FocusEvent appears not to be a low-level event. Hence it cannot be consumed. See Method AWTEvent.consume()

查看更多
登录 后发表回答