Komponente wie Outlook Empfänger-Zeile

Export

Grünschnabel
Hallo zusammen,

Ich suche eine Komponente wie man sie von der Outlook Empfänger-Zeile kennt.
Ich möchte auf einem JTextPane Komponenten hinzufügen (z. B. JLabel). Diese möchte ich verschieben können und auch wieder löschen können. Der Caret soll beim Löschen die gesamte Komponente löschen und auch beim Pfeiltasten navigieren soll diese als ein Zeichen behandelt werden.

Gibt es sowas schon in irgendeinem Framework? Bzw. Gibts dafür einen Namen?
Ich hab da schon ein bischen was versucht. Kann das auchmal posten, falls keiner eine Komponente dieser Art kennt.

Danke und Gruß
ExPörT
 
Kannst du vielleicht eine Skizze oder sowas zeigen, was du meinst? Ich kann mir darunter nicht wirklich viel was vorstellen ...
 
Im Anhang habe ich mal einen Screenshot gemacht. Genau so etwas möchte ich haben. Nur mehrzeilig...

Gruß
ExPörT
 

Anhänge

  • Untitled.png
    Untitled.png
    8,3 KB · Aufrufe: 18
Hi,
also ich kenn sowas auch nicht, aber mein Ehrgeiz war dadurch mal wieder geweckt worden, sodass ich selber eine Version entwickelt habe. Diese ist an das Tag-it im jQuery-Format angelehnt. :) Bei meiner Version wird demzufolge auch die Eingabe mit Enter bestätigt.

So sieht das dann aus:

JTagComponentDemo.PNG

Hier die JTagComponent:
Java:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.ComponentOrientation;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.Insets;
import java.awt.Toolkit;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.ArrayList;
import java.util.List;

import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.border.Border;


/**
 * @author fabioh
 * @author $Author$
 * @version $Revision$ $Date$
 */
public class JTagComponent extends JPanel
{
	private final Icon cancelIcon = new ImageIcon(Toolkit.getDefaultToolkit().createImage(
			getClass().getResource("cancel.png")));
	private final List<JTag> tagList = new ArrayList<JTag>();
	
	/**
	 * 
	 */
	public JTagComponent() {
		final FlowLayout flowLayout = new FlowLayout();
		flowLayout.setAlignment(FlowLayout.LEFT);
		setLayout(flowLayout);
		setComponentOrientation(ComponentOrientation.LEFT_TO_RIGHT);
		setOpaque(true);
		setBackground(Color.WHITE);
		
		final JTextField field = new JTextField();
		field.setBorder(null);
		field.addKeyListener(new KeyListener()
						{
			@Override
			public void keyTyped(final KeyEvent e) {
				// NOOP;
			}
			
			@Override
			public void keyReleased(final KeyEvent e) {
				// NOOP;
			}
			
			@Override
			public void keyPressed(final KeyEvent e) {
				if(KeyEvent.VK_ENTER == e.getKeyCode()) {
					onEnterKeyPressed(field, e);
				}
			}
		});
		field.setPreferredSize(new Dimension(100, 20));
		add(field);
	}
	
	protected void onEnterKeyPressed(final JTextField textField, final KeyEvent e) {
		final String text = textField.getText();
		if(text.length() > 0) {
			final JTag tag = new JTag(text, cancelIcon)
								{
				@Override
				protected void onCloseButtonPressed(final MouseEvent e) {
					tagList.remove(this);
					JTagComponent.this.remove(this);
					JTagComponent.this.updateUI();
				}
			};
			tagList.add(getComponentCount() - 1, tag);
			add(tag, getComponentCount() - 1);
			textField.setText("");
			updateUI();
		}
	}
	
	/**
	 * @return the tagList
	 */
	public List<JTag> getTagList() {
		return tagList;
	}
	
	/**
	 * @author fabioh
	 * @author $Author$
	 * @version $Revision$ $Date$
	 */
	public abstract class JTag extends JPanel
	{
		private final JLabel label;
		
		/**
		 * @param text
		 * @param closeIcon
		 */
		public JTag(final String text, final Icon closeIcon) {
			setLayout(new BorderLayout());
			setOpaque(true);
			setBackground(Color.WHITE);
			setBorder(new RoundedBorder());
			
			label = new JLabel(text);
			label.setOpaque(true);
			label.setBackground(Color.WHITE);
			label.setForeground(Color.BLUE);
			add(label, BorderLayout.CENTER);
			
			final JLabel closeButton = new JLabel(closeIcon);
			closeButton.setOpaque(true);
			closeButton.setBackground(Color.WHITE);
			closeButton.addMouseListener(new MouseListener()
								{
				@Override
				public void mouseReleased(final MouseEvent e) {
				}
				
				@Override
				public void mousePressed(final MouseEvent e) {
				}
				
				@Override
				public void mouseExited(final MouseEvent e) {
				}
				
				@Override
				public void mouseEntered(final MouseEvent e) {
				}
				
				@Override
				public void mouseClicked(final MouseEvent e) {
					onCloseButtonPressed(e);
				}
			});
			add(closeButton, BorderLayout.EAST);
		}
		
		/**
		 * @param e
		 */
		protected abstract void onCloseButtonPressed(final MouseEvent e);
		
		/**
		 * @return the label
		 */
		public JLabel getLabel() {
			return label;
		}
		
		/**
		 * @return the text
		 */
		public String getText() {
			return label.getText();
		}
	}
	
	private final class RoundedBorder implements Border
	{
		@Override
		public void paintBorder(final Component c, final Graphics g, final int x,
				final int y, final int width, final int height) {
			g.setColor(Color.LIGHT_GRAY);
			g.drawRoundRect(x, y, width - 1, height - 1, 10, 10);
		}
		
		@Override
		public boolean isBorderOpaque() {
			return true;
		}
		
		@Override
		public Insets getBorderInsets(final Component c) {
			return new Insets(7, 7, 7, 7);
		}
	}
}

Hier noch eine kleine Main, um das Ganze auch zu testen:
Java:
import javax.swing.JFrame;


/**
 * @author fabioh
 * @author $Author$
 * @version $Revision$ $Date$
 */
public class JTagTester extends JFrame
{
	/**
	 * 
	 */
	public JTagTester() {
		super("JTag (Demo)");
		setDefaultCloseOperation(EXIT_ON_CLOSE);
		setSize(200, 100);
		setLocationRelativeTo(null);
		
		add(new JTagComponent());
		
		setVisible(true);
	}
	
	/**
	 * @param args
	 */
	public static void main(final String[] args) {
		new JTagTester();
	}
}

Im Anhang findet ihr noch einen Screenshot von der Oberfläche und das Image für den Cancel-Button.

ps.: Wenn du deine Version fertig hast, würde ich die auch gerne mal sehen. :)

Gruß

Fabio

Cancel-Button:
cancel.png
 
Zuletzt bearbeitet:
Naja, das ist nicht ganz das was ich meinte.
Ich möchte den Text einfach mit "getText()" abfragen und so etwas bekommen: "Das ist ein${sehr}${sehr}langer Text, der auch${einige}Platzhalter enthält". Das entspricht dem Screenshot. (siehe unten)

Ich hab jetzt mal was. Da ist aber noch ein Fehler drin, vllt kann mir jemand was dazu sagen.

Erstmal der Code

Die neue JTextPane Komponente
Code:
public class JComponentTextPane extends JTextPane {
    private static final String STYLE_PREFIX = "COMP";
    private DragSource ds = new DragSource();
    private StyleContext context;
    private static Object SYNC = new Object();
    private static int COUNTER = 0;

    public JComponentTextPane() {
        super();
        this.context = new StyleContext();
        final StyledDocument document = new DefaultStyledDocument(context);
        super.setDocument(document);

        setTransferHandler(new MyTransferHandler());

        this.addKeyListener(new KeyAdapter() {
            @Override
            public void keyPressed(KeyEvent keyEvent) {
                int caretPosition = getCaretPosition();

                if (keyEvent.getKeyCode() == KeyEvent.VK_LEFT) {
                    moveCursorLeft(caretPosition);
                } else if (keyEvent.getKeyCode() == KeyEvent.VK_RIGHT) {
                    moveCursorRight(caretPosition + 1);
                }
            }
        });

        this.setDocument(new DefaultStyledDocument() {
            @Override
            public void insertString(int i, String s, AttributeSet attributeSet) throws BadLocationException {
                super.insertString(i, s, attributeSet);    //To change body of overridden methods use File | Settings | File Templates.
                System.out.println("------------------------------------");
                System.out.println("insert: " + JComponentTextPane.this.getText());
            }

            @Override
            public void remove(int i, int i1) throws BadLocationException {
                int caretPosition = getCaretPosition();
                if (!removeComponent(caretPosition)) {
                    super.remove(i, i1);
                }
            }
        });
    }

    /**
     *
     * @param compName
     * @return the index of the removed comp, otherwhise -1
     */
    public int removeComponent(String compName) {
        String text = getText();
        int startPos = -1;
        for (int i = 0; i < text.length(); i++) {
            if (this.getCompAtCursor(i).equals(compName)) {
                startPos = i;
                break;
            }
        }

        if (startPos == -1) {
            return -1;
        }

        Style style = this.context.getStyle(compName);
        DefaultTextComponent component = (DefaultTextComponent) StyleConstants.getComponent(style);
        this.removeComponent(startPos - 1 + component.getId().length());
        return startPos;
    }

    private boolean removeComponent(int i) {
        String compName = this.getCompAtCursor(i);
        if (!compName.startsWith(STYLE_PREFIX) || i < 0) {
            return false;
        }
        int startPos = i;
        while (this.getCompAtCursor(startPos).equals(compName)) {
            startPos--;
            setCaretPosition(startPos);
        }

        try {
            this.getDocument().remove(startPos, i - startPos);
            Style style = this.context.getStyle(compName);
//            Component component = StyleConstants.getComponent(style);
//            System.out.println(component);
            ((DefaultStyledDocument) getDocument()).removeStyle(style.getName());
            this.updateUI();

        } catch (BadLocationException e) {
            e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
        }
        setCaretPosition(startPos);
        return true;
    }

    private void moveCursorRight(int i) {
        String compName = this.getCompAtCursor(i);
        if (!compName.startsWith(STYLE_PREFIX)) {
            return;
        }
        while (this.getCompAtCursor(i).equals(compName)) {
            i++;
            System.out.println("move...");
        }
        setCaretPosition(i - 2);
    }

    private void moveCursorLeft(int i) {
        String compName = this.getCompAtCursor(i);
        if (!compName.startsWith(STYLE_PREFIX)) {
            return;
        }
        while (this.getCompAtCursor(i).equals(compName)) {
            i--;
            System.out.println("move...");
        }
        setCaretPosition(i + 1);
    }

    private String getCompAtCursor(int i) {
        Element element = ((DefaultStyledDocument) getDocument()).getCharacterElement(i - 1);
        Object attribute = element.getAttributes().getAttribute(StyleConstants.NameAttribute);
        return (String) attribute;
    }

    @Override
    public void insertComponent(final Component c) {
        setCaretPosition(this.getText().length());
        final Style addStyle = this.context.addStyle(STYLE_PREFIX + COUNTER++ + "", null);
        StyleConstants.setComponent(addStyle, c);
        final DefaultTextComponent lbl = (DefaultTextComponent) c;
        lbl.setOwner(this, addStyle);
        lbl.setTextFont(getFont());
        try {
            getDocument().insertString(getCaretPosition(), lbl.getId(), addStyle);
        } catch (BadLocationException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        ds.createDefaultDragGestureRecognizer(lbl, DnDConstants.ACTION_MOVE, new DragGestureListener() {
            @Override
            public void dragGestureRecognized(final DragGestureEvent dge) {
                synchronized (SYNC) {
                    Point p = new Point(dge.getDragOrigin());
                    p = SwingUtilities.convertPoint(lbl, p, JComponentTextPane.this);
                    final int viewToModel = viewToModel(p);
                    setCaretPosition(viewToModel);

                    Transferable t = new StringSelection(lbl.getText());

                    ds.startDrag(dge, DragSource.DefaultMoveDrop, t, new DragSourceAdapter() {
                        @Override
                        public void dragDropEnd(DragSourceDropEvent dsde) {
                            if (!dsde.getDropSuccess()) {
                                return;
                            }

                            try {
                                Document document = getDocument();
                                DragSourceContext source = (DragSourceContext) dsde.getSource();
                                DefaultTextComponent lbl = (DefaultTextComponent) source.getTrigger().getComponent();
                                int length = lbl.getId().length();
                                int caretPos = getCaretPosition();

                                int postion = removeComponent(addStyle.getName());

                                // calculate caret position
                                if (caretPos > postion) {
                                    caretPos -= length;
                                }

                                // reset position in drag object
                                c.getParent().remove(lbl);

                                setCaretPosition(caretPos);
                                document.insertString(caretPos, lbl.getId(), addStyle);
                            } catch (BadLocationException e) {
                                // TODO Auto-generated catch block
                                e.printStackTrace();
                            }
                        }
                    });
                }
            }
        });
    }

    public void appendText(String text) {
        try {
            this.getDocument().insertString(
                    this.getDocument().getLength(),
                    text, null
            );
        } catch (BadLocationException e) {
            e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
        }
    }
}

TransferHandler fürs DnD
Code:
public class MyTransferHandler extends TransferHandler {
    private static final long serialVersionUID = 1L;

    public MyTransferHandler() {

    }

    /* (non-Javadoc)
    * @see javax.swing.TransferHandler#canImport(javax.swing.TransferHandler.TransferSupport)
    */
    @Override
    public boolean canImport(TransferSupport support) {
        return true;
    }


    /* (non-Javadoc)
    * @see javax.swing.TransferHandler#importData(javax.swing.TransferHandler.TransferSupport)
    */
    @Override
    public boolean importData(TransferSupport support) {
        if (!support.isDrop()) {
            return false;
        }
        return true;
    }
}

Die Komponente welche die "Tags" darstellt
Code:
public class DefaultTextComponent extends JPanel {
// ------------------------------ FIELDS ------------------------------

    private JLabel lblText;
    private JButton btnDelete;
    private JComponentTextPane jComponentTextPane;
    private Style style;

    private static Icon icon;

    static {
        try {
            icon = new ImageIcon(ImageIO.read(ClassLoader.getSystemResource("http://www.tutorials.de/images/cancel.png")));
        } catch (IOException e) {
            e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
        }
    }

    public DefaultTextComponent() {
        this.setOpaque(false);
        this.setAlignmentY(0.75f);
        this.setAlignmentX(0f);
        this.setBorder(new RoundedBorder());

        this.initComponents();
        this.initLayout();
        this.initListener();
    }

    private void initListener() {
        this.btnDelete.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent actionEvent) {
                jComponentTextPane.removeComponent(style.getName());
            }
        });
    }

    private void initLayout() {
        super.setLayout(new BorderLayout(0, 0));
        super.add(this.lblText, BorderLayout.CENTER);
        super.add(this.btnDelete, BorderLayout.EAST);
    }

    private void initComponents() {
        this.lblText = new JLabel();

        this.btnDelete = new JButton(icon);
        this.btnDelete.setMargin(new Insets(0, 0, 0, 0));
        this.btnDelete.setBounds(0, 0, 0, 0);
        this.btnDelete.setBorder(null);
        this.btnDelete.setBorderPainted(false);
    }

    @Override
    public Dimension getMaximumSize() {
        return new Dimension(0, 0);
    }

    public DefaultTextComponent(String text) {
        this();
        this.setText(text);
    }

    public String getText() {
        return this.lblText.getText();
    }

    public void setText(String text) {
        this.lblText.setText(text);
    }
    // --------------------------- CONSTRUCTORS ---------------------------

    public String getId() {
        return "${" + this.lblText.getText() + "}";
    }

    public void setTextFont(Font font) {
        this.lblText.setFont(font);
    }

    public void setOwner(JComponentTextPane jComponentTextPane, Style style) {
        this.jComponentTextPane = jComponentTextPane;
        this.style = style;
    }
}

Und finally die main()
Code:
public class TestJComponentTextPane extends JFrame {
    public static void main(String[] args) {
        new TestJComponentTextPane().setVisible(true);
    }

    public TestJComponentTextPane() {
        super("test");

        final JComponentTextPane pane = new JComponentTextPane();
        pane.setText("Das ist ein");
        pane.insertComponent(new DefaultTextComponent("sehr"));
        pane.insertComponent(new DefaultTextComponent("sehr"));
        pane.appendText("langer Text, der auch");
        pane.insertComponent(new DefaultTextComponent("einige"));
        pane.appendText("Platzhalter enthällt");
        pane.setCaretPosition(0);

        pane.setEditable(true);

        super.setLayout(new BorderLayout());
        super.add(new JScrollPane(pane));

        super.setSize(300, 300);
        super.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}


Wenn Ihr das mal startet dann bekommt man komische Grafikfehler wenn man ans Ende der Komponente mit dem Cursor (Caret) geht.

Vllt noch ein Snapshot:
Untitled.png

Gruß
ExPörT
 
Zuletzt bearbeitet von einem Moderator:
Hallo

hier mal noch eine Alternative Variante mit HTML / CSS im JTextPane:
Java:
package de.tutorials.tags;

import java.awt.Dimension;
import java.awt.event.MouseEvent;
import java.lang.reflect.Field;

import javax.swing.JEditorPane;
import javax.swing.JFrame;
import javax.swing.JTextPane;
import javax.swing.event.HyperlinkEvent;
import javax.swing.event.HyperlinkEvent.EventType;
import javax.swing.event.HyperlinkListener;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.Element;
import javax.swing.text.html.HTMLEditorKit;
import javax.swing.text.html.HTMLEditorKit.LinkController;
import javax.swing.text.html.StyleSheet;

public class SwingTextTagsExample extends JFrame {

	JTextPane textPane;

	public SwingTextTagsExample() {
		super("SwingTextTagsExample");
		setDefaultCloseOperation(EXIT_ON_CLOSE);

		textPane = new JTextPane();
		HTMLEditorKit editorKit = new HTMLEditorKit();

		StyleSheet styleSheet = editorKit.getStyleSheet();
		styleSheet.addRule("a { color:red; background-color: yellow; font-weight: bold;}");
		
		Document doc = editorKit.createDefaultDocument();

		try {
			//Swap LinkController for link handling support on an editable JTextPane 
			Field linkHandlerField = HTMLEditorKit.class.getDeclaredField("linkHandler");
			linkHandlerField.setAccessible(true);
			linkHandlerField.set(editorKit, new CustomLinkController());
		} catch (Exception e) {
			e.printStackTrace();
		}

		textPane.setEditorKit(editorKit);
		textPane.setDocument(doc);
		textPane.setPreferredSize(new Dimension(320, 240));

		// textPane.setEditable(false);
		HyperlinkListener l = new HyperlinkListener() {
			@Override
			public void hyperlinkUpdate(HyperlinkEvent e) {
				if (EventType.ACTIVATED == e.getEventType()) {

					Element sourceElement = e.getSourceElement();
					int offset = sourceElement.getStartOffset();
					int len = sourceElement.getEndOffset() - offset;
					try {
						System.out.println("Remove: " + textPane.getDocument().getText(offset, len));
						textPane.getDocument().remove(offset, len);
					} catch (BadLocationException e1) {
						e1.printStackTrace();
					}
				}

			}

		};
		textPane.addHyperlinkListener(l);

		add(textPane);
		pack();
		setVisible(true);

		textPane.setText("AAAA BBBB <a href='#'>Remove1</a> DDDD <a href='#'>Remove2</a> XXXX");

	}

	public static void main(String[] args) {
		new SwingTextTagsExample();
	}

	public static class CustomLinkController extends LinkController {
		public void mouseMoved(MouseEvent e) {
			JEditorPane editor = (JEditorPane) e.getSource();
			boolean editable = editor.isEditable();
			if (editable) {
				editor.setEditable(false);
			}
			try {
				super.mouseMoved(e);
			} finally {
				if (editable) {
					editor.setEditable(true);
				}
			}
		}

		public void mouseClicked(MouseEvent e) {
			JEditorPane editor = (JEditorPane) e.getSource();
			boolean editable = editor.isEditable();
			if (editable) {
				editor.setEditable(false);
			}
			try {
				super.mouseClicked(e);
			} finally {
				if (editable) {
					editor.setEditable(true);
				}
			}
		}
	}
}

Gruß Tom
 
Hab meinen Fehler gefunden.
In der Tag Komponente (DefaultTextComponent) habe ich getMaximumSize() überschrieben was "new Dimension(0, 0)" zurückgibt. Richtig ist "return super.getSize()"
 
Zurück