Java Tips Weblog

  • Blog Stats

    • 2,571,932 hits
  • Categories

  • Archives

Component Border

Posted by Rob Camick on September 27, 2009

There are many times when you want a JTextField and a JButton to work together. That is you provide the user with a text field for entering data and you provide a button to display a popup component to make it easier to enter the data. An example might be a date field. Some users might choose to type the date directly into the text field, while others might choose the point and click approach. There are a couple of common appoaches for achieving this and one not so common approach that I will suggest.

The most common approach would probably be to simply create a panel and add the two components to the panel. The main draw back might be that the two components look like, well, two separate components. However, its easy to implement as the code below suggests:

JPanel panel = new JPanel();
panel.add( textField );
panel.add( button );

When you want the two components to look like a single component you can get a little fancier by having the panel use the Border and background Color of the text field. It only takes a few more lines of code:

JPanel panel = new JPanel();
//panel.setLayout( new FlowLayout(FlowLayout.CENTER, 0, 0) );
panel.add(textField);
panel.add(button);
panel.setBackground( textField.getBackground() );
panel.setBorder( textField.getBorder() );
textField.setBorder(null);

In both of the above solutions the two components are only related because they are contained in the same parent panel. When you write code for the ActionListener of the button to update the text field you must first find the text field on the panel. The obvious assumption would be that the text field is component 0, but this may not always be true.

The approach I will suggest here is to actually add the button to the text field so the button is contained within the text field and not a panel. How is this possible? Well, remember that all Swing components extend from Container. All you need to do is set the bounds of the button, before adding it to the text field. Only one problem, the button will paint over top of the text in the text field. So we somehow need to increase the size of the text field and reserve that space for the button to be painted. One way to do this is to add a Border to the text field.

The ComponentBorder is such a class. It provides two main features to allow the addition of a button to a text field:

  • first, it calculates the size of the border insets needed to contain the button within the Border.
  • secondly, it will set the location of the button within the Border insets area each time the Border is painted.
Component-Border

The ComponentBorder is easy to use as you will generally use the default settings:

ComponentBorder cb = new ComponentBorder( button );
cb.install( textField );
panel.add( textField );

Although there are a few properties to customize the behaviour:

  • edge – controls which edge of the text field the button will be painted on. The Edge enumerated values are TOP, LEFT, BOTTOM, RIGHT (default)
  • alignment – controls the alignment of the button within the specified edge of the text field. This value must be between 0.0 and 1.0 with the common values of LEADING, CENTER (default) and TRAILING.
  • gap – specifies extra space to be added to the border insets in addiditon to the size of the button
  • adjust insets – there may be times when the size of the button is greater than the size of the text field. In this case the size difference is added to the insets of the ComponentBorder so that the button can be fully painted with the bounds of the text field.

You can experiment with the Webstart demo to see how changing these properties affects the ComponentBorder.

Still not sure why you might use the ComponentBorder over the other two approaches? Well, here are a couple more points to consider:

  • the ActionListener code for the button is easier to write. There is no need to search a panel for the related text field because the text field will always be the parent of the button. So once you get the source object from the ActionEvent you can just invoke the getParent() method.
  • the text field can be used as an editor in a JTable. All you do is create a DefaultCellEditor using the text field as the parameter. You would also want to make sure the adjust insets is set to false, otherwise you may see truncated text.

A couple of considerations to be aware of when using the ComponentBorder class.

  • unlike most Border implementations, this class cannot be shared between multiple components. The button is added to the text field and Swing components can only have a single parent.
  • all the properties must be set before invoking the install() method. I know this is not a proper design and I may fix it in the future, but for now I just thought I’d throw the concept out there for you to play with.

Although, I have only tested this class using a text field and button, there should be no reason you can’t use this class on other components as well. Give it a try and see what happens. I’m sure you will let me know if it doesn’t work ;)

Get The Code

ComponentBorder.java
ComponentBorderDemo.java

Related Reading

Java Tutorials: How to Use Borders

18 Responses to “Component Border”

  1. Tbee said

    Not a bad idea. I would take this one step further though; a JComboBox is not implemented as a TextField-with-a-button-next-to-it, it simply is a completely new component. So I’d go implementing a JTextFieldWithButton component so that the whole plumbing is hidden. Your trick can perfectly be used to extend JTextField, than use your code to get that button in, and add some methods to have the component’s user handle the actual click.

    • Rob Camick said

      The motivation for this suggestion was for a JTextField, JButton combination, but the solution was designed to be general in nature. You might want to use a JTextArea and a JButton. The solution allows you to be creative without creating a custom component for every scenario.

  2. Eitan said

    I like that.

    It would be great to have the source of the demo program as “How to” sample .
    Thinking of it (here I become greedy…:-)) always let us have the demo programs.

    • Rob Camick said

      The test program is over 200 lines of code. Most of the code deals with building the GUI used in the Webstart demo and does not show the direct usage of the class. You would spend more time understanding how the GUI works to dynamically create the class then you would spend understanding how to use the class.

      All you need is a GUI with a text field and a button and the two lines of code I showed above.

  3. Ricardo said

    There is one project called xswingx, based on swingx that among other things, does this. The project makes trivial inserting several jbutton with just icons in the text components, and it is not difficult to put an slightly modified combobox on it

  4. Shane said

    One issue we discovered with this solution was if the border is changed, the child component is not removed, this makes for some interesting results.

    We applied a property change listener to the parent component and listenered for border changes and went about reinstall the component border using the new border.

    Also, it might be a good idea to check to see if the component border has already been applied, rather then running the risk of having the border applied multiple times.

    Just a suggestion.

  5. Matthias said

    Great work! But under what license is this code published? I don’t see any license so I can only assume that copying and using it is not allowed at all. Which contradicts how the code is presented. I’m confused. Could you clarify?

  6. Marcos said

    The ComponentBorder is nice, but if you look well, it leaves a little space (I think 1 pixel) above the button top border and bellow the textfield border (the panel, to be more specific). The bottom of the button is well aligned. Is there a simple change in the code of the ComponentBorder to correct this little alignment problem? It would be very nice.

    Thank you in advance.

    Marcos

    • Rob Camick said

      That is the because of the default Border used by the JTextField. Test this out by using textField.setBackground(Color.RED) on a normal text field that doesn’t use the CompoundBorder class and you will see the same gap. If you don’t like this you can always replace the default border of the text field with a LineBorder.

      • Marcos said

        I’ve made some tests. The approach suggested by you solves the space problem, but it raises another problem: the text field now looks very unprofessional with the line border and it is also different from other text fields on the form. I tried using a component border like this, thinking that it would get rid of just the space, but it also makes the border top line dissapear:

        textField.setBorder(BorderFactory.createCompoundBorder(
        BorderFactory.createEmptyBorder(-1, 0, 0, 0),
        textField.getBorder()
        ));

        I think a good solution would be to copy the text field border (or to create a custom border) and get rid of the space only.

        I also made this little change to the ComponentBorder:

        // …
        else if (edge == Edge.RIGHT)
        {
        borderInsets.right = component.getPreferredSize().width + gap;
        // This change:
        borderInsets.top = -1;
        component.setAlignmentX(1.0f);
        component.setAlignmentY(alignment);
        }
        // …

        and it seems to work, but I think this is not the best solution.

        I think the best solution would be to change the text field border to remove the space, but for the time being I don’t know how to achive this.

        Any ideas?

        Thank you.

        Marcos

      • Marcos said

        I was inverting the outer and inner borders. Changing the code to this seems to work:

        textField.setBorder(BorderFactory.createCompoundBorder(
        textField.getBorder(),
        BorderFactory.createEmptyBorder(-1, 0, 0, 0)
        ));

        Marcos

  7. Najib said

    Hi,
    First, thanks for this. its really great. I am having one problem with it though. I have a focus listener on the text field in order to change the border and background colour when the textfield has focus. The problem is that once the text field gains focus the button disappears. How can I fix this? Thanks.

    • Rob Camick said

      This solution uses a CompoundBorder to display the Border of the text field and the ComponentBorder. If you replace the Border on the text field then you lose this functionality. So instead of replacing the entire Border, you would need to change the “outer” Border of the CompoundBorder when the text field gains focus. Same when you lose focus, you would change the “outer” Border of the CompoundBorder.

  8. Reblogged this on Suchitra Phadke.

  9. lution said

    Firstly,It’s a great border. And, I want to ask can this achieve custom “gaps” ,I mean the component can be anywhere of a edge. Beacuse I want to achieve mouseDragg Listener, and I want to let the component move around the border. So How can I achieve this? Thanks.

    • lution said

      I see I can adjust gaps’ value at any float number,So my first question is finished. And I have another question that How to add multiple component to this border?

      • Rob Camick said

        This Border is not designed to allow you to drag components around the outside. The gaps will just add extra spacing between the original component and the border. If you want multiple components on the Border you could try using a CompoundBorder to nest multiple ComponentBorders. I have never tried this so I don’t know how well it would work.

Leave a comment