Лучший способ остановить изменение выбора JTree?
У меня есть диалоговое окно, в котором каждая запись в JTree имеет соответствующие параметры на другой панели, которая обновляется при изменении выбора. Если для параметров одной из записей задано недопустимое состояние, когда пользователь пытается перейти к другой записи в дереве, я хочу, чтобы появилось диалоговое окно с сообщением об ошибке, и выбор не изменился.
Я попытался сделать это с помощью valueChangeListener на JTree, но в настоящее время необходимо иметь вызов метода valueChanged "setSelectionRow" для старого выбора в случае ошибки. Чтобы я не получил Stackru, перед тем, как сделать это, я установил для логического значения "isError" значение true, чтобы можно было игнорировать новое событие valueChanged. Почему-то у меня такое чувство, что это не лучшее решение.;-)
Как бы я пошел об этом вместо этого? Есть ли хороший шаблон дизайна для подобных ситуаций?
7 ответов
Не уверен, что это лучший метод, но, возможно, вы могли бы поместить FocusListener в компонент (ы), которые вы хотите проверить... вызвать вашу проверку при вызове события и затем использовать событие затем, если вы не хотите, чтобы фокус был перемещен потому что проверка не проходит?
Позднее редактировать:
По крайней мере, с 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, в противном случае обработка события мыши происходит как обычно, и происходит выбор. Короче говоря, это делает свойство выбора потенциальных клиентов ограниченным.
Чтобы предотвратить выбор, я просто создал подкласс DefaultTreeSelectionModel и переопределил все методы для проверки объектов, которые я не хотел выбирать (примеры "DisplayRepoOwner" в моем примере ниже). Если объект был в порядке, я выбрал супер метод; в противном случае я не сделал. Я установил модель выбора моего 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);
}
}
Установите 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, но вы поняли...
Наткнулся на эту тему, изучая решение той же проблемы. Во-первых, позвольте мне рассказать вам вещи, которые не сработали. Я попытался зарегистрировать MouseListeners и все это в дереве. Проблема заключалась в том, что слушатели мыши TreeUI обрабатывали событие до того, как это сделал мой JTree, то есть было слишком поздно устанавливать флаг или что-то в этом роде. Кроме того, это решение дало некрасивый код, и я бы его вообще избегал.
Так что теперь для актуального решения!
После нескольких вызовов Thread.dumpStack() для получения дампа стека я нашел метод, который я искал для переопределения. Я расширил BasicTreeUI и переопределил "защищенный void selectPathForEvent(путь TreePath, событие MouseEvent)".
Это даст вам доступ к событию мыши, которое вызвало выделение до того, как оно произойдет. Затем вы можете использовать любую логику, в которой вы нуждаетесь, либо event.consume(), и возвращать, если хотите остановить выбор, сделать любой выбор, который хотите, или передать его для обработки по умолчанию, вызвав super.selectPathForEvent(path, event);
Просто не забудьте установить интерфейс, который вы создали в JTree. Эта ошибка потратила впустую несколько минут моей жизни;-)