I am developing one app in which I want to create two buttons. These buttons should be center aligned custom buttons with bitmap fill backgrounds. Each should also contain text that is centered in that button.
The problem is that those two buttons are not set properly. The second button is gone behind the first and its bitmap height is also decreased compared to the first button.
For both custom buttons I have the same CustomButton class.
Here is code:
CustomButtonField aboutM1 = new CustomButtonField(0,"About G1",registerbg,registerbg,Field.FOCUSABLE,0x324F85);
add(new RichTextField(Field.NON_FOCUSABLE));
// CustomButtonField2 ForgotPass = new CustomButtonField2("Forgot Password?",0x324F85);
CustomButtonField ForgotPass = new CustomButtonField(0,"Forgot Password?",registerbg,registerbg,Field.FOCUSABLE,0x324F85);
add(new RichTextField(Field.NON_FOCUSABLE));
VerticalFieldManager bottomVFM = new VerticalFieldManager(USE_ALL_WIDTH);
HorizontalFieldManager bottomHFM = new HorizontalFieldManager(FIELD_HCENTER);
bottomHFM.add(aboutM1);
bottomHFM.add(ForgotPass);
bottomVFM.add(bottomHFM);
add(bottomVFM);
Custom Button:
import net.rim.device.api.system.Bitmap;
import net.rim.device.api.ui.*;
public class CustomButtonField extends Field
{
Bitmap Unfocus_img, Focus_img, current_pic;
int width;
String text;
Font font;
int custColor;
CustomButtonField(int width, String text, Bitmap onFocus, Bitmap onUnfocus, long style,int custColor)
{
super(style);
Unfocus_img = onUnfocus;
Focus_img = onFocus;
current_pic = onFocus;
this.text = text;
this.width = width;
this.custColor = custColor;
}
protected void layout(int width, int height)
{
setExtent(current_pic.getWidth(), current_pic.getHeight());
}
protected void paint(Graphics graphics)
{
try
{
FontFamily fntFamily = FontFamily.forName("BBAlpha Sans");
font = fntFamily.getFont(Font.BOLD,20);
}
catch(Exception e)
{
font = Font.getDefault();
}
graphics.setFont(font);
graphics.setColor(custColor);
int xText = (getWidth() - font.getAdvance(text)) / 2;
int yText = (getHeight() - font.getHeight()) / 2;
graphics.drawBitmap(0, 0, current_pic.getWidth(), current_pic.getHeight(), current_pic , 0 , 0);
graphics.drawText(text, xText, yText);
/* graphics.drawBitmap(0, 0, current_pic.getWidth(), current_pic.getHeight(), current_pic , 0 , 0);
graphics.drawText(text, width , 7);*/
graphics.setDrawingStyle(Graphics.HCENTER | Graphics.VCENTER, true);
}
protected void onFocus(int direction)
{
super.onFocus(direction);
current_pic = Unfocus_img;
this.invalidate();
}
protected void drawFocus(Graphics graphics, boolean on)
{
}
protected void onUnfocus()
{
super.onUnfocus();
current_pic = Focus_img;
invalidate();
}
public boolean isFocusable() {
return true;
}
protected boolean navigationClick(int status, int time) {
fieldChangeNotify(0);
return true;
}
}
The code you used is correct , the only drawback is that you are centering your text according to your bitmap width & text length is more than bitmap width .
You may need to change your approach .
Have a look on Blackberry UI samples in this below URL & check for EmbossedButtonField Demo
https://github.com/blackberry/Samples-for-Java
Its a good approach to create a custom button , once we are not sure by the button label length.
Related
I'm using double-buffered graphics in my JLayer subclass to implement a simple swipe animation in a Java Swing application. It works fine on the older displays, but when I run it on a Retina display, the screen loses the doubled-resolution when the animation starts, and gets it back when it ends. I'm not sure how to maintain the higher resolution during the animation.
My animate method originally looked like this:
private void animate() {
Timer timer = new Timer(frameMillis, null);
final ActionListener actionListener = (evt) -> { /* omitted for brevity */ };
timer.addActionListener(actionListener);
int imageType = BufferedImage.TYPE_INT_ARGB;
upcomingScreen = new BufferedImage(liveComponent.getWidth(), liveComponent.getHeight(), imageType);
Graphics2D graphics2D = (Graphics2D) upcomingScreen.getGraphics();
liveComponent.paint(graphics2D); // liveComponent is a JComponent
graphics2D.dispose();
timer.start();
}
I tried doubling the image size, but that didn't help.
upcomingScreen = new BufferedImage(liveComponent.getWidth()*2, liveComponent.getHeight()*2, imageType);
To reflect these changes, I changed my drawing code in LayerUI by doubling xLimit, width, height:
public void paint(final Graphics g, final JComponent c) {
if (isAnimating) {
int xLimit = (c.getWidth()*2 * frame) / maxFrames;
int width = c.getWidth()*2;
int height = c.getHeight()*2;
g.drawImage(uScreen, 0, 0, xLimit, height, 0, 0, xLimit, height, c);
g.drawImage(pScreen, xLimit, 0, width, height, xLimit, 0, width, height, c);
} else {
super.paint(g, c);
}
}
This doesn't help. It draws the same with or without this last change, which makes no sense.
Here is a class that illustrates the problem:
/**
* <p>Created by IntelliJ IDEA.
* <p>Date: 5/2/20
* <p>Time: 10:25 AM
*
* #author Miguel Mu\u00f1oz
*/
#SuppressWarnings({"HardcodedLineSeparator", "StringConcatenation", "HardCodedStringLiteral", "DuplicatedCode"})
public final class SwipeViewTest extends JPanel {
public static final String text1 = "Demo of Swipe View.\n\nThe swipe button will toggle between two pages of text. It has a built-in " +
"special effect, which is a swipe. When you hit the swipe button, it should flip between two pages of text. This worked fine on " +
"the older displays, but for some reason, on a Retina display, the text briefly switches to low resolution as the swipe proceeds, " +
"then switches back once it has finished. This code is written for retina displays. I don't know if it will work for the older, " +
"low resolution displays.\n\nYou can watch it swipe by hitting the space bar or by clicking the swipe button.";
public static final String text2 = "Demo of Swipe View.\n\nThis is the second page of the swipe-text demo. The change in resolution is " +
"most easily noticed when watching the line at the top, which doesn't change as the swipe is performed.";
private final SwipeView<TestView> swipeView;
private final TestView testView;
public static void main(String[] args) {
JFrame frame = new JFrame("SwipeView demo");
SwipeViewTest comp = new SwipeViewTest();
comp.install();
frame.add(comp);
frame.setLocationByPlatform(true);
frame.pack();
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.setVisible(true);
}
private boolean page1 = true;
private SwipeViewTest() {
super(new BorderLayout());
testView = new TestView();
swipeView = SwipeView.wrap(testView, 1000);
add(BorderLayout.CENTER, swipeView.getLayer());
}
private void install() {
JButton jButton = new JButton("Swipe");
jButton.addActionListener(this::doSwipe);
add(jButton, BorderLayout.PAGE_END);
AncestorListener ancestorListener = new AncestorListener() {
#Override
public void ancestorAdded(final AncestorEvent event) {
JComponent button = event.getComponent();
button.requestFocus();
button.removeAncestorListener(this); // execute only once.
}
#Override public void ancestorRemoved(final AncestorEvent event) { }
#Override public void ancestorMoved(final AncestorEvent event) { }
};
jButton.addAncestorListener(ancestorListener);
}
private void doSwipe(ActionEvent ignored) {
swipeView.swipeLeft(this::flipPage);
}
private void flipPage() {
page1 = !page1;
if (page1) {
testView.setText(text1);
} else {
testView.setText(text2);
}
}
private static class TestView extends JPanel {
private final JTextArea textArea;
TestView() {
super(new BorderLayout());
textArea = new JTextArea(20, 40);
JScrollPane scrollPane = new JScrollPane(textArea, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
textArea.setLineWrap(true);
textArea.setWrapStyleWord(true);
textArea.setEditable(false);
textArea.setText(text1);
add(scrollPane, BorderLayout.CENTER);
}
private void setText(String text) {
textArea.setText(text);
}
}
/**
* SwipeView adds a swipe special effect to a Component. This draws a swipe-right or swipe-left effect on a chosen
* action. It also optionally supports a repeated action when the mouse is held down.
* <p>
* This class is very specific right now, but I hope to generalize it for other special effects later.
* <p>Created by IntelliJ IDEA.
* <p>Date: 4/4/18
* <p>Time: 12:38 AM
*
* #author Miguel Mu\u00f1oz
*/
#SuppressWarnings("MagicNumber")
public static final class SwipeView<C extends JComponent> extends LayerUI<C> {
public static <J extends JComponent> SwipeView<J> wrap(J view, int durationMillis) {
JLayer<J> jLayer = new JLayer<>(view);
final SwipeView<J> ui = new SwipeView<>(view, jLayer, durationMillis);
jLayer.setUI(ui);
return ui;
}
private final C liveComponent;
private Image priorScreen = null;
private Image upcomingScreen = null;
private final JLayer<C> layer;
private boolean isAnimating = false;
private SwipeDirection swipeDirection = SwipeDirection.SWIPE_RIGHT;
private final int maxFrames;
// Calculated:
#SuppressWarnings("FieldCanBeLocal")
private final int frameMillis;
private int frame = 0;
private final long startTime = System.currentTimeMillis();
private SwipeView(C view, JLayer<C> theLayer, int animationDurationMillis) {
super();
liveComponent = view;
layer = theLayer;
maxFrames = (30 * animationDurationMillis) / 1000;
frameMillis = animationDurationMillis / maxFrames;
}
public JLayer<C> getLayer() { return layer; }
/**
* Perform the specified operation with a swipe-right special effect. This is often used in an ActionListener:
* <pre>
* first.addActionListener((e) -> swipeView.swipeRight(recordModel::goFirst));
* </pre>
* Here, the Action listener will perform a Swipe-right after executing the goFirst() method of recordModel.
*
* #param operation The operation
*/
#SuppressWarnings("WeakerAccess")
public void swipeRight(Runnable operation) {
swipe(operation, SwipeDirection.SWIPE_RIGHT);
}
/**
* Perform the specified operation with a swipe-left special effect. This is often used in an ActionListener:
* <pre>
* first.addActionListener((e) -> swipeView.swipeLeft(recordModel::goFirst));
* </pre>
* Here, the Action listener will perform a Swipe-Left after executing the goFirst() method of recordModel.
*
* #param operation The operation
*/
#SuppressWarnings("WeakerAccess")
public void swipeLeft(Runnable operation) {
swipe(operation, SwipeDirection.SWIPE_LEFT);
}
private void swipe(Runnable operation, SwipeDirection swipeDirection) {
prepareToAnimate(swipeDirection);
operation.run();
animate();
}
// #SuppressWarnings({"HardCodedStringLiteral", "HardcodedFileSeparator"})
#Override
public void paint(final Graphics g, final JComponent c) {
if (isAnimating) {
int xLimit = (c.getWidth() * 2 * frame) / maxFrames;
if (swipeDirection == SwipeDirection.SWIPE_LEFT) {
xLimit = (c.getWidth() * 2) - xLimit;
}
int width = c.getWidth() * 2;
int height = c.getHeight() * 2;
// //noinspection UseOfSystemOutOrSystemErr
// System.out.printf("Dimensions: Frame: %d/%d (at %d) xLimit: %4d (%4d x %4d) (from %4d x %4d) Animating: %b%n",
// frame, maxFrames, System.currentTimeMillis() - startTime, xLimit, width, height, c.getWidth(), c.getHeight(), isAnimating);
assert upcomingScreen != null;
assert priorScreen != null;
Image pScreen = Objects.requireNonNull(priorScreen);
Image uScreen = Objects.requireNonNull(upcomingScreen);
if (swipeDirection == SwipeDirection.SWIPE_RIGHT) {
g.drawImage(uScreen, 0, 0, xLimit, height, 0, 0, xLimit, height, c);
g.drawImage(pScreen, xLimit, 0, width, height, xLimit, 0, width, height, c);
} else {
g.drawImage(uScreen, xLimit, 0, width, height, xLimit, 0, width, height, c);
g.drawImage(pScreen, 0, 0, xLimit, height, 0, 0, xLimit, height, c);
}
} else {
super.paint(g, c);
}
}
private void prepareToAnimate(SwipeDirection swipeDirection) {
this.swipeDirection = swipeDirection;
isAnimating = true;
frame = 0;
// Save current state
priorScreen = new BufferedImage(liveComponent.getWidth() * 2, liveComponent.getHeight() * 2, BufferedImage.TYPE_INT_ARGB);
Graphics2D graphics2D = (Graphics2D) priorScreen.getGraphics();
liveComponent.paint(graphics2D);
graphics2D.dispose();
}
private void animate() {
Timer timer = new Timer(frameMillis, null);
final ActionListener actionListener = (evt) -> {
frame++;
layer.repaint();
if (frame == maxFrames) {
frame = 0;
isAnimating = false;
timer.stop(); // Investigate: Am I leaking timers?
}
};
timer.addActionListener(actionListener);
upcomingScreen = new BufferedImage(liveComponent.getWidth() * 2, liveComponent.getHeight() * 2, BufferedImage.TYPE_INT_ARGB);
Graphics2D graphics2D = (Graphics2D) upcomingScreen.getGraphics();
liveComponent.paint(graphics2D);
graphics2D.dispose();
timer.start();
}
}
public static enum SwipeDirection {
#SuppressWarnings("JavaDoc") SWIPE_RIGHT,
#SuppressWarnings("JavaDoc") SWIPE_LEFT
}
}
I don't use a retina display, but I did notice a slight painting difference when the animation started.
I changed both of your BufferedImage to get rid of the alpha value and I no longer notice the painting difference:
//priorScreen = new BufferedImage(liveComponent.getWidth() * 2, liveComponent.getHeight() * 2, BufferedImage.TYPE_INT_ARGB);
priorScreen = new BufferedImage(liveComponent.getWidth() * 2, liveComponent.getHeight() * 2, BufferedImage.TYPE_INT_RGB);
It turns out I needed to change the way I animated the frame to account for the doubling of the scale.
First, I needed to detect the scale. I added this code, which requires Java 9 or greater to work correctly. (It compiles under java 8, but fails to execute correctly, always returning 1 for any screen.)
private static final int SCALE = calculateScaleForDefaultScreen();
private static int calculateScaleForDefaultScreen() {
// scale will be 2.0 for a Retina screen, and 1.0 for an older screen
double scale = GraphicsEnvironment.getLocalGraphicsEnvironment()
.getDefaultScreenDevice()
.getDefaultConfiguration()
.getDefaultTransform()
.getScaleX(); // Requires Java 9+ to work. Compiles under Java 8 but always returns 1.0.
//noinspection NumericCastThatLosesPrecision
return (int) Math.round(scale);
}
When I prepared my two off-screen graphics, I needed to do so at twice the scale:
Graphics2D graphics2D = (Graphics2D) priorScreen.getGraphics();
graphics2D.scale(SCALE, SCALE);
liveComponent.paint(graphics2D); // paint the current state of liveComponent into the image
graphics2D.dispose();
And…
Graphics2D graphics2D = (Graphics2D) upcomingScreen.getGraphics();
graphics2D.scale(SCALE, SCALE);
liveComponent.paint(graphics2D); // paint the upcoming state of liveComponent into the image
graphics2D.dispose();
Then, when I did my animation, I needed to include the SCALE in the drawing.
if (swipeDirection == SwipeDirection.SWIPE_RIGHT) {
g.drawImage(uScreen, 0, 0, xLimit, height, 0, 0, xLimit*SCALE, height*SCALE, c);
g.drawImage(pScreen, xLimit, 0, width, height, xLimit*SCALE, 0, width*SCALE, height*SCALE, c);
} else {
g.drawImage(uScreen, xLimit, 0, width, height, xLimit*SCALE, 0, width*SCALE, height*SCALE, c);
g.drawImage(pScreen, 0, 0, xLimit, height, 0, 0, xLimit*SCALE, height*SCALE, c);
}
There are several other places where I multiplied widths and heights by 2. I changed those to SCALE as well.
I wish there were a more elegant solution, but this works.
This is not a duplicate. All other solutions I tried were outdated.
So first look at this Image
I made that in eclipse today in Java.
It looks like a Visual Novel.
The point is I want to draw some text on the screen but don't know how to.
At first I only want to know:
How to draw text on screen and change it
Make something, such as an image or some text, clickable to move to
the next scene
Here is my current code:
package textboxes;
import java.applet.Applet;
import java.awt.Color;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.net.URL;
public class test extends Applet implements Runnable, KeyListener {
private Image Image, Background;
private Image actor1, actor2;
private Image textbox;
private Graphics graphics;
private URL base;
private static testbg bg;
#Override
public void init(){
setSize(960, 540);
setBackground(Color.LIGHT_GRAY);
setFocusable(true);
Frame frame = (Frame)this.getParent().getParent();
frame.setTitle("School Scene");
try{
base = getDocumentBase();
}catch(Exception e){};
//getImages from disk
Background = getImage(base, "res/background.jpg");
actor1 = getImage(base, "res/actor1.jpg");
actor2 = getImage(base, "res/actor2.jpg");
textbox = getImage(base, "res/textbox.jpg");
}
public test(){
}
#Override
public void start(){
bg = new testbg();
Thread thread = new Thread(this);
thread.start();
}
#Override
public void keyPressed(KeyEvent arg0) {
// TODO Auto-generated method stub
}
#Override
public void keyReleased(KeyEvent arg0) {
// TODO Auto-generated method stub
}
#Override
public void keyTyped(KeyEvent arg0) {
// TODO Auto-generated method stub
}
#Override
public void run() {
bg.update();
repaint();
try{
Thread.sleep(17);
}catch(InterruptedException e){
e.printStackTrace();
}
}
#Override
public void update(Graphics g){
if(Image == null){
Image = createImage(this.getWidth(), this.getHeight());
graphics = Image.getGraphics();
}
graphics.setColor(getBackground());
graphics.fillRect(0, 0, getWidth(), getHeight());
graphics.setColor(getForeground());
paint(graphics);
g.drawImage(Image, 0, 0, this);
}
#Override
public void paint(Graphics g){
super.paint(g);
g.drawImage(Background, bg.getBgX(), bg.getBgY(), this);
g.drawImage(actor2, 40, 20, this);
g.drawImage(textbox, 80, 350, this);
}
public static testbg getBg() {
return bg;
}
}
This piece of code above is what I call test.java
if you are wondering about the Background part
the following piece of code is what I call testbg.java
package textboxes;
public class testbg {
private int bgX, bgY;
public testbg(){
bgX = 0;
bgY = 0;
}
public void update(){
}
public int getBgX(){
return bgX;
}
public int getBgY(){
return bgY;
}
public void setBgX(int bgX) {
this.bgX = bgX;
}
public void setBgY(int bgY) {
this.bgY = bgY;
}
}
Thanks for reading this much till the end...Now so can I know how to do it ??
As for the text, I have two solutions, but for what you want (and for what I know of CG games), I guess the first is the best.
This first solution is one that I found a long time ago for a problem of mine in StackOverflow (I don't remember where, sorry), in which includes de use of the several classes together to draw directly in the panel.
private final String message;
private final java.awt.geom.Rectangle2D.Float aboutMessageBounds;
private final AttributedString aboutMessageAttributedString;
private final AttributedCharacterIterator paragraph;
// The LineBreakMeasurer used to line-break the paragraph.
private java.awt.font.LineBreakMeasurer lineMeasurer;
// index of the first character in the paragraph.
private final int paragraphStart;
// index of the first character after the end of the paragraph.
private final int paragraphEnd;
#Override
public void init(){
(...)
java.util.Hashtable<TextAttribute, Object> textAtributMap =
new java.util.Hashtable<TextAttribute, Object>();
textAtributMap.put(TextAttribute.FAMILY, "Serif");
textAtributMap.put(TextAttribute.SIZE, new Float(26.0));
textAtributMap.put(TextAttribute.JUSTIFICATION, TextAttribute.JUSTIFICATION_FULL );
textAtributMap.put(TextAttribute.WEIGHT, TextAttribute.WEIGHT_DEMIBOLD );
textAtributMap.put(TextAttribute.LIGATURES, TextAttribute.LIGATURES_ON );
message = "This is a sample of a message.";
aboutMessageAttributedString = new AttributedString( aboutMessage, textAtributMap );
paragraph = aboutMessageAttributedString.getIterator();
paragraphStart = paragraph.getBeginIndex();
paragraphEnd = paragraph.getEndIndex();
(...)
}
#Override
protected void paintComponent( Graphics g ) {
super.paintComponent( g ); //To change body of generated methods, choose Tools | Templates.
Graphics2D g2 = (Graphics2D)g.create();
try {
g2.setRenderingHint( RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON );
// Create a new LineBreakMeasurer from the paragraph.
// It will be cached and re-used.
if (lineMeasurer == null) {
FontRenderContext frc = g2.getFontRenderContext();
lineMeasurer = new java.awt.font.LineBreakMeasurer(paragraph, frc);
}
//You can scale it like I did. this part is not part of the code that I found.
g2.scale( ratio.scaleDx, ratio.scaleDy );
// Set break width to width of Component.
//these were the measures I used for a something in a game;
float breakWidth = 734.0f;
float drawPosY = 90.0f;
float posX0 = 30.0f;
// Set position to the index of the first character in the paragraph.
lineMeasurer.setPosition(paragraphStart);
// Get lines until the entire paragraph has been displayed.
while (lineMeasurer.getPosition() < paragraphEnd) {
int next = lineMeasurer.nextOffset(breakWidth);
int limit = next;
if (limit <= message.length()) {
for (int i = lineMeasurer.getPosition(); i < next; ++i) {
char c = aboutMessage.charAt(i);
if (c == '\n') {
limit = i + 1;
break;
}
}
}
java.awt.font.TextLayout layout = lineMeasurer.nextLayout( breakWidth, limit, false );
// Retrieve next layout. A cleverer program would also cache
// these layouts until the component is re-sized.
// Compute pen x position. If the paragraph is right-to-left we
// will align the TextLayouts to the right edge of the panel.
// Note: this won't occur for the English text in this sample.
// Note: drawPosX is always where the LEFT of the text is placed.
float drawPosX = layout.isLeftToRight()
? posX0 : breakWidth - layout.getAdvance();
// Move y-coordinate by the ascent of the layout.
drawPosY += layout.getAscent();
// Draw the TextLayout at (drawPosX, drawPosY).
layout.draw(g2, drawPosX, drawPosY);
// Move y-coordinate in preparation for next layout.
drawPosY += layout.getDescent() + layout.getLeading();
}
}
finally {
g2.dispose();
}
}
As for the second solution, you could use a JEditorPane or a JTextPane. See the Oracle tutorial for this mater:
https://docs.oracle.com/javase/tutorial/uiswing/components/editorpane.html
I hop I have helped.
Have a nice day. :)
I'm trying to set up a menu system with buttons; however, only one button is displayed right.
Well I found the problem, I can't create multiple instances of the Button class from one class or it's sub-classes. If I do that it doesn't create the second instance right and it will then have a missing background image. Could that have to do with the fact that I made the Button class a standart class?
Here is the main portion of the Button class, all I took out where get methods which return the values of the things in this class.
public class Button {
private int x, y;
private int width, height;
private Image sprite;
private data.ImageControl Image = new data.ImageControl();
private String text = "";
public Button() {
sprite = Image.getImage("game/menu/btn.png");
}
public void setImage(String file) {
sprite = Image.getImage(file);
}
public void draw(Graphics2D g) {
g.drawImage(sprite, x, y, null);
Font_LARGE font = new Font_LARGE();
//Find text pos
int stringX, stringY;
int textWidth;
textWidth = text.length() * 14;
stringX = x + ((width / 2) - (textWidth / 2));
stringY = y + ((height / 2) - 8);
font.drawString(g, text, stringX, stringY);
}
And here is the code for where I get the image from:
public Image getImage(String filename) {
Image img;
try {
ImageIcon i = new ImageIcon(getClass().getResource("sprite/" + filename));
img = i.getImage();
} catch(Exception e) {
e.printStackTrace();
System.err.println("ERROR - Unable to load image at " + filename + " loading empty image.");
ImageIcon i = new ImageIcon(getClass().getResource("sprite/Physix/noImage.png"));
img = i.getImage();
}
return img;
}
What are the x and y positions?
It looks to me that you draw one button on top of the other one.
I have fixed the problem now by just drawing the button background outside of the button class. I have still no idea why it doesn't work, but this way it works.
I want to set the font size of button on componentResized event. I get the screensize and the size of the button. But can't get a way to calculate the preferred font size to set. If the Width of button is increased/decreased the font size also should increase/decrease accordingly. I can't get an object of Graphics also.
What could be a solution to work out for this problem ?
This is more of brute force solution.
1) You can try getting the font metrics using doing something like this:
// get metrics from the graphics
FontMetrics metrics = graphics.getFontMetrics(font);
// get the height of a line of text in this font and render context
int hgt = metrics.getHeight();
// get the advance of my text in this font and render context
int adv = metrics.stringWidth(text);
// calculate the size of a box to hold the text with some padding.
Dimension size = new Dimension(adv+2, hgt+2);
2) Then you can search through the fonts sizes that fit your component.
Font savedFont = oldFont;
for (int i = 0; i < 100; i++) {
Font newFont = new Font(oldFont.getFontName(), oldFont.getStyle(), i);
Dimension d = getFontSize(g,newFont,text);
if(componentSize.height < d.height || componentSize.width < d.width){
return savedFont;
}
savedFont = newFont;
}
Putting it all together (note this is not tested)
public Dimension getFontSize(Graphics graphics, Font font, String text){
// get metrics from the graphics
FontMetrics metrics = graphics.getFontMetrics(font);
// get the height of a line of text in this font and render context
int hgt = metrics.getHeight();
// get the advance of my text in this font and render context
int adv = metrics.stringWidth(text);
// calculate the size of a box to hold the text with some padding.
Dimension size = new Dimension(adv+2, hgt+2);
return size;
}
public Font findFont(Dimension componentSize, Font oldFont, String text, Graphics g){
//search up to 100
Font savedFont = oldFont;
for (int i = 0; i < 100; i++) {
Font newFont = new Font(oldFont.getFontName(), oldFont.getStyle(), i);
Dimension d = getFontSize(g,newFont,text);
if(componentSize.height < d.height || componentSize.width < d.width){
return savedFont;
}
savedFont = newFont;
}
return oldFont;
}
EDIT using component to get FontMetrics
public Dimension getFontSize(FontMetrics metrics ,Font font, String text){
// get the height of a line of text in this font and render context
int hgt = metrics.getHeight();
// get the advance of my text in this font and render context
int adv = metrics.stringWidth(text);
// calculate the size of a box to hold the text with some padding.
Dimension size = new Dimension(adv+2, hgt+2);
return size;
}
public Font findFont(Component component, Dimension componentSize, Font oldFont, String text){
//search up to 100
Font savedFont = oldFont;
for (int i = 0; i < 100; i++) {
Font newFont = new Font(oldFont.getFontName(), oldFont.getStyle(), i);
Dimension d = getFontSize(component.getFontMetrics(newFont),newFont,text);
if(componentSize.height < d.height || componentSize.width < d.width){
return savedFont;
}
savedFont = newFont;
}
return oldFont;
}
Measuring Text
If you would like the font size to adjust automagically on resizing, you could try something like this:
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
public final class ButtonFrame extends JFrame {
ButtonFrame(String buttonText) {
final JButton button = new JButton(buttonText);
addComponentListener(new ComponentAdapter() {
#Override
public void componentResized(ComponentEvent e) {
float fittedFontSize = 1.0f;
while (getFittedText(button, fittedFontSize += 1.0f).equals(button.getText()));
button.setFont(button.getFont().deriveFont(fittedFontSize - 1.0f));
button.revalidate();
button.repaint();
}
});
getContentPane().add(button);
}
private String getFittedText(JButton button, float fontSize) {
Insets i = button.getInsets();
Rectangle viewRect = new Rectangle();
Rectangle textRect = new Rectangle();
Rectangle iconRect = new Rectangle();
viewRect.x = i.left;
viewRect.y = i.top;
viewRect.width = button.getWidth() - (i.right + viewRect.x);
viewRect.height = button.getHeight() - (i.bottom + viewRect.y);
textRect.x = textRect.y = textRect.width = textRect.height = 0;
iconRect.x = iconRect.y = iconRect.width = iconRect.height = 0;
return SwingUtilities.layoutCompoundLabel(
button,
button.getFontMetrics(button.getFont().deriveFont(fontSize)),
button.getText(),
button.getIcon(),
button.getVerticalAlignment(),
button.getHorizontalAlignment(),
button.getVerticalTextPosition(),
button.getHorizontalTextPosition(),
viewRect,
textRect,
iconRect,
button.getIconTextGap());
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new ButtonFrame("sample text");
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.setVisible(true);
frame.pack();
}
});
}
}
You could create a class ResizingButton with a paintComponent which wraps the super.paintComponent. Then do on the (Graphics2D)graphics a resizing transformation. The reason: font resizing is stepwise.
If you delve deeper into resizing fonts, use a FontRenderingContext with fractional metrics.
In response to the comment some code:
public class ResizingButton extends JButton {
#Override
protected void paintComponent(Graphics g) {
int h = this.getHeight();
final int DEFAULT_H = 26;
double resizal = ((double)h) / DEFAULT_H;
String t = getText();
setText("<html><span style='font-size:" + (resizal*11) + "'>" + t);
super.paintComponent(g);
setText(t);
}
I found that the above (Java 6/7) works. Graphics2D.scale also scales border and is hence too cumbersome.
Setting the text as in the last but one answer causes a high load on the CPU.
My proposal is to scale the font accordingly:
public class ScalableJButton extends JButton {
int mCurrentSize = 0;
Font mInitialFont = null;
int mInitialHeight;
private static final long serialVersionUID = 1L;
public ScalableJButton(String pString) {
super(pString);
init();
}
public ScalableJButton() {
super();
init();
}
private void init() {
mInitialFont = getFont();
}
#Override
protected void paintComponent(Graphics g) {
if (mInitialHeight == 0) {
mInitialHeight = getHeight();
}
int resizal = this.getHeight() * mInitialFont.getSize() / mInitialHeight;
if(resizal != mCurrentSize){
setFont(mInitialFont.deriveFont((float) resizal));
mCurrentSize = resizal;
}
super.paintComponent(g);
}
}
I'm trying to create some nicer looking JTextFields with an image and a hint. To do this I made a decorator that overrides the paintComponent method. The reason I used a decorator is that I wanted to apply it to other types of JTextField such as JPasswordField.
Here is what I've made so far;
The problem as seen in the form on the left is that, even though I have used a JPasswordField the paintComponent seems to ignore what I assume is the passwords paintComponent which presumably does the password masking symbols.
So the question is, how can I avoid duplicating the code for JTextFields and JPasswordFields but still have the different functionality such as password masking.
This is the decorator code;
public class JTextFieldHint extends JTextField implements FocusListener{
private JTextField jtf;
private Icon icon;
private String hint;
private Insets dummyInsets;
public JTextFieldHint(JTextField jtf, String icon, String hint){
this.jtf = jtf;
setIcon(createImageIcon("icons/"+icon+".png",icon));
this.hint = hint;
Border border = UIManager.getBorder("TextField.border");
JTextField dummy = new JTextField();
this.dummyInsets = border.getBorderInsets(dummy);
addFocusListener(this);
}
public void setIcon(Icon newIcon){
this.icon = newIcon;
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
int textX = 2;
if(this.icon!=null){
int iconWidth = icon.getIconWidth();
int iconHeight = icon.getIconHeight();
int x = dummyInsets.left + 5;
textX = x+iconWidth+2;
int y = (this.getHeight() - iconHeight)/2;
icon.paintIcon(this, g, x, y);
}
setMargin(new Insets(2, textX, 2, 2));
if ( this.getText().equals("")) {
int width = this.getWidth();
int height = this.getHeight();
Font prev = g.getFont();
Font italic = prev.deriveFont(Font.ITALIC);
Color prevColor = g.getColor();
g.setFont(italic);
g.setColor(UIManager.getColor("textInactiveText"));
int h = g.getFontMetrics().getHeight();
int textBottom = (height - h) / 2 + h - 4;
int x = this.getInsets().left;
Graphics2D g2d = (Graphics2D) g;
RenderingHints hints = g2d.getRenderingHints();
g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
g2d.drawString(hint, x, textBottom);
g2d.setRenderingHints(hints);
g.setFont(prev);
g.setColor(prevColor);
}
}
protected ImageIcon createImageIcon(String path, String description) {
java.net.URL imgURL = getClass().getResource(path);
if (imgURL != null) {
return new ImageIcon(imgURL, description);
} else {
System.err.println("Couldn't find file: " + path);
return null;
}
}
#Override
public void focusGained(FocusEvent arg0) {
this.repaint();
}
#Override
public void focusLost(FocusEvent arg0) {
this.repaint();
}
}
And this is where I create the fields;
JTextField usernameField = new JTextFieldHint(new JTextField(),"user_green","Username");
JTextField passwordField = new JTextFieldHint(new JPasswordField(),"bullet_key","Password");
Hopefully i've not went completely off in the wrong direction here!
Thanks!
EDIT : Again the more I look at it, it is obvious that calling super.paintComponent(g) is going to call the JTextFields paintcomponent, but I can't see how to solve this without duplicating the code.
Text Prompt works with a JPasswordField.
One difference is that the displayed icon disappears when text is entered. If you want the icon to be permanent then I suggest you create a custom "IconBorder* class to paint an Icon rather then do custom painting in the paintComponent() method.
You approach will not work unless you duplicate the code for both JTextField and JPasswordField.
Edit:
Actually you don't need to create a custom IconBorder. The MatteBorder supports the painting of an Icon in a Border.
In order to paint an icon inside a text field you need to add some insets.
You don't want to hard-code insets in your component but just add a little bit of space for the icon, letting clients and subclasses to set their own.
In the figure above I painted original insets in green and additional insets in red. First thing you want to extend JTextField. We keep track of two things: the original insets (the green ones) mBorder, and the icon.
public class IconTextField extends JTextField {
private Border mBorder;
private Icon mIcon;
// ...
}
Then you need to override setBorder() method.
#Override
public void setBorder(Border border) {
mBorder = border;
if (mIcon == null) {
super.setBorder(border);
} else {
Border margin = BorderFactory.createEmptyBorder(0, mIcon.getIconWidth() + ICON_SPACING, 0, 0);
Border compound = BorderFactory.createCompoundBorder(border, margin);
super.setBorder(compound);
}
}
Here, if we have an icon (the field mIcon is not null), we add our additional insets using a compound border. Then, you should also override the paintComponent() method.
#Override
protected void paintComponent(Graphics graphics) {
super.paintComponent(graphics);
if (mIcon != null) {
Insets iconInsets = mBorder.getBorderInsets(this);
mIcon.paintIcon(this, graphics, iconInsets.left, iconInsets.top);
}
}
Finally, you need a setIcon() method.
public void setIcon(Icon icon) {
mIcon = icon;
resetBorder();
}
private void resetBorder() {
setBorder(mBorder);
}
What we are doing here is saving the icon and recalculating the borders.
If you want to do the same same thing with JPasswordField, the most elegant thing is probably to create a helper class with all the methods discussed above.
class IconTextComponentHelper {
private static final int ICON_SPACING = 4;
private Border mBorder;
private Icon mIcon;
private Border mOrigBorder;
private JTextComponent mTextComponent;
IconTextComponentHelper(JTextComponent component) {
mTextComponent = component;
mOrigBorder = component.getBorder();
mBorder = mOrigBorder;
}
Border getBorder() {
return mBorder;
}
void onPaintComponent(Graphics g) {
if (mIcon != null) {
Insets iconInsets = mOrigBorder.getBorderInsets(mTextComponent);
mIcon.paintIcon(mTextComponent, g, iconInsets.left, iconInsets.top);
}
}
void onSetBorder(Border border) {
mOrigBorder = border;
if (mIcon == null) {
mBorder = border;
} else {
Border margin = BorderFactory.createEmptyBorder(0, mIcon.getIconWidth() + ICON_SPACING, 0, 0);
mBorder = BorderFactory.createCompoundBorder(border, margin);
}
}
void onSetIcon(Icon icon) {
mIcon = icon;
resetBorder();
}
private void resetBorder() {
mTextComponent.setBorder(mOrigBorder);
}
}
And use it like this:
public class IconTextField extends JTextField {
private IconTextComponentHelper mHelper = new IconTextComponentHelper(this);
public IconTextField() {
super();
}
public IconTextField(int cols) {
super(cols);
}
private IconTextComponentHelper getHelper() {
if (mHelper == null)
mHelper = new IconTextComponentHelper(this);
return mHelper;
}
#Override
protected void paintComponent(Graphics graphics) {
super.paintComponent(graphics);
getHelper().onPaintComponent(graphics);
}
public void setIcon(Icon icon) {
getHelper().onSetIcon(icon);
}
public void setIconSpacing(int spacing) {
getHelper().onSetIconSpacing(spacing);
}
#Override
public void setBorder(Border border) {
getHelper().onSetBorder(border);
super.setBorder(getHelper().getBorder());
}
}