Редакторът на клетки JTree получава щракванията на мишката по различен начин в зависимост от операционната система

Създадох рамка за изобразяване/редактиране на дървовидни клетки, която наистина е малко хакерска, но работи перфектно на Windows и Linux. Изображението по-долу илюстрира примерна настройка.

въведете описание на изображението тук

Целта е, ако потребителят щракне върху изображение (цифра) 1 или 2, приложението да отговори на това щракване, но да не избере дървовидния ред. Ако потребителят щракне върху текста едно или две, приложението реагира на това щракване и избира дървовидния ред. Начинът, по който внедрих това, отново е малко хакерски. По принцип, когато потребителят щракне върху дървовидния ред, компонентът на редактора се показва (който изглежда идентичен с компонента на рендерера), а компонентът на редактора има слушател на мишката, който може да определи къде потребителят е щракнал в реда.

Фактът, че това работи на Windows/Linux, разчита на нещо, на което винаги съм смятал, че е крехко да разчитам. По принцип, ако щракнете върху реда един път, това едно щракване едновременно (a) извежда редактора и (b) активира слушателя на мишката върху компонента на редактора. Това е начина, по който го искам! Въпреки това, когато се опитате да стартирате приложението на Mac OSX (10.6.2, ако има значение), гореспоменатото крехко предположение вече не е вярно. Всеки път, когато искате да взаимодействате с дървото, трябва да щракнете два пъти (веднъж, за да активирате редактора, и отново, за да активирате слушателя на мишката).

SSCCE по-долу може да възпроизведе поведението. Разбира се, ако нямате OSX, не можете да възпроизведете нежеланото поведение, но може би все пак можете да препоръчате по-интелигентен начин за постигане на моята цел. Наблюдавайте конзолата за sysout съобщения, показващи какво се случва, докато щраквате върху различните части на дървото.

О, и SSCCE препраща към тези две изображения:

изображение 1изображение 2

package TreeTest;

import java.awt.Color;
import java.awt.Component;
import java.awt.EventQueue;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.net.URL;
import java.util.EventObject;
import javax.imageio.ImageIO;
import javax.swing.BoxLayout;
import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTree;
import javax.swing.UIManager;
import javax.swing.border.EmptyBorder;
import javax.swing.border.LineBorder;
import javax.swing.event.CellEditorListener;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeCellEditor;
import javax.swing.tree.TreeCellRenderer;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;

@SuppressWarnings("serial")
public class TreeTest extends JComponent {

    private JFrame frame;
    private DefaultTreeModel treeModel;
    private DefaultMutableTreeNode root;
    private JTree tree;

    public static void main(String[] args) {
        try {
            UIManager.setLookAndFeel(
                UIManager.getSystemLookAndFeelClassName());
        } catch (Throwable e) {
            e.printStackTrace();
        }
        EventQueue.invokeLater(new Runnable() {
            public void run() {
                try {
                    TreeTest window = new TreeTest();
                    window.frame.setVisible(true);
                    window.frame.requestFocusInWindow();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }

    public TreeTest() {
        initialize();
    }

    private void initialize() {
        frame = new JFrame("Tree Test");
        frame.setBounds(400, 400, 250, 150);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        root = new DefaultMutableTreeNode("root");
        treeModel = new DefaultTreeModel(root);
        tree = new JTree(treeModel);
        tree.setEditable(true);
        tree.getSelectionModel().setSelectionMode(
            TreeSelectionModel.SINGLE_TREE_SELECTION);
        tree.setRootVisible(false);
        tree.setShowsRootHandles(false);
        tree.setCellRenderer(new TreeRenderer());
        tree.setCellEditor(new TreeEditor());
        tree.putClientProperty("JTree.lineStyle", "None");
        tree.setBackground(Color.white);

        treeModel.insertNodeInto(new DefaultMutableTreeNode("two"), root, 0);
        treeModel.insertNodeInto(new DefaultMutableTreeNode("one"), root, 0);

        TreeNode[] nodes = treeModel.getPathToRoot(root);
        tree.expandPath(new TreePath(nodes));
        tree.addTreeSelectionListener(new TreeSelectionListener() {
            @Override
            public void valueChanged(TreeSelectionEvent e) {
                System.out.println("SELECTION CHANGED!");
            }
        });

        frame.getContentPane().add(tree);
    }

    public class TreeComponent extends JPanel {

        public TreeComponent(JLabel numIcon, JLabel numText) {
            this.setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
            this.add(numIcon);
            this.add(numText);
        }
    }

    public class TreeRenderer implements TreeCellRenderer {

        private ImageIcon oneIcon;
        private ImageIcon twoIcon;

        public TreeRenderer() {
            try {
                oneIcon = new ImageIcon(ImageIO.read(
                    new URL("http://i.imgur.com/HtHJkfI.png")));
                twoIcon = new ImageIcon(ImageIO.read(
                    new URL("http://i.imgur.com/w5jAp5c.png")));
            } catch (Exception e) {
                e.printStackTrace();
            }

        }

        @Override
        public Component getTreeCellRendererComponent(JTree tree, Object value,
            boolean selected, boolean expanded, boolean leaf, int row,
            boolean hasFocus) {
            JLabel numIcon = new JLabel();
            numIcon.setAlignmentX(JLabel.CENTER_ALIGNMENT);
            numIcon.setAlignmentY(JLabel.CENTER_ALIGNMENT);
            numIcon.setBorder(new EmptyBorder(0, 0, 0, 4));

            JLabel numText = new JLabel();

            TreeComponent comp = new TreeComponent(numIcon, numText);

            String str = (String) ((DefaultMutableTreeNode) value).getUserObject();
            if (str.equals("one")) {
                numIcon.setIcon(oneIcon);
                numText.setText("one");
            } else if (str.equals("two")) {
                numIcon.setIcon(twoIcon);
                numText.setText("two");
            }

            numText.setOpaque(true);
            if (selected) {
                numText.setBackground(new Color(209, 230, 255));
                numText.setBorder(new LineBorder(
                    new Color(132, 172, 221), 1, false));
            } else {
                numText.setBackground(Color.white);
                numText.setBorder(new LineBorder(Color.white, 1, false));
            }
            comp.setFocusable(false);
            comp.setBackground(Color.white);
            return comp;
        }
    }

    public class TreeEditor implements TreeCellEditor {

        private TreeRenderer rend;
        private TreeComponent editorComponent;
        private JTree tree;
        private DefaultTreeModel treeModel;
        private DefaultMutableTreeNode node;
        private String str;

        public TreeEditor() {
            rend = new TreeRenderer();
        }

        @Override
        public Component getTreeCellEditorComponent(
            final JTree tree, final Object value, boolean isSelected,
            boolean expanded, boolean leaf, int row) {
            this.tree = tree;
            treeModel = (DefaultTreeModel) tree.getModel();
            node = (DefaultMutableTreeNode) value;
            Object userObject = node.getUserObject();
            this.str = (String) userObject;

            TreeNode[] nodes = treeModel.getPathToRoot(node);
            final TreePath path = new TreePath(nodes);

            editorComponent = (TreeComponent) rend.getTreeCellRendererComponent(
                tree, value, isSelected, expanded, leaf, row, false);
            editorComponent.addMouseListener(new MouseAdapter() {
                public void mousePressed(MouseEvent e) {
                    TreeEditor.this.stopCellEditing();
                    int x = e.getX();
                    if (x >= 0 && x <= 16) {
                        System.out.println(
                            "you clicked the image for row " + str);
                    } else if (x > 16) {
                        System.out.println(
                            "you clicked the text for row " + str);
                        tree.setSelectionPath(path);
                    }
                }
            });
            return editorComponent;
        }

        @Override
        public boolean isCellEditable(EventObject anEvent) {
            return true;
        }

        @Override
        public boolean shouldSelectCell(EventObject anEvent) {
            return false;
        }

        @Override
        public boolean stopCellEditing() {
            tree.cancelEditing();
            return false;
        }

        @Override
        public Object getCellEditorValue() {
            return null;
        }

        @Override
        public void cancelCellEditing() {
        }

        @Override
        public void addCellEditorListener(CellEditorListener l) {
        }

        @Override
        public void removeCellEditorListener(CellEditorListener l) {
        }
    }
}

person The111    schedule 31.03.2013    source източник
comment
несвързано или не, не копах: внедряването на вашия редактор е невалидно! Той не трябва да действа върху дървото директно, той трябва да уведоми своите слушатели, когато е готов. Малки отклонения: не създавайте нови компоненти в getXXComponent, не добавяйте слушатели повече от веднъж (този mouseListener така или иначе изглежда странно, тъй като взаимодейства с дървото, което не би трябвало)   -  person kleopatra    schedule 01.04.2013
comment
добре, просто прочетете коментарите си към отговора на @trashgod - не е предназначен да бъде редактор :-) И аз виждам проблема: зоната между рендеринг и редактор е хлъзгава земя, нещо като не-редактор-но-мишка -активен не се поддържа и е труден за фалшифициране. Моето лично предпочитание в такъв случай е да не използвам погрешно редактора, а да добавя контролна логика върху самото дърво ... ще проверя какво имаме в инкубатора на swingx (все пак ще бъде наполовина изпечен)   -  person kleopatra    schedule 01.04.2013
comment
@kleopatra Благодаря, опитвам се да прегледам някои други опции. Защо казвате да не създавате нови компоненти в getXXComponent?   -  person The111    schedule 01.04.2013
comment
@kleopatra Измислих нов подход, който все още вероятно е малко хак, но изобщо не използва редактор. Ще се радвам на вашите коментари като известен суинг гуру. :-) Вижте тук, моля. Благодаря.   -  person The111    schedule 02.04.2013
comment
всъщност не разбирам нуждата ви да не използвате обикновен редактор: всички данни, които са представени от различните компоненти в компонента за изобразяване/редактиране, са свойства на userObject на вашия възел - внедрете редактора правилно и оставете го да си свърши работата, тогава всичко ще си дойде на мястото, без никакви трикове :-)   -  person kleopatra    schedule 02.04.2013


Отговори (3)


Бързо описание на това как да внедрите сложни редактори на клетки. Предположението е, че всички интерактивни елементи на редактора наистина редактират свойство на userObject на възела - какъвто е случаят в пълния код на OP.

Сътрудниците

  • фалшив обект с данни с няколко свойства
  • средство за изобразяване с няколко деца, всяко от които е свързано с едно свойство на обекта с данни
  • редактор, който използва "жив" екземпляр на компонента за изобразяване, който добавя слушатели, за да спазва договора на редактора, ако е необходимо

Някакъв код (очевидно не използваем за реална употреба, само за да разберете идеята :)

public static class ViewProvider extends AbstractCellEditor 
    implements TreeCellEditor, TreeCellRenderer {

    private JCheckBox firstBox;
    private JButton colorButton;
    private JComponent nodePanel;
    private JButton nameButton;

    private Data data;
    private boolean ignore;

    public ViewProvider(boolean asEditor) {
        initComponents();
        if (asEditor)
          installListeners();
    }

    protected void initComponents() {
        nodePanel = new JPanel();
        nodePanel.setOpaque(false);
        firstBox = new JCheckBox();
        colorButton = new JButton();
        // if we need something clickable use something ... clickable :-)
        nameButton = new JButton();
        nameButton.setContentAreaFilled(false);
        nameButton.setOpaque(true);
        nameButton.setBorderPainted(false);
        nameButton.setMargin(new Insets(0, 0, 0, 0));
        nodePanel.add(firstBox);
        nodePanel.add(colorButton);
        nodePanel.add(nameButton);
    }

    protected void installListeners() {
        ActionListener cancel = new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                cancelCellEditing();
            }

        };
        nameButton.addActionListener(cancel);
        ActionListener stop = new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                stopCellEditing();
            }

        };
        firstBox.addActionListener(stop);
        // Note: code for using a button to trigger opening a dialog
        // is in the tutorial, should replace this
        colorButton.addActionListener(stop);
    }

    @Override
    public Component getTreeCellRendererComponent(JTree tree, Object value,
            boolean selected, boolean expanded, boolean leaf, int row,
            boolean hasFocus) {
        Data data = (Data) ((DefaultMutableTreeNode) value).getUserObject();
        firstBox.setSelected(data.isVisible);
        colorButton.setBackground(data.color);
        nameButton.setText(data.name);
        nameButton.setBackground(selected ? Color.YELLOW : tree.getBackground());
        nameButton.setFont(tree.getFont());
        return nodePanel;
    }

    @Override
    public Component getTreeCellEditorComponent(JTree tree, Object value,
            boolean isSelected, boolean expanded, boolean leaf, int row) {
        // copy to not fiddle with the original
        data = new Data((Data) ((DefaultMutableTreeNode) value).getUserObject());
        ignore = true;
        getTreeCellRendererComponent(tree, value, isSelected, expanded, leaf, row, false);
        ignore = false;
        return nodePanel;
    }

    @Override
    public Object getCellEditorValue() {
        return data;
    }

    @Override
    public boolean shouldSelectCell(EventObject anEvent) {
        // at this point the editing component is added to the tree
        // and the mouse coordinates still in tree coordinates
        if (anEvent instanceof MouseEvent) {
            MouseEvent me = (MouseEvent) anEvent;
            Point loc = SwingUtilities.convertPoint(me.getComponent(), 
                    me.getPoint(), nodePanel);
            return loc.x >= nameButton.getX();
        }
        return false;
    }

    @Override
    public boolean stopCellEditing() {
        if (ignore) return false;
        // real-world data will have setters
        data.isVisible = firstBox.isSelected();
        return super.stopCellEditing();
    }

    @Override
    public void cancelCellEditing() {
        if (ignore) return;
        data = null;
        super.cancelCellEditing();
    }

}

// simple Data - obviously not for production 
public static class Data {
    boolean isVisible;
    Color color;
    String name;

    public Data(boolean isVisible, Color color,
            String name) {
        this.isVisible = isVisible;
        this.color = color;
        this.name = name;
    }

    /**
     * A copy constructor to allow editors to manipulate its
     * properties without changing the original.
     * 
     * @param original
     */
    public Data(Data original) {
        this.isVisible = original.isVisible;
        this.color = original.color;
        this.name = original.name;
    }
}

// usage:
DefaultMutableTreeNode root = new DefaultMutableTreeNode(
    new Data(true, Color.RED, "someName"));
root.add(new DefaultMutableTreeNode(new Data(true, Color.GREEN, "other")));
root.add(new DefaultMutableTreeNode(new Data(false, Color.BLUE, "whatagain")));
root.add(new DefaultMutableTreeNode(new Data(false, Color.YELLOW, "dummy")));

JTree tree = new JTree(root);
tree.setCellRenderer(new ViewProvider(false));
tree.setCellEditor(new ViewProvider(true));
tree.setEditable(true);
person kleopatra    schedule 02.04.2013
comment
@mKorbel направих ли? Не мога да си спомня и няма смисъл за мен сега :-) - person kleopatra; 02.04.2013
comment
+1, изглежда, че работи, въпреки че повтарям забавлението на @mKorbel, че вече поддържате използването на редактор за това поведение. :-) По-важното обаче е, че не мога да разбера защо вашият код тук работи на OSX, но моят в OP не. Изглежда не мога да разбера какво правите по различен начин, което позволява едно и също щракване да се използва както за (а) отваряне на редактора, така и за (б) ИЗПОЛЗВАНЕ на редактора. Някакви идеи? - person The111; 03.04.2013
comment
@The111 Всеки бутон съответства или на булев флаг, или на цвета на GPSObject и го променя, което се нарича ... редактиране :-) Нищо специално, просто играя по правилата за редактори: първоначалният тригер е (както в: трябва е имало бъгове в по-ранни версии) прехвърлени в редактора, след като бъдат добавени. - person kleopatra; 03.04.2013
comment
всъщност, не следя съвсем забавлението @mKorbel, предубеден бих заподозрял недоразумение - използването на JPanel в рендеринг/редактор не е проблем, докато разширението на всяко JSething с нова роля е рендиращ/редактор :-) Така че, ако някой има връзка към мен, проповядвайки срещу първото, моля, уведомете, трябва да се изтрие ‹g› - person kleopatra; 03.04.2013
comment
1. JButton(s) с каквото и да е отстрани (икона, текст, подреждане, внедрени слушатели) е(са) най-доброто от, най-.... , 2. страхотно като вас, 3. моята FlameWar е изтрита, запазена за лоши времена :-) 4. забележете, че все още не е възможно да се търси в собствените коментари, въпросът беше затворен два пъти, след което ги изтрих 5. благодаря ви за ясните идеи в кода след последната редакция - person mKorbel; 03.04.2013
comment
Съжалявам, забавлението ми се основаваше на факта, че мислех, че и вие, и trashgod по-рано ми казахте, че този проблем изобщо не е правилното място за използване на редактор. Но предполагам, че това беше, защото моят SSCCE не илюстрира правилно действителната ми употреба. Така че, вероятно моята грешка. Но все още съм объркан, тъй като вашият редактор връща JComp със слушатели, които улавят първото щракване на мишката, а моят редактор също връщаше JComp със слушатели, което би не улови първото щракване на мишката (само за OSX). Чудя се дали е защото сте използвали слушатели на бутон, а не на панел. Трябва да тествам още. - person The111; 03.04.2013
comment
@The111 няма проблем :-) Отново: внедряването на редактора ви беше невалидно - което може да има каквито и да е непредсказуеми (четете: неочаквани) резултати - person kleopatra; 04.04.2013

Не съм сигурен, че разбирам изискването, но примерът по-долу добавя удобно свързване на клавиши и работи с подхода на единично щракване, показан тук. Избрах да отменя canEditImmediately(), както е предложено от kleopatra, при спазване на нейните предупреждения относно използваемостта.

изображение на дърво

import java.awt.Component;
import java.awt.EventQueue;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.net.URL;
import java.util.EventObject;
import javax.imageio.ImageIO;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTree;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellEditor;
import javax.swing.tree.DefaultTreeCellRenderer;

/**
 * @see https://stackoverflow.com/a/15738813/230513
 * @see https://stackoverflow.com/q/15625424/230513
 */
public class Test {

    private static Icon one;
    private static Icon two;

    private void display() {
        JFrame f = new JFrame("Test");
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        final JTree tree = new JTree();
        for (int i = 0; i < tree.getRowCount(); i++) {
            tree.expandRow(i);
        }
        final TreeRenderer renderer = new TreeRenderer();
        tree.setCellRenderer(renderer);
        tree.setCellEditor(new TreeEditor(tree, renderer));
        tree.setEditable(true);
        tree.getInputMap().put(
            KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), "startEditing");
        f.add(new JScrollPane(tree));
        f.pack();
        f.setLocationRelativeTo(null);
        f.setVisible(true);
    }

    private static class TreeRenderer extends DefaultTreeCellRenderer {

        @Override
        public Component getTreeCellRendererComponent(JTree tree, Object value,
            boolean sel, boolean exp, boolean leaf, int row, boolean hasFocus) {
            DefaultMutableTreeNode node = (DefaultMutableTreeNode) value;
            String s = node.getUserObject().toString();
            if ("colors".equals(s)) {
                setOpenIcon(one);
                setClosedIcon(one);
            } else if ("sports".equals(s)) {
                setOpenIcon(two);
                setClosedIcon(two);
            } else {
                setOpenIcon(getDefaultOpenIcon());
                setClosedIcon(getDefaultClosedIcon());
            }
            super.getTreeCellRendererComponent(
                tree, value, sel, exp, leaf, row, hasFocus);
            return this;
        }
    }

    private static class TreeEditor extends DefaultTreeCellEditor {

        public TreeEditor(JTree tree, DefaultTreeCellRenderer renderer) {
            super(tree, renderer);
        }

        @Override
        public Component getTreeCellEditorComponent(JTree tree, Object value,
            boolean isSelected, boolean exp, boolean leaf, int row) {
            Component c = super.getTreeCellEditorComponent(
                tree, value, isSelected, exp, leaf, row);
            DefaultMutableTreeNode node = (DefaultMutableTreeNode) value;
            String s = node.getUserObject().toString();
            if ("colors".equals(s)) {
                editingIcon = one;
            } else if ("sports".equals(s)) {
                editingIcon = two;
            }
            return c;
        }

        @Override
        protected boolean canEditImmediately(EventObject event) {
            if ((event instanceof MouseEvent)
                && SwingUtilities.isLeftMouseButton((MouseEvent) event)) {
                MouseEvent me = (MouseEvent) event;

                return ((me.getClickCount() >= 1)
                    && inHitRegion(me.getX(), me.getY()));
            }
            return (event == null);
        }
    }

    public static void main(String[] args) throws Exception {
        one = new ImageIcon(ImageIO.read(
            new URL("http://i.imgur.com/HtHJkfI.png")));
        two = new ImageIcon(ImageIO.read(
            new URL("http://i.imgur.com/w5jAp5c.png")));
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                new Test().display();
            }
        });
    }
}
person trashgod    schedule 01.04.2013
comment
Благодаря както винаги за приноса, trashgod. Не мисля обаче, че отговорът, който търся, е там. Може би трябва да обясня изискванията си по-добре, тъй като разбирам объркването ви. Проверете моята действителна кандидатура тук. Натиснете CTRL+N, за да направите нов файл и след това играйте с дървото отляво. В моето приложение потребителят всъщност никога не редактира дървото, той просто го използва за взаимодействие. Всяка икона в дървовиден ред прави нещо различно и редът не трябва да бъде избран, освен ако потребителят не кликне върху текста. - person The111; 01.04.2013
comment

Цяло число е просто:

int x;

Име за горното е дадено от:

typedef int x_type;

Указател към int е:

int *p;

Типът му би бил:

typedef int *p_type;

Функция, наречена foo вземаща double``and returning anint` е:

int foo(double);

Дефинирането на типа на foo ще бъде:

typedef int foo_type(double);

Сега указател към горното трябва да приеме *, но () (извикване на функция) се свързва по-здраво от * (дереференция), така че скобите:

typedef int (*ptr_to_foo_type)(double);

Това може да е по-добре написано:

typedef foo_type *ptr_to_foo_type;

както някои предлагат да се пише за яснота.

Идеята е, че описанието на типа изглежда (донякъде) като неговата употреба. Лошо опорочена идея, предвид префикс/постфикс оператори, с което всички са съгласни. Но вече е твърде късно за промяна.

- person The111; 01.04.2013
comment
Съжаляваме, опитвате се да победите делегата на потребителския интерфейс с ненужен редактор — обречен подход. Защо не погледнете Outline, което прави добавянето на компоненти на ред лесно. Работи с всеки TreeModel. - person trashgod; 01.04.2013
comment
Съгласете се, редакторът никога не е бил необходим. Измислих нов подход, който вероятно все още е малко хак, но изобщо не използва редактор. Ще се радвам на вашите коментари. Вижте тук, моля. Благодаря. - person The111; 02.04.2013

Измислих нещо, което създава желаното от мен поведение и изобщо не използва TreeCellEditor. Вместо това дървото не може да се редактира и се създава с персонализирано разширение JTree, което processMouseEvent е отменено. Получих тази идея тук.

Изглежда, че работи перфектно, но все още е малко хакерско (извършва цикъл от изчисления, за да определи къде е началото на дървовидната клетка, тъй като това може да варира в зависимост от отстъпа). Също така почти деактивирах събития от типа mouseClicked и mouseReleased и контролирам JTreeMod само с mousePressed събития. Не съм сигурен дали това ще ме ухапе по-късно или е лоша практика, но не ми хареса персонализираният ми код да се изпълнява 3 пъти подред за всички събития. Също така все още не съм успял да тествам на операционна система, различна от Windows.

Ето изхода от конзолата след щракване, в серия (1) изображението едно (2) текстът едно (3) изображението две (4) текстът две. Отново, това перфектно изпълнява моето желано поведение.

you clicked the image for row 1. this was detected, but no selection will happen!
you clicked the text for row 1. this was detected, and selection WILL happen!
SELECTION CHANGED!
you clicked the image for row 2. this was detected, but no selection will happen!
you clicked the text for row 2. this was detected, and selection WILL happen!
SELECTION CHANGED!

И ето го новият SSCCE:

package TreeTest;

import java.awt.Color;
import java.awt.Component;
import java.awt.EventQueue;
import java.awt.event.MouseEvent;
import java.net.URL;

import javax.imageio.ImageIO;
import javax.swing.BoxLayout;
import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTree;
import javax.swing.UIManager;
import javax.swing.border.EmptyBorder;
import javax.swing.border.LineBorder;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeCellRenderer;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;

@SuppressWarnings("serial")
public class TreeTest2 extends JComponent {

    private JFrame frame;
    private DefaultTreeModel treeModel;
    private DefaultMutableTreeNode root;
    private JTreeMod tree;

    public static void main(String[] args) {
        try {
            UIManager.setLookAndFeel(
                UIManager.getSystemLookAndFeelClassName());
        } catch (Throwable e) {
            e.printStackTrace();
        }
        EventQueue.invokeLater(new Runnable() {
            public void run() {
                try {
                    TreeTest2 window = new TreeTest2();
                    window.frame.setVisible(true);
                    window.frame.requestFocusInWindow();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }

    public TreeTest2() {
        initialize();
    }

    private void initialize() {
        frame = new JFrame("Tree Test");
        frame.setBounds(400, 400, 250, 150);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        root = new DefaultMutableTreeNode("root");
        treeModel = new DefaultTreeModel(root);
        tree = new JTreeMod(treeModel);
        tree.setEditable(false);
        tree.getSelectionModel().setSelectionMode(
            TreeSelectionModel.SINGLE_TREE_SELECTION);
        tree.setRootVisible(false);
        tree.setShowsRootHandles(true);
        tree.setCellRenderer(new TreeRenderer());
        tree.putClientProperty("JTree.lineStyle", "None");
        tree.setBackground(Color.white);

        DefaultMutableTreeNode one = new DefaultMutableTreeNode("one");
        DefaultMutableTreeNode two = new DefaultMutableTreeNode("two");

        treeModel.insertNodeInto(one, root, 0);
        treeModel.insertNodeInto(two, one, 0);

        TreeNode[] nodes = treeModel.getPathToRoot(root);
        tree.expandPath(new TreePath(nodes));
        nodes = treeModel.getPathToRoot(one);
        tree.expandPath(new TreePath(nodes));
        tree.addTreeSelectionListener(new TreeSelectionListener() {
            @Override
            public void valueChanged(TreeSelectionEvent e) {
                System.out.println("SELECTION CHANGED!");
            }
        });

        frame.getContentPane().add(tree);
    }

    public class TreeRenderer implements TreeCellRenderer {

        private ImageIcon oneIcon;
        private ImageIcon twoIcon;

        public TreeRenderer() {
            try {
                oneIcon = new ImageIcon(ImageIO.read(
                        new URL("http://i.imgur.com/HtHJkfI.png")));
                twoIcon = new ImageIcon(ImageIO.read(
                        new URL("http://i.imgur.com/w5jAp5c.png")));
            } catch (Exception e) {
                e.printStackTrace();
            }

        }

        @Override
        public Component getTreeCellRendererComponent(JTree tree, Object value,
            boolean selected, boolean expanded, boolean leaf, int row,
            boolean hasFocus) {
            JLabel numIcon = new JLabel();
            numIcon.setAlignmentX(JLabel.CENTER_ALIGNMENT);
            numIcon.setAlignmentY(JLabel.CENTER_ALIGNMENT);
            numIcon.setBorder(new EmptyBorder(0, 0, 0, 4));

            JLabel numText = new JLabel();

            JPanel comp = new JPanel();
            comp.setLayout(new BoxLayout(comp, BoxLayout.X_AXIS));
            comp.add(numIcon);
            comp.add(numText);

            String str = (String) ((DefaultMutableTreeNode) value).getUserObject();
            if (str.equals("one")) {
                numIcon.setIcon(oneIcon);
                numText.setText("one");
            } else if (str.equals("two")) {
                numIcon.setIcon(twoIcon);
                numText.setText("two");
            }

            numText.setOpaque(true);
            if (selected) {
                numText.setBackground(new Color(209, 230, 255));
                numText.setBorder(new LineBorder(
                    new Color(132, 172, 221), 1, false));
            } else {
                numText.setBackground(Color.white);
                numText.setBorder(new LineBorder(Color.white, 1, false));
            }
            comp.setFocusable(false);
            comp.setBackground(Color.white);
            return comp;
        }
    }

    public class JTreeMod extends JTree {
        public JTreeMod(DefaultTreeModel treeModel) {
            super(treeModel);
        }

        @Override
        protected void processMouseEvent(MouseEvent e) {
            int type = e.getID();
            if (type == MouseEvent.MOUSE_CLICKED || type == MouseEvent.MOUSE_RELEASED) {
                // do nothing
            } else if (type == MouseEvent.MOUSE_PRESSED) {
                int x = e.getX();
                int y = e.getY();

                int row = this.getRowForLocation(x, y);
                if (row == -1) {
                    super.processMouseEvent(e);
                    return;
                }

                int xOffset = x;
                int row1 = row;
                while (row1 == row) {
                    xOffset--;
                    row1 = this.getRowForLocation(xOffset, y);
                }
                xOffset++;

                if (x - xOffset <= 16) {
                    System.out.println("you clicked the image for row " + (row + 1) +
                            ". this was detected, but no selection will happen!");
                    return;
                } else {
                    System.out.println("you clicked the text for row " + (row + 1) + 
                            ". this was detected, and selection WILL happen!");
                    super.processMouseEvent(e);
                }
            } else {
                super.processMouseEvent(e);
            }
        }
    }
}
person The111    schedule 01.04.2013
comment
+1 за sscce, въпреки че мисля, че подчертава неудобството на подхода. В Outline, това биха били нормални програми за изобразяване (и може би редактори) във вашия RowModel. - person trashgod; 02.04.2013
comment
@trashgod благодаря, скоро ще разгледам Outline, но междувременно това решение ще ме умори. - person The111; 02.04.2013