Лучший способ предотвратить изменение выбора JTree?
У меня есть диалоговое окно, где каждая запись в JTree имеет свои соответствующие параметры в другой панели, которая обновляется при изменении выбора. Если параметры для одной из записей установлены в недопустимое состояние, когда пользователь пытается перейти к другой записи в дереве, я хочу, чтобы было диалоговое окно ошибки и выбор не менялся.
Я пытался сделать это с valueChangeListener на JTree, но в настоящее время затем должен иметь valueChanged вызов метода " setSelectionRow" к старому выбору, если есть ошибка. Чтобы не получить StackOverflow, я устанавливаю булеву "isError" значение true, прежде чем сделать это, чтобы я мог игнорировать новое событие valueChanged. Почему-то я нутром чувствую, что это не лучшее решение. ;- )
Как бы я поступил вместо этого? Есть ли хороший шаблон дизайна для подобных ситуаций?
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); }
Затем в дереве с помощью класса:
Механизм запускается в самом раннем возможном месте. Действие мыши перехвачено, путь к выбранному объявлен VetoableChangeListeners. В конкретном VCL исследуется изменяющееся свойство, и если это выбор ведущего, то проверяется логика вето. Если требуется наложить вето, VCL создает исключение PropertyVetoException, в противном случае обработка событий мыши идет как обычно и отбор происходит. Короче говоря, это делает свойство выбора лидов ограниченным свойством.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);
Вот пример реализации 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. Эта ошибка растратила впустую несколько менуэтов моей жизни; -)