简体   繁体   English

阻止JTree选择发生变化的最佳方法是什么?

[英]Best way to stop a JTree selection change from happening?

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. 我有一个对话框,其中JTree中的每个条目在不同的面板中都有相应的选项,当选择更改时会更新。 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. 我尝试在JTree上使用valueChangeListener执行此操作,但是如果出现错误,则当前必须将valueChanged方法调用“setSelectionRow”到旧选择。 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. 所以我没有获得StackOverflow,我在执行此操作之前将布尔“isError”设置为true,以便我可以忽略新的valueChanged事件。 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? 对于这样的情况,有一个好的设计模式吗?

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. 我知道在Delphi中这是一个非常方便的事件:“在更改选择之前”,你可以很容易地停止改变选择。

here is my java code with prevention of infinite recursion problem 这是我的java代码,防止无限递归问题

    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());
}

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? 不确定这是最佳实践,但也许您可以将FocusListener放在要验证的组件上...在调用事件时调用验证然后如果不希望移动焦点则使用then then事件因为验证失败了?

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. 至少使用java 8(我没有检查早期版本),这个解决方案不起作用,因为FocusEvent似乎不是一个低级事件。 Hence it cannot be consumed. 因此它无法消费。 See Method AWTEvent.consume() 请参见方法AWTEvent.consume()

Here is my solution. 这是我的解决方案。

In a JTree subclass: 在JTree子类中:

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. 拦截鼠标操作,将要选择的路径通告给VetoableChangeListeners。 In the concrete VCL the changing property is examined and if it is the lead selection, veto logic is checked. 在具体的VCL中,检查更改属性,如果是前导选择,则检查否决逻辑。 If vetoing is needed, the VCL throws PropertyVetoException, otherwise mouse event handling goes as usual and selection happens. 如果需要否决,则VCL抛出PropertyVetoException,否则鼠标事件处理将照常进行并进行选择。 In short, this makes lead selection property become a constrained property. 简而言之,这使得选择铅属性成为一种约束属性。

设置实现适当语义的TreeSelectionModel。

Here is an example of implementing a TreeSelectionModel that wraps another TreeSelectionModel but allows selection to be vetoed: 下面是一个实现TreeSelectionModel的示例,该TreeSelectionModel包装另一个TreeSelectionModel但允许选择被否决:

public class VetoableTreeSelectionModel implements TreeSelectionModel
{
   private final ListenerList<VetoableTreeSelectionListener> m_vetoableTreeSelectionListeners = new ListenerList<VetoableTreeSelectionListener>();

   private final DefaultTreeSelectionModel m_treeSelectionModel = new DefaultTreeSelectionModel();

   /**
    * {@inheritDoc}
    */
   public void addTreeSelectionListener(final TreeSelectionListener listener)
   {
      m_treeSelectionModel.addTreeSelectionListener(listener);
   }

   /**
    * {@inheritDoc}
    */
   public void removeTreeSelectionListener(final TreeSelectionListener listener)
   {
      m_treeSelectionModel.removeTreeSelectionListener(listener);
   }

   /**
    * Add a vetoable tree selection listener
    *
    * @param listener the listener
    */
   public void addVetoableTreeSelectionListener(final VetoableTreeSelectionListener listener)
   {
      m_vetoableTreeSelectionListeners.addListener(listener);
   }

   /**
    * Remove a vetoable tree selection listener
    *
    * @param listener the listener
    */
   public void removeVetoableTreeSelectionListener(final VetoableTreeSelectionListener listener)
   {
      m_vetoableTreeSelectionListeners.removeListener(listener);
   }

   /**
    * {@inheritDoc}
    */
   public void addPropertyChangeListener(final PropertyChangeListener listener)
   {
      m_treeSelectionModel.addPropertyChangeListener(listener);
   }

   /**
    * {@inheritDoc}
    */
   public void removePropertyChangeListener(final PropertyChangeListener listener)
   {
      m_treeSelectionModel.removePropertyChangeListener(listener);
   }

   /**
    * {@inheritDoc}
    */
   public void addSelectionPath(final TreePath path)
   {
      try
      {
         m_vetoableTreeSelectionListeners.fireVetoableEvent(new VetoableAction<VetoableTreeSelectionListener>() {
            public void fireEvent(final VetoableTreeSelectionListener listener) throws EventVetoedException
            {
               listener.aboutToAddSelectionPath(path);
            }});

         m_treeSelectionModel.addSelectionPath(path);
      }
      catch (final EventVetoedException e)
      {
         return;
      }
   }

   /**
    * {@inheritDoc}
    */
   public void addSelectionPaths(final TreePath[] paths)
   {
      try
      {
         m_vetoableTreeSelectionListeners.fireVetoableEvent(new VetoableAction<VetoableTreeSelectionListener>() {
            public void fireEvent(final VetoableTreeSelectionListener listener) throws EventVetoedException
            {
               listener.aboutToAddSelectionPaths(paths);
            }});

         m_treeSelectionModel.addSelectionPaths(paths);
      }
      catch (final EventVetoedException e)
      {
         return;
      }
   }

   /**
    * {@inheritDoc}
    */
   public void clearSelection()
   {
      try
      {
         m_vetoableTreeSelectionListeners.fireVetoableEvent(new VetoableAction<VetoableTreeSelectionListener>() {
            public void fireEvent(final VetoableTreeSelectionListener listener) throws EventVetoedException
            {
               listener.aboutToClearSelection();
            }});

         m_treeSelectionModel.clearSelection();
      }
      catch (final EventVetoedException e)
      {
         return;
      }
   }

   /**
    * {@inheritDoc}
    */
   public TreePath getLeadSelectionPath()
   {
      return m_treeSelectionModel.getLeadSelectionPath();
   }

   /**
    * {@inheritDoc}
    */
   public int getLeadSelectionRow()
   {
      return m_treeSelectionModel.getLeadSelectionRow();
   }

   /**
    * {@inheritDoc}
    */
   public int getMaxSelectionRow()
   {
      return m_treeSelectionModel.getMaxSelectionRow();
   }

   /**
    * {@inheritDoc}
    */
   public int getMinSelectionRow()
   {
      return m_treeSelectionModel.getMinSelectionRow();
   }

   /**
    * {@inheritDoc}
    */
   public RowMapper getRowMapper()
   {
      return m_treeSelectionModel.getRowMapper();
   }

   /**
    * {@inheritDoc}
    */
   public int getSelectionCount()
   {
      return m_treeSelectionModel.getSelectionCount();
   }

   public int getSelectionMode()
   {
      return m_treeSelectionModel.getSelectionMode();
   }

   /**
    * {@inheritDoc}
    */
   public TreePath getSelectionPath()
   {
      return m_treeSelectionModel.getSelectionPath();
   }

   /**
    * {@inheritDoc}
    */
   public TreePath[] getSelectionPaths()
   {
      return m_treeSelectionModel.getSelectionPaths();
   }

   /**
    * {@inheritDoc}
    */
   public int[] getSelectionRows()
   {
      return m_treeSelectionModel.getSelectionRows();
   }

   /**
    * {@inheritDoc}
    */
   public boolean isPathSelected(final TreePath path)
   {
      return m_treeSelectionModel.isPathSelected(path);
   }

   /**
    * {@inheritDoc}
    */
   public boolean isRowSelected(final int row)
   {
      return m_treeSelectionModel.isRowSelected(row);
   }

   /**
    * {@inheritDoc}
    */
   public boolean isSelectionEmpty()
   {
      return m_treeSelectionModel.isSelectionEmpty();
   }

   /**
    * {@inheritDoc}
    */
   public void removeSelectionPath(final TreePath path)
   {
      try
      {
         m_vetoableTreeSelectionListeners.fireVetoableEvent(new VetoableAction<VetoableTreeSelectionListener>() {
            public void fireEvent(final VetoableTreeSelectionListener listener) throws EventVetoedException
            {
               listener.aboutRemoveSelectionPath(path);
            }});

         m_treeSelectionModel.removeSelectionPath(path);
      }
      catch (final EventVetoedException e)
      {
         return;
      }
   }

   /**
    * {@inheritDoc}
    */
   public void removeSelectionPaths(final TreePath[] paths)
   {
      try
      {
         m_vetoableTreeSelectionListeners.fireVetoableEvent(new VetoableAction<VetoableTreeSelectionListener>() {
            public void fireEvent(final VetoableTreeSelectionListener listener) throws EventVetoedException
            {
               listener.aboutRemoveSelectionPaths(paths);
            }});

         m_treeSelectionModel.removeSelectionPaths(paths);
      }
      catch (final EventVetoedException e)
      {
         return;
      }
   }

   /**
    * {@inheritDoc}
    */
   public void resetRowSelection()
   {
      try
      {
         m_vetoableTreeSelectionListeners.fireVetoableEvent(new VetoableAction<VetoableTreeSelectionListener>() {
            public void fireEvent(final VetoableTreeSelectionListener listener) throws EventVetoedException
            {
               listener.aboutToResetRowSelection();
            }});

         m_treeSelectionModel.resetRowSelection();
      }
      catch (final EventVetoedException e)
      {
         return;
      }
   }

   /**
    * {@inheritDoc}
    */
   public void setRowMapper(final RowMapper newMapper)
   {
      m_treeSelectionModel.setRowMapper(newMapper);
   }

   /**
    * {@inheritDoc}
    */
   public void setSelectionMode(final int mode)
   {
      try
      {
         m_vetoableTreeSelectionListeners.fireVetoableEvent(new VetoableAction<VetoableTreeSelectionListener>() {
            public void fireEvent(final VetoableTreeSelectionListener listener) throws EventVetoedException
            {
               listener.aboutToSetSelectionMode(mode);
            }});

         m_treeSelectionModel.setSelectionMode(mode);
      }
      catch (final EventVetoedException e)
      {
         return;
      }
   }

   /**
    * {@inheritDoc}
    */
   public void setSelectionPath(final TreePath path)
   {
      try
      {
         m_vetoableTreeSelectionListeners.fireVetoableEvent(new VetoableAction<VetoableTreeSelectionListener>() {
            public void fireEvent(final VetoableTreeSelectionListener listener) throws EventVetoedException
            {
               listener.aboutToSetSelectionPath(path);
            }});

         m_treeSelectionModel.setSelectionPath(path);
      }
      catch (final EventVetoedException e)
      {
         return;
      }
   }

   /**
    * {@inheritDoc}
    */
   public void setSelectionPaths(final TreePath[] paths)
   {
      try
      {
         m_vetoableTreeSelectionListeners.fireVetoableEvent(new VetoableAction<VetoableTreeSelectionListener>() {
            public void fireEvent(final VetoableTreeSelectionListener listener) throws EventVetoedException
            {
               listener.aboutToSetSelectionPaths(paths);
            }});

         m_treeSelectionModel.setSelectionPaths(paths);
      }
      catch (final EventVetoedException e)
      {
         return;
      }
   }

   /**
    * {@inheritDoc}
    */
   @Override
   public String toString()
   {
      return m_treeSelectionModel.toString();
   }

} }

And here is the listener to go with it: 以下是听众:

public interface VetoableTreeSelectionListener
{
   /**
    * About to add a path to the selection
    *
    * @param path the path to add
    *
    * @throws EventVetoedException
    */
   void aboutToAddSelectionPath(TreePath path) throws EventVetoedException;

   /**
    * About to add paths to the selection
    *
    * @param paths the paths to add
    *
    * @throws EventVetoedException
    */
   void aboutToAddSelectionPaths(TreePath[] paths) throws EventVetoedException;

   /**
    * About to clear selection
    *
    * @throws EventVetoedException
    */
   void aboutToClearSelection() throws EventVetoedException;

   /**
    * About to remove a selection path
    *
    * @param path the path
    *
    * @throws EventVetoedException
    */
   void aboutRemoveSelectionPath(TreePath path) throws EventVetoedException;

   /**
    * About to remove multiple selection paths
    *
    * @param paths the paths
    *
    * @throws EventVetoedException
    */
   void aboutRemoveSelectionPaths(TreePath[] paths) throws EventVetoedException;

   /**
    * About to reset the row selection
    *
    * @throws EventVetoedException
    */
   void aboutToResetRowSelection() throws EventVetoedException;

   /**
    * About to set the selection mode
    *
    * @param mode the selection mode
    *
    * @throws EventVetoedException
    */
   void aboutToSetSelectionMode(int mode) throws EventVetoedException;

   /**
    * About to set the selection path
    *
    * @param path the path
    *
    * @throws EventVetoedException
    */
   void aboutToSetSelectionPath(TreePath path) throws EventVetoedException;

   /**
    * About to set the selection paths
    *
    * @param paths the paths
    *
    * @throws EventVetoedException
    */
   void aboutToSetSelectionPaths(TreePath[] paths) throws EventVetoedException;
}

You can use your own implementation of ListenerList, but you get the idea... 你可以使用你自己的ListenerList实现,但你明白了......

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). 为了防止选择,我只是将DefaultTreeSelectionModel子类化,并覆盖所有方法以检查我不想选择的对象(下面我的例子中的“DisplayRepoOwner”实例)。 If the object was OK to be selected, I called the super method; 如果对象可以选择,我调用了super方法; otherwise I didn't. 否则我没有。 I set my JTree's selection model to an instance of that subclass. 我将JTree的选择模型设置为该子类的实例。

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);
}

} }

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. 我试图用树注册MouseListeners和所有这些。 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. 问题是TreeUI的鼠标监听器在我的JTree之前进入事件的过程,这意味着设置一个标志或类似的东西为时已晚。 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. 在使用一些Thread.dumpStack()调用来获取堆栈转储后,我找到了我想要覆盖的方法。 I extended the BasicTreeUI and overrode the "protected void selectPathForEvent(TreePath path, MouseEvent event)". 我扩展了BasicTreeUI并覆盖了“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); 然后,您可以使用event.consume()所需的任何逻辑,如果要停止选择,返回所需的任何选择,或通过调用super.selectPathForEvent(path,event)将其传递给默认处理;

Just remember to set the UI you created in JTree. 只需记住设置您在JTree中创建的UI。 That mistake wasted a few minuets of my life ;-) 这个错误浪费了我生命中的几分钟;-)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM