I've got a JTree with icons on some of the nodes within the tree. They appear and work fine but when I select a node with a icon, the renderer does not render the entire node selected but appears to have an offset applied to it, as if it thinks the icon is still to the left of the node as below:
The code for the renderer (which extends DefaultTreeCellRenderer) is below:
public ProfileTreeRenderer() {
super.setLeafIcon(null);
super.setClosedIcon(null);
super.setOpenIcon(null);
}
#Override
public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean expanded, boolean leaf, int row, boolean hasFocus) {
Component c = super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus);
if (c instanceof JLabel) {
JLabel label = (JLabel) c;
label.setHorizontalTextPosition(SwingConstants.LEADING);
}
if(sel && !hasFocus) {
setBackgroundSelectionColor(UIManager.getColor("Panel.background"));
setTextSelectionColor(UIManager.getColor("Panel.foreground"));
} else {
setTextSelectionColor(UIManager.getColor("Tree.selectionForeground"));
setBackgroundSelectionColor(UIManager.getColor("Tree.selectionBackground"));
}
if (value instanceof ProfileNode) {
ProfileNode node = (ProfileNode) value;
if (node.isUsed() && !sel) {
c.setForeground(Color.GRAY);
}
if (node.getIcon() != null) {
setIcon(node.getIcon());
}
}
}
I cannot see why the renderer would apply this offset, so can anyone offer a way to get the node fully selected with the icon? The SSCCE code for the tree itself is below.
public class Example extends JDialog {
public Example() {
JTree tree = new JTree(createModel());
tree.setCellRenderer(new ProfileTreeRenderer());
setLayout(new BorderLayout());
add(tree, BorderLayout.CENTER);
}
private TreeModel createModel() {
ProfileNode root = new ProfileNode("Profiles");
ProfileNode userA = new ProfileNode("Example User A");
ProfileNode userB = new ProfileNode("Example User B");
// You'll need to subsitute your own 16x16 icons here
userA.setIcon(ImageSet.USER_ICON);
userB.setIcon(ImageSet.USER_ICON);
root.add(userA);
root.add(userB);
return new DefaultTreeModel(root);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
new Example().setVisible(true);
}
});
}
}
The ProfileNode class:
public class ProfileNode extends DefaultMutableTreeNode {
#Getter private String labelDisplay;
#Getter #Setter private ImageIcon icon;
#Getter #Setter private boolean isUsed = false;
public ProfileNode(String labelDisplay) {
this.labelDisplay = labelDisplay;
}
#Override
public String toString() {
return labelDisplay;
}
}
Thanks in advance.
The problem is that the DefaultTreeCellRenderer uses its icon property exclusively for the open/leaf/close icons: it assumes that - if the icon != null - it's at the start of the component (even if it isn't) and adjusts the selection accordingly. You need to re-adjust ... or use SwingX renderers :-)
Something like:
JXTree tree = new JXTree();
tree.expandAll();
IconValue iv = new IconValue() {
Icon icon = XTestUtils.loadDefaultIcon("green-orb.png");
#Override
public Icon getIcon(Object value) {
return value.toString().contains("s") ? icon : null;
}
};
StringValue converter = new MappedValue(StringValues.TO_STRING, iv);
WrappingProvider provider = new WrappingProvider(IconValues.NONE, converter);
// hacking around missing api ...
LabelProvider wrappee = (LabelProvider) provider.getWrappee();
wrappee.getRendererComponent(null).setHorizontalTextPosition(JLabel.LEADING);
TreeCellRenderer r = new DefaultTreeRenderer(provider);
tree.setCellRenderer(r);
Related
I'm having a lot of trouble dealing with TreeView, more specificcaly with the text shown per node.
Starting with an initializer for the Tree, where I expected a single node with the text, it might make the program more intuitive for the users:
private void defineEmpityTree(){
cbt.setModel(new DefaultTreeModel(
new DefaultMutableTreeNode("Relat\u00F3rios Individuais") {
{
}
}
));
}
and I add initialize the Tree:
cbt = new JCheckBoxTree();
defineEmpityTree();
scrollPane.setViewportView(cbt);
"Relatórios Individuais" aren't shown, Tree Ok, another bug
I ignored this problem and continued with the actual filling of the nodes, the user specifies his search and press "ok", thats when we get the Tree filled, 2nd image.
But then then another strange problem comes, if "ok" is pressed again, some texts go strange, 3rd image.
Heres the part of the cade where the nodes are created. For an array of files nodes are created for each type of report and each report lot, its not really important.
private void defineNewTree(ArrayList<File> files){
DefaultMutableTreeNode dmtRoot = new DefaultMutableTreeNode("Relat\u00F3rios Individuais");
DefaultMutableTreeNode dmtSubFolder = null;
DefaultMutableTreeNode dmtLotFolder = null;
int childsRoot = 0;
int childsSub = 0;
for(File f : files){
String subFolder = f.getName().substring(17,f.getName().length()-4);
String name = f.getName();
String lot = f.getName().substring(0, 3);
childsRoot = dmtRoot.getChildCount();
boolean subFoldExists = false;
boolean foldLotExists = false;
//creatingo folder reports:
for(int i = 0;i<childsRoot;i++){
if(dmtRoot.getChildAt(i).toString().equals(subFolder)){
dmtSubFolder = (DefaultMutableTreeNode) dmtRoot.getChildAt(i);
subFoldExists = true;
i=childsRoot;
}
}
if(!subFoldExists){
dmtSubFolder = new DefaultMutableTreeNode(subFolder);
dmtRoot.add(dmtSubFolder);
}
for(int j = 0;j<childsSub;j++){
if(dmtSubFolder.getChildAt(j).toString().equals(lot)){
dmtLotFolder = (DefaultMutableTreeNode) dmtSubFolder.getChildAt(j);
foldLotExists = true;
j=childsSub;
}
}
if(!foldLotExists){
dmtLotFolder = new DefaultMutableTreeNode("lote "+lot);
dmtSubFolder.add(dmtLotFolder);
}
dmtLotFolder.add(new DefaultMutableTreeNode(name));
}
DefaultTreeModel myTree = new DefaultTreeModel(dmtRoot);
cbt.setModel(myTree);
}
I think the real problem is that:
cbt.setModel(myTree);
Is that the correct way to define the Tree contents?
Edit.:
Button OK:
...
JButton btnOk = new JButton("OK");
btnOk.setBounds(161, 37, 49, 23);
btnOk.setActionCommand("ok");
btnOk.addActionListener(buttonListener);
panel.add(btnOk);
...
class ButtonListener implements ActionListener{
#Override
public void actionPerformed(ActionEvent a) {
switch(a.getActionCommand()){
case "ok":
search();
self.requestFocusInWindow();
break;
case "cancel":
dispose();
break;
case "print":
TreePath[] tp = cbt.getCheckedPaths();
for(TreePath t : tp){
System.out.println(t.getLastPathComponent().toString());
}
self.requestFocusInWindow();
break;
default:
break;
}
}
}
private void search(){
FileSeeker fs = new FileSeeker(textField.getText());
ArrayList<File> files = fs.getFiles();
defineNewTree(files);
}
Edit.:
CheckBoxCellRenderer:
private class CheckBoxCellRenderer extends JPanel implements TreeCellRenderer {
private static final long serialVersionUID = -7341833835878991719L;
JCheckBox checkBox;
public CheckBoxCellRenderer() {
super();
this.setLayout(new BorderLayout());
checkBox = new JCheckBox();
add(checkBox, BorderLayout.CENTER);
setOpaque(false);
}
#Override
public Component getTreeCellRendererComponent(JTree tree, Object value,
boolean selected, boolean expanded, boolean leaf, int row,
boolean hasFocus) {
DefaultMutableTreeNode node = (DefaultMutableTreeNode)value;
Object obj = node.getUserObject();
TreePath tp = new TreePath(node.getPath());
CheckedNode cn = nodesCheckingState.get(tp);
if (cn == null) {
return this;
}
checkBox.setSelected(cn.isSelected);
checkBox.setText(obj.toString());
checkBox.setOpaque(cn.isSelected && cn.hasChildren && ! cn.allChildrenSelected);
return this;
}
}
So, for the first problem the solution is:
private void defineEmpityTree(){
DefaultMutableTreeNode dmtn = new DefaultMutableTreeNode("Relat\u00F3rios Individuais");
cbt.setModel(new DefaultTreeModel(dmtn));
((DefaultTreeModel) cbt.getModel()).nodeChanged(dmtn);
}
I simply had to notify that the node had changed.
For the truncation problem,
cbt.setLargeModel(true);
did the trick.
To put this short:
What is this about
I have a JTable with Model which displays data fetched from an SAP system.
My Goal is in a specific column to display only a part of the data which is in the model. For example the row of the model has Object["a","b"] but the user is only supposed to see a.
So I read a lot of threads here on StackOverflow and a lot of tutorials on how to use custom tablecellrenderers and editors etc. but I am not able to fix my problem, which is that the cell where i registered the renderer will not be highlighted when selected.
A possible solution is described HERE but this does not work for me.
Here is my custom Renderer:
public class SapChangeNumberCellRenderer extends DefaultTableCellRenderer{
private static final long serialVersionUID = -2649719064483586819L;
private SapChangeNumberTable table;
private int valuesSize;
public final static String ASM_AMOUNT = LanguageServer.getString("71", "Baugruppen");
public SapChangeNumberCellRenderer(SapChangeNumberTable table) {
super();
this.table = table;
}
#Override
public Component getTableCellRendererComponent(final JTable table, final Object value, final boolean isSelected,
final boolean hasFocus,
final int row, final int column) {
// components & Layout
JPanel panel = new JPanel(new BorderLayout());
JButton buttonDots = new JButton("...");
JLabel text = new JLabel();
List<String> values = (List<String>) value;
valuesSize = values.size();
if (valuesSize > 0) {
// set values
buttonDots.setPreferredSize(new Dimension(14, 14));
text.setText(values.get(0));
} else {
text.setText("");
}
if (valuesSize > 1) {
// button to open dialog only if we have more than 1 item
panel.add(buttonDots, BorderLayout.EAST);
}
panel.add(text, BorderLayout.WEST);
panel.setOpaque(true);
return panel;
}
#Override
public String getToolTipText(MouseEvent e) {
String toolTip = String.valueOf(valuesSize) + Initializer.SPACE + ASM_AMOUNT;
return toolTip;
}
public SapChangeNumberTable getTable() {
return table;
}
}
So as you can see depending on the list size of the values I manipulate the component which will be given back from the method. The setOpaque(true) method does somehow not achieve my goal.
Here is the according JTabel (note: BaseTable is just a wrapper for JTable with some goodies I need...nothing fancy here)
public class SapChangeNumberTable extends BaseTable {
/** the model */
private SapChangeNumberTableModel model = new SapChangeNumberTableModel();
/** parent frame */
private SapPanel parent = null;
public SapChangeNumberTable(SapPanel parent) {
this.parent = parent;
this.init();
}
/**
* init the table
*/
private void init() {
// set model (not filled yet)
this.setModel(model);
// set renderer
setRendererAndEditor();
// add search filter row, enabling sorting is included
this.addFilterSearchRowPanel();
// single selection
this.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
// hide
this.hideColumns();
this.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
}
/**
* sets the default table cell renderer
*/
private void setRendererAndEditor() {
getColumnModel().getColumn(convertColumnIndexToView(SapChangeNumberTableModel.COL_ID_SPM_ASM_NUMBER))
.setCellRenderer(new SapChangeNumberCellRenderer(this));
getColumnModel().getColumn(convertColumnIndexToView(SapChangeNumberTableModel.COL_ID_SPM_ASM_NUMBER))
.setCellEditor(new SapChangeNumberAsmRefTableCellEditor(this));
}
#Override
public void setStatusBarDataCount(boolean value) {
}
#Override
public void hideColumns() {
}
#Override
public int getColModelSortIndex() {
return 0;
}
#Override
public void load() {
}
#Override
public SapChangeNumberTableModel getModel() {
return model;
}
public boolean isChanging() {
return model.isFilling();
}
public SapFactoryChange getRow(int row) {
return model.getSapFactoryChange(row);
}
#Override
public void clear() {
model.clear();
}
#Override
public Component prepareRenderer(TableCellRenderer renderer, int rowIndex, int vColIndex) {
Component comp = super.prepareRenderer(renderer, rowIndex, vColIndex);
if (vColIndex == SapChangeNumberTableModel.COL_ID_SPM_ASM_NUMBER) {
//what the hack to do here to manipulate the comp ? I can't add a JPanel to a plain Component
}
return comp;
}
}
In the table I tried some stuff with prepareRenderer but here I can't manipulate the data (values) and all other stuff I am doing in the custom renderer. Maybe I have a basic understanding problem of how to approach this. I am thankful for any hints !
I just found a very simple solution which I thought would overwrite my wanted behavior, but it doesn't.
Just implemented this into the Table:
#Override
public Component prepareRenderer(TableCellRenderer renderer, int rowIndex, int vColIndex) {
Component comp = super.prepareRenderer(renderer, rowIndex, vColIndex);
if (isRowSelected(rowIndex)) {
comp.setBackground(Color.ORANGE);
}
return comp;
}
works like a charme!
I am stuck with implementing ListCellRenderer. This is my code.
I am getting the data from a DB in the form of Domain class that looks like this:
public class Domain {
private Integer id;
private String naziv;
private Integer status;
public Domain(){}
public Integer getId() {return id;}
public void setId(int i){id = i;}
public String getNaziv(){return naziv;}
public void setNaziv(String n){naziv = n;}
public Integer getStatus(){return status;}
public void setStatus(int s){status = s;}
}
Set up of JList:
DefaultListSelectionModel m = new DefaultListSelectionModel();
m.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
m.setLeadAnchorNotificationEnabled(false);
DefaultListModel<String> modelRN = new DefaultListModel<String>();
JScrollPane scrollPane = new JScrollPane();
scrollPane.setBounds(10, 86, 390, 199);
contentPane.add(scrollPane);
JList<String> listRN = new JList<String>(modelRN);
scrollPane.setViewportView(listRN);
listRN.setBorder(new TitledBorder(null, "", TitledBorder.LEADING, TitledBorder.TOP, null, null));
listRN.setSelectionModel(m);
and this is how I populate the list:
dRN = new DBdomain(s,a,b).Conn();
for(int i=0;i<dRN.size();i++){
modelRN.addElement(dRN.get(i).getNaziv());
where dRN = ArrayList<Domain>
So the problem is this. I am populating the list with strings that are Domain.getNaziv() but i want to change the background in the list where Domain.getStatus() has certain value. I know I need to implement something like this:
public class MyListCellRenderer extends DefaultListCellRenderer {
#Override
public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
Component c = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
// do something
return c;
}
}
The problem is that I am not populating Jlist with Domain but with a Domain filed which is a string, so the value argument in getListCellRendererComponent doesnt see the filed status so I don't know how I would mark the fields whose background I want to change.
hope I provided all the info and that somebody can point me in the right direction.
The problem is that I am not populating Jlist with Domain
Well DO populate JList with Domain. Perhaps something like this:
public class MyListCellRenderer extends DefaultListCellRenderer {
#Override
public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
Component cell = null;
if (value instanceof Domain) {
Domain domain = (Domain)value;
int status = domain.getStatus();
String naziv = domain.getNaziv();
cell = super.getListCellRendererComponent(list,
naziv, // note this...
index,
isSelected,
cellHasFocus);
if (status > 0) { // or whatever...
cell.setBackground(STATUS_ON_COLOR);
} else {
cell.setBackground(STATUS_OFF_COLOR);
}
}
}
return cell;
}
I have taken a jtable, and added a model to it, and added text area rendere and editor to it.
Table.setModel(
JUTableBindingFactory.createAttributeListBinding(
Binding, Table name, View name, null, null, attributes));
Table.getColumnModel()
.getColumn(i)
.setCellEditor(
new TextAreaEditor(
true, true, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
JScrollPane.HORIZONTAL_SCROLLBAR_NEVER));
Table.getColumnModel()
.getColumn(i)
.setCellRenderer(
new TextAreaCellRenderer(
true, true, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
JScrollPane.HORIZONTAL_SCROLLBAR_NEVER));
_Table.setRowHeight(100);
Also I have set the table row hieght.
Text area renderer class
public class TextAreaCellRenderer extends JTextArea implements TableCellRenderer
{
private JScrollPane pane;
private boolean _isEditable = false;
private boolean _isEnabled = false;
private int _verticalBarProperyValue = JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED;
private int _horizontalBarProperyValue = JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED;
public TextAreaCellRenderer()
{
super();
init();
}
public TextAreaCellRenderer(boolean isEnabled, boolean isEditable, int
verticalBarProperyValue, int horizontalBarProperyValue)
{
super();
_isEditable = isEditable;
_isEnabled = isEnabled;
_verticalBarProperyValue = verticalBarProperyValue;
_horizontalBarProperyValue = horizontalBarProperyValue;
init();
}
private void init()
{
pane = new JScrollPane(this, _verticalBarProperyValue, _horizontalBarProperyValue);
setLineWrap(true);
setWrapStyleWord(true);
setEditable(_isEditable);
setEnabled(_isEnabled);
}
/**
* Returns the cell renderer component.
*/
public Component getTableCellRendererComponent(JTable table, Object value,
boolean isSelected, boolean hasFocus, int row, int column)
{
setLineWrap(true);
setWrapStyleWord(true);
setText(value != null ? value.toString() : "");
setBackground((isSelected) ? NWSTheme.BLUE_DARK : NWSTheme.WHITE);
setForeground((isSelected) ? NWSTheme.WHITE : NWSTheme.BLACK);
pane.setEnabled(true);
return pane;
}
}
the editor class is also quite similar, but the problem doesn't seem to be relating to this, what else should i try?
Try moving the horizontal scroll more up as sometimes we don't and are given the impression of what you experienced. Try atleast
I want to implement DnD on my system by dragging a node from a JTree and dropping it onto a JLabel.
The JLabel is an Icon with certain properties about a machine, and by dragging the information from the JTree node onto the JLabel I want it to be able to send a message to a client listening on that machine.
Any help is much appreciated!
Example of label method:
private void makeLabel(String html, final String version) {
// Create a button to link to the DR environment
//JButton button = new JButton(html);
JLabel machineLabel = new JLabel();
machineLabel.setTransferHandler(new TransferHandler("text"));
MouseListener listener = new DragMouseAdapter();
machineLabel.addMouseListener(listener);
machineLabel.setIcon(onlineIcon);
machineLabel.setToolTipText("IP: " + html);
//Add the button to the panel and make sure it appears
machineLabel.setSize(25, 10);
vecIcons.addElement(machineLabel);
buttonPanel.add(machineLabel);
buttonPanel.setVisible(true);
buttonPanel.validate();
dynaScrollPane.validate();
buttonPanel.repaint();
dynaScrollPane.repaint();
}
DragMouseAdapter method:
private class DragMouseAdapter extends MouseAdapter {
public void mousePressed(MouseEvent e) {
JComponent c = (JComponent) e.getSource();
TransferHandler handler = c.getTransferHandler();
handler.exportAsDrag(c, e, TransferHandler.LINK);
}
}
Then in order to make my tree draggable I just have:
exampleTree.setDragEnabled(true);
not sure I understand your setup: assuming you want the label to be a drop target, simply implement a custom Transferhandler which accepts the dataflavour as exported by the tree and do something with it
EDIT
To get hold of the TreePath use a custom Transferhandler on the tree as well: override its createTransferable which returns the TreePath:
final DataFlavor flavor =new DataFlavor(TreePath.class, "treePath");
TransferHandler treeHandler = new TransferHandler() {
DataFlavor[] pathFlavour = new DataFlavor[]
{flavor};
/**
* #inherited <p>
*/
#Override
protected Transferable createTransferable(JComponent c) {
JTree tree = (JTree) c;
final TreePath path = tree.getSelectionPath();
Transferable transferable = new Transferable() {
#Override
public DataFlavor[] getTransferDataFlavors() {
return pathFlavour;
}
#Override
public boolean isDataFlavorSupported(DataFlavor flavor) {
return pathFlavour[0].equals(flavor);
}
#Override
public Object getTransferData(DataFlavor flavor)
throws UnsupportedFlavorException, IOException {
return path;
}
};
return transferable;
}
public int getSourceActions(JComponent c) {
return COPY;
}
};
tree.setTransferHandler(treeHandler);
JLabel labelTarget = new JLabel("I'm a drop target!");
TransferHandler labelHandler = new TransferHandler() {
/**
* #inherited <p>
*/
#Override
public boolean importData(JComponent comp, Transferable t) {
try {
LOG.info("import from: " + t.getTransferData(flavor));
} catch (Exception e) {
e.printStackTrace();
}
return true;
}
/**
* #inherited <p>
*/
#Override
public boolean canImport(JComponent comp,
DataFlavor[] transferFlavors) {
return true;
}
};
labelTarget.setTransferHandler(labelHandler);
no need for an additional mouseListener