Component Titled Border

来源:互联网 发布:php socket 长连接 编辑:程序博客网 时间:2024/04/30 20:47

Component Titled Border

The Swing Components that support renderers are:
         JTable, JList and JTree

How do CellRenderers work ?

All of know, that component we see in JTable is not the actual renderer component but is just it's rubber stamp. Let us see how JTable creates these rubber stamps.

 System.out.println(new JTable().getComponent(0));

When the above snippet is executed, you will see the following output:

 javax.swing.CellRendererPane[,0,0,0x0,invalid,hidden]

CellRendererPane is a class which is used by JTable to create rubber stamps. A CellRendererPane is added to JTable in BasicTableUI.installUI(). When renderer has to be rubber-stamped, it is added to CellRendererPane and its paint(...) method is called with JTable's Graphics Object.

Why do we need CellRendererPane? Can't we say cellRenderer.getTableCellRendererComponent(...).paint(tableGraphics) ?

In Swing, A Component is painted if and only if it is added to a showing component. So cellRenderer must be added to some currently visible component in order to paint its rubber-stamp. CellRendererPane fills this need. CellRenderer doesn't do any thing in its paint(...) and update(..) methods.

SwingUtilities class has two utility methods to create rubber stamps of component:

public static void paintComponent(Graphics g, Component c, Container p, Rectangle r)public static void paintComponent(Graphics g, Component c, Container p, int x, int y, int w, int h)

The above two methods internally use CellRendererPane;

Let us create some small application using rubber stamps:

   

We are going to create a ComponentTitledBorder:
    a border which can allow to place any component in it. (NOTE: border is not a visual component so we can't add a component)

    24   public class ComponentTitledBorder implements Border, 
MouseListener, SwingConstants{ 
25       int offset = 5; 
    27       Component comp; 
28       JComponent container; 
29       Rectangle rect; 
30       Border border; 31    32       public ComponentTitledBorder(Component comp, JComponent container,
 Border border){ 33           this.comp = comp; 
34           this.container = container; 
35           this.border = border; 
36           container.addMouseListener(this); 
37       } 
38    39       public boolean isBorderOpaque(){ 
40           return true; 
41       } 
42    43       public void paintBorder(Component c, Graphics g, int x, 
int y, int width, int height){ 44           Insets borderInsets = border.getBorderInsets(c); 
45           Insets insets = getBorderInsets(c); 
46           int temp = (insets.top-borderInsets.top)/2; 
47           border.paintBorder(c, g, x, y+temp, width, height-temp); 
48           Dimension size = comp.getPreferredSize(); 
49           rect = new Rectangle(offset, 0, size.width, size.height); 
50           SwingUtilities.paintComponent(g, comp, (Container)c, rect); 
51       } 
52    53       public Insets getBorderInsets(Component c){ 
54           Dimension size = comp.getPreferredSize(); 
55           Insets insets = border.getBorderInsets(c); 
56           insets.top = Math.max(insets.top, size.height); 
57           return insets; 
58       } 
59    60       private void dispatchEvent(MouseEvent me){ 
61           if(rect!=null && rect.contains(me.getX(), me.getY())){ 
62               Point pt = me.getPoint(); 
63               pt.translate(-offset, 0); 
64               comp.setBounds(rect); 
65               comp.dispatchEvent(new MouseEvent(comp, me.getID() 
66                       , me.getWhen(), me.getModifiers() 
67                       , pt.x, pt.y, me.getClickCount() 
68                       , me.isPopupTrigger(), me.getButton())); 
69               if(!comp.isValid()) 
70                   container.repaint(); 
71           } 
72       } 
73    74       public void mouseClicked(MouseEvent me){ 
75           dispatchEvent(me); 
76       } 
77    78       public void mouseEntered(MouseEvent me){ 
79           dispatchEvent(me); 
80       } 
81    82       public void mouseExited(MouseEvent me){ 
83           dispatchEvent(me); 
84       } 
85    86       public void mousePressed(MouseEvent me){ 
87           dispatchEvent(me); 
88       } 
89    90       public void mouseReleased(MouseEvent me){ 
91           dispatchEvent(me); 92       } 93   }

32-35
     First argument is the component to be displayed in the border. Second argument is the container for which this border is set and the third argument is the border which has to be titled with component. If you see javax.swing.border.TitledBorder, it allows to place title on any given border. Similarly we make the component to be placed on any given border.

36-37
      we add a mouse listener to the component (I will refer to this as checkbox to make explanation more understandable). We want the checkbox selectable/unselectable by user. That is checkbox should respond to mouse events. But remember that Border is not a component, so we can't add the checkbox to border.

44-47
      we paint the specified border with proper insets.

48-50
      the rubber stamp of the checkbox is painted at some offset say 5 pixels. We have used SwingUtilities.paintComponent(...) here which in turn used CellRendererPane.

53-58
     we compute the union of the specified border insets and the checkbox height and return as insets of this border

74-92
     in the constructor we added this border as mouselistener to checkbox. now we re-dispatch any mouse events in the checkbox's rubberstamp to the actual checkbox so that, user feels that checkbox actually responds to the mouse clicks.

61
    we dispatch only mouse events within the rubber stamp.

62-63
    the mouse coordinates must be translated to the actual checkbox which is painted at some offset (5 pixels here).

64
    we must set the size of checkbox to the size of current rubberstamp, because after the rubberstamp has been painted, CellRendererPane sets the size of checkbox to [0,0]

65-68
    we create the new mouse event with translated coordinates and dispatch it to checkbox.

69-70
    we must repaint the border, if there is any change in checkbox status such as selected/unselected.

The ComponentTitledBorder is not just for JCheckBox. You can use any component. For example we might want to show a title with icon which is not possible with Swing's TitleBorder.

NOTE:
       The checkbox doesn't respond to keyboard. So you can't bring focus to checkbox by TAB or select/unselect using SPACE. After all, it is a rubber stamp :)

Here is the source of demo application:

    public static void main(String[] args){ 
        try{ 
            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); 
        } catch(Exception e){             e.printStackTrace(); 
        } 
        final JPanel proxyPanel = new JPanel(); 
        proxyPanel.add(new JLabel("Proxy Host: ")); 
        proxyPanel.add(new JTextField("proxy.xyz.com")); 
        proxyPanel.add(new JLabel("  Proxy Port")); 
        proxyPanel.add(new JTextField("8080")); 
        final JCheckBox checkBox = new JCheckBox("Use Proxy", true); 
        checkBox.setFocusPainted(false); 
        ComponentTitledBorder componentBorder = 
                new ComponentTitledBorder(checkBox, proxyPanel 
                , BorderFactory.createEtchedBorder()); 
        checkBox.addActionListener(new ActionListener(){ 
            public void actionPerformed(ActionEvent e){ 
                boolean enable = checkBox.isSelected(); 
                Component comp[] = proxyPanel.getComponents(); 
                for(int i = 0; i<comp.length; i++){ 
                    comp[i].setEnabled(enable); 
                } 
            } 
        }); 
        proxyPanel.setBorder(componentBorder); 
        JFrame frame = new JFrame("ComponentTitledBorder - santhosh@in.fiorano.com");        
Container contents = frame.getContentPane();         contents.setLayout(new FlowLayout()); 
        contents.add(proxyPanel); 
        frame.pack(); 
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
        frame.setVisible(true);     } 
 

Your comments are appreciated.

( Aug 08 2005, 05:23:54 PM EDT ) Permalink Comments [15]

 

Comments:

Again, another brilliant demo.

How about something tough that hasnt been done... A rotated jlabel that doesnt use an image. JCommon has a method (drawRotatedString) which you could use in a custom UI. Now theres something noone has ever done before!

Posted by Ben (222.153.46.5) on August 09, 2005 at 03:48 AM EDT #

> A rotated jlabel that doesnt use an image

checkout:
http://www.jroller.com/page/santhosh?entry=adobe_like_tabbedpane_in_swing

Posted by Santhosh Kumar T on August 09, 2005 at 03:59 AM EDT #

That entry while useful doesnt render the text using a custom UI, but renders onto a icon. It isnt easy to change. Imagine being able to have a rotated linklabel (respond to hover etc). This would be a really useful UI (VerticalLabelUI) or something.

Posted by ben (222.153.23.191) on August 09, 2005 at 09:01 PM EDT #

Simple. create VerticalLabelUI and paint the icons.
Icons are more reusable than UI classes.

why do you want to create new UI class. You can simply do this:

new JLabel(new VerticalTextIcon("sampleLabel", 90));

when you are writing a custom ui, you have to write UI class for each LNF:
WindowsVerticalLabelUI
MetalVerticalLabelUI
MotifVerticalLabelUI

otherwize it will not match with the theme of other components.

Posted by Santhosh Kumar T on August 09, 2005 at 09:19 PM EDT #

why do you bother to obfuscate your code at all? Just curious...since you post your code for free.

i like the border idea though!

Posted by codecraig on August 10, 2005 at 12:19 PM EDT
Website: http://www.codecraig.com #

...even copying/pasting the code is quite easy if you know how to use find/replace and RegEX :)

Not trying to deter, just curious I guess.

Posted by 204.156.6.2 on August 10, 2005 at 12:27 PM EDT #

> why do you bother to obfuscate your code at all

this is to avoid readers decompile the jar and use the code.

I want the readers to spend time in understanding the post and writing code manually. This gives a scope to learning.

Posted by Santhosh Kumar T on August 10, 2005 at 12:32 PM EDT #

Found a problem. Doesn't work with JSlider. When you click on the slider the value increases, but you can not slide down (or decrease the value).

JSlider slider = new JSlider(0, 10, 5);...as an example.

Also, JComboBox does not work, I get an exception:
java.awt.IllegalComponentStateException: component must be showing on the screen to determine its location

JMenu does not work either. I think having a menu could be interesting! Perhaps having menus for individual sections of one main panel!

Finally, JSpinner does not work either.

I realize some of these suggestions, such as JMenu, JSpinner, etc may not seem realistic to use...but I think there are possibilities. Even if you don't think using a JSpinner for border component is viable, then the code shouldn't support any Component :)

Do you think you will work on any of these?

Posted by codecraig on August 10, 2005 at 12:38 PM EDT
Website: http://www.codecraig.com #

Yes, it doesn't work will all components.

in order to make it work for any component you need to implement celleditor approach, which we followed in "Making JList Editor" post.

and it wont make sense to place any arbiraty component in border as per gui design guidelines.

The border allows to place any component. because it works for JLabel, all AbstractButton subclasses etc. and we don't want to restrict the component because user might want to place a customcomponent which could have worked perfectly.

Posted by Santhosh Kumar T on August 10, 2005 at 01:05 PM EDT #

Found a little problem.

If the container the ComponentTitledBorder is applied to has a LayoutManager that requires components in the container to have some kind of constraints (TableLayout is one of them), it won't work.

Simple workaround is to apply the ComponentTitledBorder to a container with no LayoutManager at all that will have a single component: the container with the TableLayout.

Brilliant and very useful work any way, as usual.

Posted by sle on September 03, 2005 at 12:55 PM EDT #

Hello !

I use your JListMutable component (think you for that), with a personnal IPField component that works like Window IPFiled. To do that I have extended Document model (insert, replace etc...) & I listen about caret update to adjust the caret position on authorized location.

It work on a frame as test, but not on the JListMutable. I see that it is because I don't receive caret update event. When I create the IPTextField, on getDocument() a instanciate the IPDocument and do it listening on caret.

Would you have any Idea ?

Thinks a lot.

Posted by alexis on November 02, 2005 at 04:18 AM EST #

it is supposed to works.

are u able to see cursor movement in IPField;
if not, the problem could be the following:
the changes in cell-editor are being commited after any change to IPField document.

to confirm this, you can use the DefaultListCellEditor with CaretListener and see whether you get caret update events or not.

Posted by Santhosh Kumar T (61.247.233.82) on November 02, 2005 at 04:25 AM EST #

I think there is a bug with the mouseEntered / mouseExited Events

With the given implementation mouseExited and mouseEntered Events won't be forwarded correctly to the Component.

For Exemple if the cursor exit the container to a point that doesn't belong to the component the test :
if(rect!=null && rect.contains(me.getX(), me.getY())){

will prevent the event to be forwarded.

I think this should also implement MouseMotionListener#MouseMoved method and check if the cursor isIn the Rect of the Component and forward the Events as needed.

I tried to also find a way to forward the FocusEvents but that does not seems good for now.

here is the implementation I tried:



public class ComponentTitledBorder implements
Border,
MouseListener,
MouseMotionListener,
SwingConstants,
FocusListener {
/**
* <code>true</code> if the Cursor is in the Rect of the component
*/
private boolean mIsIn = false;
/**
* set to <code>true</code> when the cursor enter the Rect of the Component
* set to <code>false</code> when mIsIn switch to <code>false</code>
*/
private boolean mCursorHasJustEntered = false;
/**
* set to <code>true</code> when the cursor leaves the Rect of the Component
* set to <code>false</code> when mIsIn switch to <code>true</code>
*/
private boolean mCursorHasJustExited = true;

private int offset = 5;

private Component mComp;
private JComponent mContainer;
private Rectangle mRect;

private Border mBorder;
/**
* Default constructor
* @param iComp Component to Insert in the Border
* @param iContainer Container to be surround by the Border
* @param iBorder Border used
*/
public ComponentTitledBorder(Component iComp, JComponent iContainer, Border iBorder) {
this.mComp = iComp;
this.mContainer = iContainer;
this.mBorder = iBorder;
mContainer.setFocusable(true);
iContainer.addMouseListener(this);
iContainer.addMouseMotionListener(this);
iContainer.addFocusListener(this);
}

public boolean isBorderOpaque() {
return true;
}

public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
Insets borderInsets = mBorder.getBorderInsets(c);
Insets insets = getBorderInsets(c);
int temp = (insets.top-borderInsets.top)/2;
mBorder.paintBorder(c, g, x, y+temp, width, height-temp);
Dimension size = mComp.getPreferredSize();
mRect = new Rectangle(offset, 0, size.width, size.height);
SwingUtilities.paintComponent(g, mComp, (Container)c, mRect);
}
/**
* @see javax.swing.border.Border#getBorderInsets(java.awt.Component)
*/
public Insets getBorderInsets(Component c) {
Dimension size = mComp.getPreferredSize();
Insets insets = mBorder.getBorderInsets(c);
insets.top = Math.max(insets.top, size.height);
return insets;
}
/**
* Dispatch Mouse Event to Component in the Border
* @param iMe the MouseEvent
*/
private void dispatchEvent(MouseEvent iMe) {
Point lPoint = iMe.getPoint();
if (mRect != null && mRect.contains(lPoint)) {
lPoint.translate(-offset, 0);
mComp.setBounds(mRect);
mComp.dispatchEvent(new MouseEvent(mComp, iMe.getID()
, iMe.getWhen(), iMe.getModifiers()
, lPoint.x, lPoint.y, iMe.getClickCount()
, iMe.isPopupTrigger(), iMe.getButton()));
if (!mComp.isValid()) {
mContainer.repaint(mRect);
}
}
}
/**
* @see java.awt.event.MouseListener
* #mouseClicked(java.awt.event.MouseEvent)
*/
public void mouseClicked(MouseEvent iMe) {
dispatchEvent(iMe);
}
/**
* @see java.awt.event.MouseListener
* #mouseEntered(java.awt.event.MouseEvent)
*/
public void mouseEntered(MouseEvent iMe) {
//dispatchEvent(iMe);
}
/**
* @see java.awt.event.MouseListener
* #mouseExited(java.awt.event.MouseEvent)
*/
public void mouseExited(MouseEvent iMe) {
if (mIsIn) {
mIsIn = false;
mCursorHasJustEntered = false;
mCursorHasJustExited = true;
Point lPoint = iMe.getPoint();
lPoint.translate(-offset, 0);
mComp.dispatchEvent(new MouseEvent(mComp,
MouseEvent.MOUSE_EXITED,
iMe.getWhen(), iMe.getModifiers(),
lPoint.x, lPoint.y, iMe.getClickCount(),
iMe.isPopupTrigger(), iMe.getButton()));
if (!mComp.isValid()) {
mContainer.repaint(mRect);
}
}
}
/**
* @see java.awt.event.MouseListener#mousePressed(java.awt.event.MouseEvent)
*/
public void mousePressed(MouseEvent iMe) {
dispatchEvent(iMe);
}
/**
*
* @see java.awt.event.MouseListener
* #mouseReleased(java.awt.event.MouseEvent)
*/
public void mouseReleased(MouseEvent iMe) {
dispatchEvent(iMe);
}

/**
* @see java.awt.event.FocusListener#focusGained(java.awt.event.FocusEvent)
*/
public void focusGained(FocusEvent iE) {
forwardFocusEvent(iE);
}

/**
* @see java.awt.event.FocusListener#focusLost(java.awt.event.FocusEvent)
*/
public void focusLost(FocusEvent iE) {
forwardFocusEvent(iE);
}

private void forwardFocusEvent(FocusEvent iE) {
mComp.dispatchEvent(
new FocusEvent(
mComp,
iE.getID(),
iE.isTemporary(),
iE.getOppositeComponent()));
if (!mComp.isValid()) {
mContainer.repaint(mRect);
}
}

/**
* @see java.awt.event.MouseMotionListener
* #mouseDragged(java.awt.event.MouseEvent)
*/
public void mouseDragged(MouseEvent iE) {
dispatchEvent(iE);
}

/**
* @see java.awt.event.MouseMotionListener
* #mouseMoved(java.awt.event.MouseEvent)
*/
public void mouseMoved(MouseEvent iE) {
Point lPoint = iE.getPoint();
mIsIn = mRect.contains(lPoint);
lPoint.translate(-offset, 0);
if (mIsIn) {
mCursorHasJustExited = false;
if (!mCursorHasJustEntered) {
mCursorHasJustEntered = true;
mComp.dispatchEvent(
new MouseEvent(
mComp,
MouseEvent.MOUSE_ENTERED,
iE.getWhen(),
iE.getModifiers(),
iE.getX(),
iE.getY(),
iE.getClickCount(),
iE.isPopupTrigger()));
if (!mComp.isValid()) {
mContainer.repaint(mRect);
}
}
} else {
mCursorHasJustEntered = false;
if (!mCursorHasJustExited) {
mCursorHasJustExited = true;
mComp.dispatchEvent(
new MouseEvent(
mComp,
MouseEvent.MOUSE_EXITED,
iE.getWhen(),
iE.getModifiers(),
iE.getX(),
iE.getY(),
iE.getClickCount(),
iE.isPopupTrigger()));
if (!mComp.isValid()) {
mContainer.repaint(mRect);
}
}
}
dispatchEvent(iE);
}
}  
原创粉丝点击