I have a popup menu which contains a JMenuItem with an icon (I) and a JList. See the diagram below.
------------------------
| I | Clear |
|----------------------|
| | List |^|
| | Item A | |
| | Item B | |
| | Item C | |
| | |v|
------------------------
When I initially created the popup, the list was aligned to the left hand side and did not take into account the icon offset.
I was able to find a way to move the list using the sun.swing.SwingUtilities2.BASICMENUITEMUI_MAX_TEXT_OFFSET client property.
// Move the items out of the gutter (Run inside of the show(Component, int, int) method)
final Integer gutter = (Integer) getClientProperty(SwingUtilities2.BASICMENUITEMUI_MAX_TEXT_OFFSET);
panelList.removeAll();
panelList.add(Box.createHorizontalStrut(gutter));
panelList.add(scrollList);
However, I am unable to use that constant in production code (due to it being in the sun package).
How can I retrieve the max text/icon offset without relying upon Java's internals?
One common solution for this is to add an empty Icon for each menu item that is the same size as the Icon for the menu.
You might be able to use the concepts from the code below which uses:
SwingUtilities.layoutCompoundLabel(...);
Basically it get the layout information of the label to determine where the text and icon are actually painted relative to the entire label. If you know that information you should then have your offset.
This example paints grid lines only over the Icon in the label:
import java.awt.*;
import javax.swing.*;
import javax.swing.border.*;
public class LabelLayout extends JLabel
{
#Override
protected void paintComponent(Graphics g)
{
super.paintComponent(g);
Graphics grid = g.create();
grid.setColor( Color.ORANGE );
Rectangle viewR = new Rectangle();
viewR.width = getSize().width;
viewR.height = getSize().height;
Rectangle iconR = new Rectangle();
Rectangle textR = new Rectangle();
String clippedText = SwingUtilities.layoutCompoundLabel
(
this,
grid.getFontMetrics(),
getText(),
getIcon(),
getVerticalAlignment(),
getHorizontalAlignment(),
getVerticalTextPosition(),
getHorizontalTextPosition(),
viewR,
iconR,
textR,
getIconTextGap()
);
int gridSize = 10;
int start = iconR.x;
int end = iconR.x + iconR.width;
System.out.println( iconR );
for (int i = start; i < end; i += gridSize)
{
grid.drawLine(i, iconR.y, i, iconR.y + iconR.height);
}
grid.dispose();
// g.setColor( getForeground() );
// g.drawString(display, textR.x, textR.y + fm.getHeight());
// System.out.println(iconR + " : " + textR);
}
private static void createAndShowGUI()
{
LabelLayout label = new LabelLayout();
label.setBorder( new LineBorder(Color.RED) );
label.setText( "Some Text" );
label.setIcon( new ImageIcon( "DukeWaveRed.gif" ) );
label.setVerticalAlignment( JLabel.CENTER );
label.setHorizontalAlignment( JLabel.CENTER );
// label.setVerticalTextPosition( JLabel.BOTTOM );
label.setVerticalTextPosition( JLabel.TOP );
label.setHorizontalTextPosition( JLabel.CENTER );
JFrame frame = new JFrame("SSCCE");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add( label );
frame.setLocationByPlatform( true );
frame.pack();
frame.setSize(300, 200);
frame.setVisible( true );
}
public static void main(String[] args)
{
EventQueue.invokeLater(new Runnable()
{
public void run()
{
createAndShowGUI();
}
});
}
}
I think a JMenu would use the same layout.
Related
I have a problem: I want to create a small game, I have to make a window like the following:
When I tried to change the font size of "Fun With Words", it wasn't changed ...
What should I do?
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.JComponent;
public class GameWords extends JFrame
{
private static int W = 800 ;
private static int H = 600 ;
public GameWords ()
{
setTitle ( " Word Order Game " ) ;
setSize ( H , W ) ;
setLayout ( new FlowLayout() ) ;
setDefaultCloseOperation ( EXIT_ON_CLOSE ) ;
createContent () ;
setVisible ( true ) ;
}
public void createContent ()
{
JLabel heading = new JLabel (" Fun With Words ") ;
heading.setFont ( heading.getFont().deriveFont ( 26f ) );
heading.setPreferredSize ( new Dimension ( H , 4 * W ) ) ;
JLabel h1 = new JLabel ( " Hey Kids! Want to prictice your typing and word-ordering Skills ? \n" ) ;
add ( heading ) ;
add ( h1 ) ;
}
public static void main(String[] args)
{
new GameWords () ;
}
}
The short answer is don't, the API is quite capable of calculating the desired size it self.
The longer answers is, don't use setSize, use pack instead, which uses the containers preferred size to calculate the size of the window
public GameWords ()
{
setTitle ( " Word Order Game " ) ;
setLayout ( new FlowLayout() ) ;
setDefaultCloseOperation ( EXIT_ON_CLOSE ) ;
createContent () ;
pack();
setVisible ( true ) ;
}
public void createContent ()
{
JLabel heading = new JLabel (" Fun With Words ") ;
heading.setFont ( heading.getFont().deriveFont ( 26f ) );
JLabel h1 = new JLabel ( " Hey Kids! Want to prictice your typing and word-ordering Skills ? \n" ) ;
add ( heading ) ;
add ( h1 ) ;
}
As a general recommendation, you shouldn't extend directly from a JFrame, you're not adding any new functionality to the class and you're locking yourself into a single use case. As a general recommendation, you should start by extending from JPanel and then add this to whatever container you want to use
I'm writing a calculator program for my java class. I want to account for errors like
User decides to divide by 0. I want the calculator to be able to acknowledge this error and display ERROR.
The user enters 6+7+ and then hits the equal button.
I want, once again, for the calculator to acknowledge that there is an error. I was hoping I could get some hints as to how to go about this. Thanks!
import java.awt.*;
import javax.swing.*;
import java.awt.event.*;
public class Calculator
{
JFrame window;
// stuff for top panel
JPanel topPanel;
JTextField expr,result;
JButton equals;
// stuff for bottom panel
JPanel bottomPanel,digitsPanel,opsPanel;
JButton[] digits,ops;
JButton clear, clearEntry;
Container content;
Listener listener;
String[] oplabels = { "+", "-", "/", "*" };
public Calculator()
{
listener = new Listener(); // our Listener class implements ActionListener
window= new JFrame("GUI Calc");
content=window.getContentPane();
content.setLayout( new GridLayout(2,1) );
topPanel=new JPanel();
topPanel.setLayout( new GridLayout(1,3) );
// TOP PANEL WORK
expr = new JTextField( );
equals = new JButton("=");
equals.addActionListener( listener );
result = new JTextField( );
topPanel.add( expr );
topPanel.add( equals );
topPanel.add( result );
// BOTTOM PANEL WORK
bottomPanel = new JPanel();
bottomPanel.setLayout( new GridLayout(1,2) );
digitsPanel = new JPanel();
digitsPanel.setLayout( new GridLayout(4,3) );
opsPanel = new JPanel();
opsPanel.setLayout( new GridLayout(4,1) );
digits = new JButton[12];
ops = new JButton[4];
for (int i=0 ; i<10 ; i++)
{
digits[i] = new JButton( i+"" );
digits[i].addActionListener(listener);
digitsPanel.add( digits[i] );
}
clear = new JButton( "C" );
clearEntry = new JButton( "CE" );
clear.addActionListener(listener);
clearEntry.addActionListener(listener);
digitsPanel.add( clear );
digitsPanel.add( clearEntry);
for (int i=0 ; i<4 ; i++)
{
ops[i] = new JButton( oplabels[i] ) ;
ops[i].addActionListener(listener);
opsPanel.add( ops[i] );
}
bottomPanel.add( digitsPanel );
bottomPanel.add( opsPanel );
content.add( topPanel);
content.add( bottomPanel);
window.setVisible(true);
}
class Listener implements ActionListener
{
public void actionPerformed(ActionEvent e)
{
Component clicked = (Component) e.getSource();
if ( clicked == equals )
{
result.setText( evaluate( expr.getText() ) );
return;
}
for ( int i=0 ; i<10 ; i++)
{
if ( clicked == digits[i] )
{
expr.setText( expr.getText() + i );
return;
}
}
if ( clicked == clear )
{
expr.setText("0");
return;
// do something
}
/* if (clicked == clearEntry)
{
expr.setText(expr.getDigits[i]);
}
for (int i=0; i < 10; i++)
{
if (clicked == digits[i])
{
int lastValue = clicked;
if(clicked == clearEntry)
{
expr.setText(expr.getText()-clicked);
return;
}
}
} */
for ( int i=0 ; i<4 ; i++ )
{
if(clicked == digits[i])
{
expr.setText(expr.getText() + oplabels[i]);
return;
}
// tack on that operator to the expr string
}
}
String evaluate( String exp )
{
return "NOT WRITTEN YET";
}
}
public static void main(String [] args)
{
new Calculator();
}
}
I'm still working on it. Especially where I'm trying to figure out the clearEntry button so I have put that section as a comment because its still a work in progress.
You can try this:
add a new class(Calculator) member:
Component last_clicked;
Inside Constructor:
last_clicked=null;
Before every return statement of actionPerformed, you can add this:
last_clicked=clicked;
Now, in actionPerformed method, when checking value of clicked, you can do this:
When clicked is '0' & last_clicked is '/', you can display error 1.
When clicked is 'equals' & last_clicked is an operator, you can display error 2.
I'm writing a Java code to display the grades that students in a class have achieved. I have a small problem. I want to use TWO sliders, one to adjust the text (the output) font size, and another slider to adjust the spacing between the letters. For example, when I enter the number '5' into the the JLabel box labelled #of Grade As, and I press the button the button "Display output as a string of letters", I get the result : "Grade As : AAAAA." What I would like to do (as mentioned above) is to adjust the size of the letter A, and also adjust the spacing between the consecutive letter As. Can someone please tell me how to do this. I know of course that I have to set up a JSlider, but what exactly do I do then? My partial code is the following :
if ( e.getSource() == displayChartButton ) {
checkAndRecordData();
Graphics g = chartPanel.getGraphics();
g.setColor(Color.white);
g.fillRect(20,20,410,52);
g.setColor(Color.black);
g.drawRect(20,20,410,52);
g.setColor( Color.black );
g.drawString( "Grade As: " + gradeAs, chartLeftXA, chartTopYA );
}
if (e.getSource() == displayLongString) {
Graphics g = chartPanel.getGraphics();
g.setColor(Color.white);
g.fillRect(20,20,410,52);
g.setColor(Color.black);
g.drawRect(20,20,410,52);
g.setColor( Color.black );
g.drawString( "Grade As: " + longStringA, 100, 50 );
}
private void checkAndRecordData() {
gradeAs = tempAs;
longStringA = "";
String gradeLetterA = "A";
for (int i=0; i<tempAs; i++)
longStringA += gradeLetterA;
}
public void stateChanged(ChangeEvent event) {
int size = slider.getValue();
}
Once I get to the last line, int size = slider.getValue(), I don't know what to do. Remember, I have TWO things to do. One is to adjust the size of the letters, the other is to adjust the spacing between the letters, hence I need two sliders. Can someone please help?
You can do it easily via recursion for child components
public static void changeFont(Component component, int fontSize) {
Font f = component.getFont();
component.setFont(new Font(f.getName(), f.getStyle(), f.getSize() + fontSize));
if (component instanceof Container) {
for (Component child : ((Container) component).getComponents()) {
changeFont(child, fontSize);
}
}
}
you can even set you own font or font size for a child component
by getting the name of the component
public static void changeFont(Component component, int fontSize) {
Font f = component.getFont();
component.setFont(new Font(f.getName(), f.getStyle(), f.getSize() + fontSize));
if (component instanceof Container) {
for (Component child : ((Container) component).getComponents()) {
if(child.getName().equals("myComponentName")){
//set font to the component
}else{
changeFont(child, fontSize);
}
}
}
}
and call this method from an event , like here i am posting how it will change the font size with the change in the value of the slider
where sldValue is my global variable to store the old slider value
if (!((JSlider) evt.getSource()).getValueIsAdjusting()) {
changeFont(this, sldFontSet.getValue() - sldValue);
sldValue = sldFontSet.getValue();
}
Sorry about Spacing I am working on it
I want a quick-to-use way for entering times down to centiseconds. Entering digits should push them in from the right and automatically add leading zeros, "." and ":" as needed, so for example:
"" => "0.03" => "0.31" => "3.14" => "31.41" => "3:14.15"
Also, entering "f" should result in "DNF" ("did not finish"):
"3:14.15" => "DNF" => "0.04"
My code below "works", except it always puts the cursor at the end (because I full-replace), which is not nice when someone tries to edit in the middle. Is there an easy way to keep the cursor where it should be? And more importantly, is my whole approach proper at all or I should really do this differently? I've tried many ways before (JFormattedTextField and Formatters, DocumentFilter, DocumentListener, etc), some because they were suggested here for similar things, but couldn't get them to work.
import java.awt.*;
import javax.swing.*;
import javax.swing.text.*;
class TimeField extends JTextField {
public static void main ( String[] args ) {
JFrame frame = new JFrame();
frame.add( new TimeField() );
frame.add( new TimeField(), BorderLayout.SOUTH );
frame.pack();
frame.setDefaultCloseOperation( frame.EXIT_ON_CLOSE );
frame.setVisible( true );
}
TimeField () {
super( 10 );
setHorizontalAlignment( RIGHT );
setFont( new Font( "MONOSPACED", Font.BOLD, 32 ));
((AbstractDocument) getDocument()).setDocumentFilter( new DocumentFilter() {
public void insertString ( DocumentFilter.FilterBypass fb, int offset, String string, AttributeSet attr ) throws BadLocationException {
replace( fb, offset, 0, string, attr );
}
public void remove ( DocumentFilter.FilterBypass fb, int offset, int length ) throws BadLocationException {
replace( fb, offset, length, "", null );
}
public void replace ( DocumentFilter.FilterBypass fb, int offset, int length, String text, AttributeSet attrs ) throws BadLocationException {
fb.replace( offset,length, text, attrs );
if ( text.contains("f") ) {
fb.replace( 0, getText().length(), "DNF", null );
return;
}
text = getText(); // get current text
text = text.replaceAll( "\\D", "" ); // remove non-digits
text = text.replaceAll( "^0+", "" ); // remove leading zeros
while ( text.length() < 3 ) text = "0" + text; // make at least three digits
text = text.replaceAll( "(.)(..)$", "$1.$2" ); // add point if necessary
text = text.replaceAll( "(.)(..\\.)", "$1:$2" ); // add colon if necessary
fb.replace( 0, getText().length(), text, null ); // replace original with formatted
}
} );
}
}
Edit:
I tried JFormattedTextField before, here's my best attempt. I can enter digits and when the focus leaves the field, valueToString gets called and adds point+colon which is then displayed. But I'd like this to be done during editing, not after. I must be missing something, as MaskFormatter can apparently do that.
import java.awt.*;
import javax.swing.*;
import javax.swing.text.*;
class TimeField extends JFormattedTextField {
public static void main(String[] args) {
//Create Event Disptach Thread for swing UI and its components to run on
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame();
JFormattedTextField ftf = new JFormattedTextField(new TimeFormatter());
ftf.setHorizontalAlignment(JTextField.RIGHT);
ftf.setFont(new Font(Font.MONOSPACED, Font.BOLD, 32));
ftf.setColumns(10);
frame.getContentPane().add(ftf);
frame.getContentPane().add(new JButton("foo"), BorderLayout.SOUTH);
frame.pack();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
});
}
static class TimeFormatter extends DefaultFormatter {
public Object stringToValue(String text) {
System.out.println("stringToValue(" + text + ")");
return text == null ? "" : text.replaceAll("\\D", "");
}
public String valueToString(Object value) {
System.out.println("valueToString(" + value + ")");
return value == null ? null : format((String)value);
}
private String format(String text) {
text = text.replaceAll("\\D", ""); // remove non-digits
text = text.replaceAll("^0+", ""); // remove leading zeros
while (text.length() < 3) text = "0" + text; // make at least three digits
text = text.replaceAll("(.)(..)$", "$1.$2"); // add point
text = text.replaceAll("(.)(..\\.)", "$1:$2"); // add colon if necessary
return text;
}
}
}
I'd like to create a Google Chrome favorites-style popover with arrow, rounded corners and if I have time a shadow effect. In Java Swing. What is the best approach? SplashScreen? Or just a plain AWT Window? Other ideas? Thanks!
There are a few options and each of them has its own pros and cons...
Create a custom-shaped window - with this approach some systems will be able to create additional shade behind the shaped window, also this works on most of systems (should work even on linux JDK's). The bad thing about this approach (that actually makes it unusable) is the unaliased shape border line - if you create some ellipse-shaped window its sides will appear rough.
Create a non-opaque undecorated window with drawn shape - this approach will fix the main problem of the (1) approach. You can alias shape you re drawing on fully transparent window. The bad thing about this one is that it works only on Win and Mac systems. On (mostly) any linux system you will get a rectangle resulting window and tons of errors about unsupported operations.
Create a custom-shaped popup inside the java-window and place it on the window layered or glass panes. This will allow you to fully avoid any compatibility problems and get the benefits of the (2) approach. There is a bad thing about this approach though - you can only display such popup in window root pane bounds. This is still much better than two other ways in most of cases, since it uses less resources, does not create additional windows and you can control every part of the popup.
About the 3rd approach - you can check TooltipManager i have created in my own project WebLookAndFeel - it uses window glass pane to display custom-shaped semi-transparent tooltips with a shadow-effect. Also soon enough i will add window PopupManager that will allow quick creation of "inner" window popups.
Here are some examples of the approaches:
A bit of code that used ahead in all of the examples
Method to create shape:
private static Area createShape ()
{
Area shape = new Area ( new RoundRectangle2D.Double ( 0, 20, 500, 200, 20, 20 ) );
GeneralPath gp = new GeneralPath ( GeneralPath.WIND_EVEN_ODD );
gp.moveTo ( 230, 20 );
gp.lineTo ( 250, 0 );
gp.lineTo ( 270, 20 );
gp.closePath ();
shape.add ( new Area ( gp ) );
return shape;
}
Mouse adapter that allows to move window by dragging the component:
public static class WindowMoveAdapter extends MouseAdapter
{
private boolean dragging = false;
private int prevX = -1;
private int prevY = -1;
public WindowMoveAdapter ()
{
super ();
}
public void mousePressed ( MouseEvent e )
{
if ( SwingUtilities.isLeftMouseButton ( e ) )
{
dragging = true;
}
prevX = e.getXOnScreen ();
prevY = e.getYOnScreen ();
}
public void mouseDragged ( MouseEvent e )
{
if ( prevX != -1 && prevY != -1 && dragging )
{
Window w = SwingUtilities.getWindowAncestor ( e.getComponent () );
if ( w != null && w.isShowing () )
{
Rectangle rect = w.getBounds ();
w.setBounds ( rect.x + ( e.getXOnScreen () - prevX ),
rect.y + ( e.getYOnScreen () - prevY ), rect.width, rect.height );
}
}
prevX = e.getXOnScreen ();
prevY = e.getYOnScreen ();
}
public void mouseReleased ( MouseEvent e )
{
dragging = false;
}
}
1st approach example:
public static void main ( String[] args )
{
JFrame frame = new JFrame ();
frame.setUndecorated ( true );
JPanel panel = new JPanel ();
panel.setBackground ( Color.BLACK );
WindowMoveAdapter wma = new WindowMoveAdapter ();
panel.addMouseListener ( wma );
panel.addMouseMotionListener ( wma );
frame.getContentPane ().add ( panel );
Area shape = createShape ();
AWTUtilities.setWindowShape ( frame, shape );
frame.setSize ( shape.getBounds ().getSize () );
frame.setLocationRelativeTo ( null );
frame.setDefaultCloseOperation ( JFrame.EXIT_ON_CLOSE );
frame.setVisible ( true );
}
As you can see - corners of the rounded shape are pretty rough and not good-looking
2nd approach:
public static void main ( String[] args )
{
JFrame frame = new JFrame ();
frame.setUndecorated ( true );
final Area shape = createShape ();
JPanel panel = new JPanel ()
{
protected void paintComponent ( Graphics g )
{
super.paintComponent ( g );
Graphics2D g2d = ( Graphics2D ) g;
g2d.setRenderingHint ( RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON );
g2d.setPaint ( Color.BLACK );
g2d.fill ( shape );
}
};
panel.setOpaque ( false );
WindowMoveAdapter wma = new WindowMoveAdapter ();
panel.addMouseListener ( wma );
panel.addMouseMotionListener ( wma );
frame.getContentPane ().add ( panel );
AWTUtilities.setWindowOpaque ( frame, false );
frame.setSize ( shape.getBounds ().getSize () );
frame.setLocationRelativeTo ( null );
frame.setDefaultCloseOperation ( JFrame.EXIT_ON_CLOSE );
frame.setVisible ( true );
}
Now it should look perfect - the only problem this will properly work only on Windows and Mac (atleast in 1.6.x JDK). Atleast it was so about a month ago when i last time checked it on various OS.
3rd approach
public static void main ( String[] args )
{
JFrame frame = new JFrame ();
JPanel panel = new JPanel ( new BorderLayout () );
panel.setOpaque ( false );
WindowMoveAdapter wma = new WindowMoveAdapter ();
panel.addMouseListener ( wma );
panel.addMouseMotionListener ( wma );
frame.getContentPane ().add ( panel );
panel.add ( new JButton ( "Test" ) );
final Area shape = createShape ();
JPanel glassPane = new JPanel ( null )
{
public boolean contains ( int x, int y )
{
// This is to avoid cursor and mouse-events troubles
return shape.contains ( x, y );
}
};
glassPane.setOpaque ( false );
frame.setGlassPane ( glassPane );
glassPane.setVisible ( true );
JComponent popup = new JComponent ()
{
protected void paintComponent ( Graphics g )
{
super.paintComponent ( g );
Graphics2D g2d = ( Graphics2D ) g;
g2d.setRenderingHint ( RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON );
g2d.setPaint ( Color.BLACK );
g2d.fill ( shape );
}
};
popup.addMouseListener ( new MouseAdapter ()
{
// To block events on the popup
});
glassPane.add ( popup );
popup.setBounds ( shape.getBounds () );
popup.setVisible ( true );
frame.setSize ( 800, 500 );
frame.setLocationRelativeTo ( null );
frame.setDefaultCloseOperation ( JFrame.EXIT_ON_CLOSE );
frame.setVisible ( true );
}
This is a simple example of the popup placed on glass-pane. As you can see it exists only inside of the JFrame, but has the aliased side and works properly on any type of OS.