Custom swing component - Turning a node into text - java

I'm writing a custom swing component (something completely new, but think JTree or JList). I'm trying to follow the general design of JTree, JTable, JList etc for consistency (I've also seen various poor 3rd party components abandon the separable model and/or renderer approach).
So, I have a model full of nodes, the component itself and a renderer. At some point the node has to be turned into text and displayed by a renderer. I'm not clear on the best way to do this:
Pass the node itself (as Object) to the renderer, and let the renderer decide how to display it.
This is how JList does it.
Requires a customised renderer just to change the text.
Allows great flexibility in how to display the node (doesn't even have to be text).
Pass the node itself (as Object) to the renderer, but have a convertValueToText() method in the component class.
This is how JTree does it.
Renderers can be just as flexibile as before - don't have to use this method.
Have to override component to change the text transformation.
As above, but delegate convertValueTotext() to the model.
This is how JXTable does it.
The model is probably the best place for this method - and it's easier to override there.
I don't want to have to customise the renderer just to change the text, but I'd like to be able to customise the renderer to do more than display a model-displayed string (else why bother with renderers). I really don't like the fact that JXTable uses reflection to look for convertValueToText() in the model - this smells of bad magic to me.
Can anyone shed any light on this oft-neglected part of Swing?
SOLUTION
What I ended up doing was this:
Add a method to the model which returns a string for the given node. Importantly, this can be null to indicate that the renderer should know what to do or that we simply can't provide anything useful.
The component has the same method, and passes the call on to the model. This is important for view-model separation. The renderer calls this method, so it doesn't talk to the model directly.
The default renderer calls the above method and if it's not null, it uses it, otherwise it can call toString on the value, or provide a default, or whatever.
This leaves developers a choice when they want to override the displayed value
- Override the method with a non-null return value knowing that the default renderer will display this text.
- Provide a custom renderer which is passed the actual node object so it can do "clever" things if it needs to.
I'm quite happy with it - it "feels" right, it works, and it's easy to use.
Thanks for your perspectives!

Good question. This is not specific to Swing, but a philosophical question about the difference between a model and a view.
In general, is converting objects into text the job of the model or the view? My purist head says that actually you want a hierarchy of views - one to convert object model to text, and one to display the text. You might even want more than two - for instance, object-to-text, text-to-document-structure, document-structure-to-HTML, and then CSS to present to the user.
However, pragmatism says this may get too hard to remember and maintain. So in your circumstance I would suggest: think about how likely it is you'll ever want to extract non-text data from the model. If it is not very likely, then put the equivalent of convertValueToText in the model.
Otherwise, allow the component to either use a renderer, if it is given one, or else take the object value and convert it to text internally.
This allows maximum flexibility and probably makes things feel most natural to the users of the API. I believe this is the JTable model though I haven't used Swing for a long time.

AFAIK neither JList nor JTree require the renderer to render text. The renderer gets passed the data object and return a JComponent which gets positioned as a child in Tree/List itself and then rendered.
I would go with this. A renderer for text would simply return a JLabel. If you want to be able to change the way, the text is constructed pass a Formatter to the TextRender, and you are done.
Stephan

If you had to write your own component do it as simple as possible. In a lot of cases if you need a custom renderer then you don't care about interpretation by component or model. Model holds your data. And in this case is also custom written. From my point of view the good choice is based on first option. Provide DefaultRenderer which implements AbstractRenderer and add there all methods like toText(Object o) and so on. Then allow me to decide whether I want to use default functionality or I prefer to wrote my own. Do you really need custom component? To make it works correctly it is a LOT of work. Is this component worth all this?

Related

Using just one Custom Renderer for several ComboBoxes

I am currently working through the "Providing a Custom Renderer" example on this page. And now I wanted to create more than just one of these boxes. I did this by creating six renderer classes, one for each box.
And now for my question. Is it possible to just have one renderer class for all six boxes? For that purpose I tried to parse two variables to the constructor of the CustomBoxRenderer, like this.
public ComboBoxRenderer(ImageIcon[] currentImage, String[] currentString)
But due to how the programm seems to work, the currentImage array is null until a certain point, so I get a exception.
But let's assume this would work how I expected it to work, I still would have to create six seperate instances of the renderer for each box, which I'd like to avoid aswell.
I hope this is enough information, I could also provide my full code, but I think that'd be too much for this page here, if not, let me know.
If i'm reading correctly you could create a class that extends combobox and just adjust it so that it automatically uses your custom renderer, then all you have to is create a normal instance of your custom combobox and use it as normal except it will use your renderer without any hassle.
e.g. in your constructor you would just have this line
this.setRenderer(new ComboBoxRenderer(currentImage, currentString));
Im unsure why you think you would need to create six instances as the renderer deals with each box.
Hope this helps.

JTree : modify apperance regardless of L&F

I've got a JTree which I'm trying to modify it so that the actual selection area for any selected node will extend from the very left of the JTree to the very right of the JTree.
Most examples on the web talks about extending BasicTreeUI. From there you just modify the methods that determine selection area, and that's it. However, this will require that the JTree - regardless of which platform it runs on - will use that implementation. I won't really be able to take advantage of the various UI implementations that target specific platforms as I always will be using BasicTreeUI (and not the Metal, Windows or Aquia implementations).
What I ideally would like to be able to do is to take whatever implementation of BasicTreeUI that is currently installed on the JTree and wrap it in a custom wrapper of mine which extends BasicTreeUI. From there I would override all methods and delegate to the wrapped UI, and do my own implementation of a few methods where required to recalculate the node selection area. However, most methods on BasicTreeUI is protected,
so I can really do this.
Not sure how to get around this...any ideas would be welcome!
Perhaps you could override setUI and wrap the provided UI in your custom tree UI, like so:
#Override
public void setUI(TreeUI ui) {
super.setUI(new MyCustomTreeUI(ui));
}
Another option to consider is looking into providing/setting tree UI properties in the global UIManager. I believe that the platform-specific tree UIs will ask the global UIManager for property values and configure themselves accordingly - perhaps you could simply do something like UIManager.put("Tree.selectionWidth", 100).

The Page Turn Effect in Java Swing/AWT

I would like to implement the page turn effect in Swing/AWT. The theory is pretty much explained here. My question is that since the page is to contain text, which is the correct swing/awt component to use? Also, to get a little deeper, how should I approach this problem? I mean I have the theory laid out before me, I know the tool I need to use, but I have never done anything like this with Swing/AWT, do I need to master all the minutiae of Swing/AWT to be able to do this?
Whatever you do, you will have to implement your own drawing routines. As the text you intend to draw is going to not share most characteristics of the text drawn with other components (it will be angled, rotating over time, and clipped at a moving visible "edge" of the page), you will also have to draw the text.
In SWING, and in all non-console programs the text is drawn to the screen as would any graphics primitive. It takes a bit of learning; especially in the details of line spacing, letter spacing, word wrapping, etc.
That means I'd consider a new swing UI component. Here's how to write custom painting for one. Depending on the details, you may want to reuse a model. Generally it is easier to only write a view than a view and a model at the same time; however, you must balance the needs of the view with the suitability of the existing model.
If you choose an existing model, the Document interface is what you should code your view against. You can take your lead from the JTextPane or JTextArea view components, depeding on if you want the ability to "add style" to your text.
Don't forget that the view actually doesn't do the painting in a well structured Swing component. If it did, then it would not be able to be skinned (to follow the platform's theme). This means that while you will provide a Swing view (typically with a name pattern like JPagedBook), that view will have to be coupled to a UIDelegate which does the actual drawing of the view. This allows multiple UIDelegates for each view, with each UIDelegate containing the instructions of how to render (draw) the view in a manner that is consistent with the look-and-feel of the platform.
The biggest issues around this is typically the extra work involved to properly support look-and-feel, and the lack of knowledge of proper UIDelegate registration (so when the view draws, it finds something that will draw it). It's worth the extra few hours (or couple of days if GUI component programming is sort of new to you) to make it work like the rest of Swing. In the end you'll have a component that doesn't feel like a "separate" part of the library.

JTree nodes' rendering and font changes

I have a problem with rendering nodes in JTree. When node's font is changed and node's text gets wider that way then node's text is cut and end of text replaced with dots.
How to tell the JTree then that it should widen area to render tho whole node.
Thank you for help
You can use custom renderer and set to the component (JLabel) something like this
final Dimension size = label.getPreferredSize();
label.setMinimumSize(size);
label.setPreferredSize(size);
or just set text like this
setText("<html>" +valueText+"</html>")
Sounds like the trigger of the font change happens under the feet of the tree: internally, the ui delegate does lots of size caching which must be updated on any change which effects the cached sizes. That's done automatically on changes to the treeModel, to relevant expansion state and some visual changes to the tree itself.
So the basic question is: what triggers the change of the font? If it's some change of the model/nodes, the model implementation is incorrect in not firing an appropriate TreeModelEvent, the obvious solution would be to fix that :-) If it's something outside the model, the solution depends on the details of your context, nothing generally applicable.
Size caching
The JTree uses a renderer to render nodes. The renderer is the same renderer for all OSs, so the differnt lookings are inside ComponentUIs. JTree uses by default a JLabel to paint nodes, so its the JLabel's size wo guides us to cut the text using ....
Lets make a short excourse: Swing has differen LookAndFeels for different Operation Systems, they are detached from the components in UI-Classes like the BasicLabelUI (and this is the source of your problem). BasicLabelUI caches the size of the label to prevent recalculation if no changes has made. So BasicLabelUI did not clear the cache of theese old size values. BasicLabelUI do clear the cache if he gets informed about any changes.
The question is, why does the BasicLabelUI did not get informed about changes? Well, if you modify/expand/rename the Tree programatically you must tell the ComponentUI to drop that cache!
You lucky, you do not need to write much code because a genius already wrote something for you, the creators of the TreeUI-class Rob Davis and Scott Violet wrote startEditingAtPath and stopEditing.
Example
TreeUI ui = tree.getUI();
for (TreePath treePath : selectionPaths) {
ui.startEditingAtPath(tree, treePath);
}
tree.setSelectionPaths(selectionPaths);
tree.expandPath(expandPaths.getSelectionPath());
ui.stopEditing(layer);
Call your TreeModel's reload()

Colouring JTree's TreeNodes

I have a JTree which I give objects that implement the TreeNode interface, and a custom TreeModel to display them (not using DefaultMutableTreeNode). I would like to change the text colour of some nodes. I can't find anything in the docs, except javax.swing.tree.DefaultTreeCellRenderer.setTextNonSelectionColor(Color newColor), but it will change everything indiscriminately, and I only need it for some nodes (specifically, broken links, i.e. nodes whose corresponding files can't be found on the disk, should be greyed out, the rest should be default). Can it be done, and how?
You are close to your answer. What you need to do is Sub Class the DefaultTreeCellRenderer and override a few of the DefaultTreeCellRenderer's methods. Then make sure you tell the tree to use your custom cell renderer.
What you will need to do is have some state variables that indicate whether or not a link is broken, and set the color of the node based on that.
You might also look at org.netbeans.swing.outline, mentioned in this answer. Ordinary extensions of TableCellRenderer and the RenderDataProvider interface make it especially easy to customize the appearance of rows in the tree.

Categories

Resources