Лучший способ предотвратить изменение выбора JTree?


У меня есть диалоговое окно, где каждая запись в JTree имеет свои соответствующие параметры в другой панели, которая обновляется при изменении выбора. Если параметры для одной из записей установлены в недопустимое состояние, когда пользователь пытается перейти к другой записи в дереве, я хочу, чтобы было диалоговое окно ошибки и выбор не менялся.

Я пытался сделать это с valueChangeListener на JTree, но в настоящее время затем должен иметь valueChanged вызов метода " setSelectionRow" к старому выбору, если есть ошибка. Чтобы не получить StackOverflow, я устанавливаю булеву "isError" значение true, прежде чем сделать это, чтобы я мог игнорировать новое событие valueChanged. Почему-то я нутром чувствую, что это не лучшее решение. ;- )

Как бы я поступил вместо этого? Есть ли хороший шаблон дизайна для подобных ситуаций?

7 2

7 ответов:

Не уверен, что это лучшая практика, но, возможно, вы могли бы поместить FocusListener на компонент(Ы), который вы хотите проверить... вызовите проверку при вызове события, а затем потребляйте событие then, если вы не хотите, чтобы фокус был перемещен из-за сбоя проверки?

Позднее Редактирование:

По крайней мере, с java 8 (я не проверял более ранние версии) это решение не будет работать, потому что FocusEvent не является низкоуровневым событием. Поэтому его нельзя употреблять. См. Метод AWTEvent.consume ()

Я не нашел лучшего способа, но этот подход прекрасно работает для меня. Я знаю, что в Delphi это было очень удобное событие: "перед изменением выбора", где вы могли очень легко остановить изменение выбора.

Вот мой 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;
                    }
                }
            }
        }
    });

Всегда помните, чтобы удалить все добавленные прослушиватели, чтобы предотвратить утечку памяти.

Вот еще один подход:

private class VetoableTreeSelectionModel extends DefaultTreeSelectionModel {
    public void setSelectionPath(TreePath path){
        if (allow selection change?) {
            super.setSelectionPath(path);
        }
    }
}
{
    navTree.setSelectionModel(new VetoableTreeSelectionModel());
}

Вот мое решение.

В подклассе 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);
}

Затем в дереве с помощью класса:

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);
Механизм запускается в самом раннем возможном месте. Действие мыши перехвачено, путь к выбранному объявлен VetoableChangeListeners. В конкретном VCL исследуется изменяющееся свойство, и если это выбор ведущего, то проверяется логика вето. Если требуется наложить вето, VCL создает исключение PropertyVetoException, в противном случае обработка событий мыши идет как обычно и отбор происходит. Короче говоря, это делает свойство выбора лидов ограниченным свойством.

Задайте модель 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();
   }

}

И вот слушатель, чтобы пойти с ним:

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

Вы можете использовать свою собственную реализацию ListenerList, но вы получите идею...

Чтобы предотвратить выделение, я просто подкласс DefaultTreeSelectionModel и переопределил все методы для проверки объектов, которые я не хотел выбирать (экземпляры "DisplayRepoOwner" в моем примере ниже). Если объект был в порядке для выбора, я вызвал метод super; в противном случае я не сделал этого.я установил модель выбора моего 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);
}

}

Наткнулся на этот поток, исследуя решение той же проблемы. Во-первых, позвольте мне рассказать вам о том, что не сработало. Я попытался зарегистрировать Муселистенеров и все такое с деревом. Проблема заключалась в том, что слушатели мыши TreeUI добирались до процесса события раньше моего JTree, а это означало, что было слишком поздно устанавливать флаг или что-то подобное. Кроме того, это решение произвело некоторый уродливый код, и я обычно избегал его.

Итак, теперь для фактического решение!
После использования нескольких нитей.вызовы dumpStack () чтобы получить дамп стека, я нашел метод, который искал, чтобы переопределить. Я расширил BasicTreeUI и преодолел "protected void selectPathForEvent (TreePath path, mouseevent event)".

Это даст вам доступ к событию мыши, вызвавшему выделение, до того, как оно действительно произойдет. Затем вы можете использовать любую логику, которая вам нужна для любого события.consume () и return если вы хотите остановить выделение, сделайте то, что вы выбрали хотите или передайте его для обработки по умолчанию, позвонив в super.selectPathForEvent (путь, событие);

Просто не забудьте установить пользовательский интерфейс, созданный в JTree. Эта ошибка растратила впустую несколько менуэтов моей жизни; -)