Why don't the iText 7 form field values print? - pdf

I have a PDF with forms that was generated with iText 7. However, when I fill out the form and attempt to print, the form values do not show up; only the form outline shows. How do I generate form fields that will display the value when printed?
A sample form generator:
import java.io.FileNotFoundException;
import java.io.IOException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.itextpdf.forms.PdfAcroForm;
import com.itextpdf.forms.fields.PdfFormField;
import com.itextpdf.forms.fields.PdfTextFormField;
import com.itextpdf.kernel.font.PdfFont;
import com.itextpdf.kernel.font.PdfFontFactory;
import com.itextpdf.kernel.geom.PageSize;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfWriter;
import com.itextpdf.layout.Document;
import com.itextpdf.layout.border.Border;
import com.itextpdf.layout.element.Cell;
import com.itextpdf.layout.element.Table;
import com.itextpdf.layout.renderer.CellRenderer;
import com.itextpdf.layout.renderer.DrawContext;
/**
* #author Lucas Vander Wal
*
*/
public final class TestForm {
private static final Logger log = LoggerFactory.getLogger(TestForm.class);
private static final PageSize LETTER_SIZE = new PageSize(612, 792),
LETTER_SIZE_LANDSCAPE = LETTER_SIZE.rotate();
private static final PdfFont FONT;
static {
try {
FONT = PdfFontFactory.createFont();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private static final float FONT_SIZE_12 = 12;
/**
* #param args
*/
public static void main(String[] args) {
log.info("Running pdf generation...");
new TestForm().generate("formTest.pdf");
log.info("Done with pdf generation.");
}
private Document doc;
private PdfDocument pdfDoc;
private PdfAcroForm form;
public TestForm() {
}
/**
* Generates the timesheet pdf and saves it to the given file location
*
* #param outputFile
*/
public void generate(String outputFile) {
try {
pdfDoc = new PdfDocument(new PdfWriter(outputFile));
doc = new Document(pdfDoc, LETTER_SIZE_LANDSCAPE);
// set document properties
doc.setFontSize(10);
float marginSize = doc.getTopMargin();
doc.setMargins(marginSize / 2, marginSize, marginSize / 2, marginSize);
form = PdfAcroForm.getAcroForm(pdfDoc, true);
// build the form
buildUserInfo();
// close the document
doc.close();
} catch (FileNotFoundException e) {
log.warn("Unable to save to file: " + outputFile, e);
}
}
private void buildUserInfo() {
// build the user info table
Table userTable = new Table(4).setBorder(Border.NO_BORDER).setWidthPercent(100).setMargin(0).setPadding(0);
// add 4 text entry fields
this.addFieldToTable(userTable, "nameEmployee", "Name of Employee:");
// add the table to the document
this.doc.add(userTable);
}
private void addFieldToTable(Table t, String fieldName, String fieldLabel) {
t.addCell(new Cell().add(fieldLabel).setPadding(5));
Cell cell = new Cell().setPadding(5);
cell.setNextRenderer(new CellRenderer(cell) {
#Override
public void draw(DrawContext drawContext) {
super.draw(drawContext);
PdfTextFormField field = PdfFormField.createText(
drawContext.getDocument(), getOccupiedAreaBBox().decreaseHeight(5), fieldName, "",
FONT, FONT_SIZE_12);
form.addField(field);
}
});
t.addCell(cell);
}
}

PDF form fields have a visibility setting that affects the ability to print. See this Adobe page for more information.
The form fields need to be set to 'VISIBLE', using the setVisibility() method on PdfFormField objects. This iText 7 documentation explains how to set forms to 'HIDDEN','VISIBLE_BUT_DOES_NOT_PRINT', and 'HIDDEN_BUT_PRINTABLE', however it doesn't mention setting to visible, and in fact, visible is not one of the static options in PdfFormField.
The three values listed above correspond to the integers 1, 2, and 3, and on a hunch, I set the visibility to 0, and the form field displayed when printed!
In summary, call PdfFormField.setVisibility(0) to create form fields that are printable.

Related

Cleaning up unused images in PDF page resources

Please forgive me if this has been asked but I have not found any matches yet.
I have some PDF files where images are duplicated on each page's resources but never used in its content stream. I think this is causing the PDFSplit command to create very bloated pages. Is there any utility code or examples to clean up unused resources like this? Maybe a starting point for me to get going?
I was able to clean up the resources for each page by gathering a list of the images used inside the page's content stream. With the list of images, I then check the resources for the page and remove any that weren't used. See the PageExtractor.stripUnusedImages below for implementation details.
The resource object was shared between pages so I also had to make sure each page had its own copy of the resource object before removing images. See PageExtractor.copyResources below for implementation details.
The page splitter:
package org.apache.pdfbox.examples;
import org.apache.pdfbox.contentstream.operator.Operator;
import org.apache.pdfbox.cos.COSBase;
import org.apache.pdfbox.cos.COSDictionary;
import org.apache.pdfbox.cos.COSName;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDResources;
import org.apache.pdfbox.pdmodel.interactive.action.PDAction;
import org.apache.pdfbox.pdmodel.interactive.action.PDActionGoTo;
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation;
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationLink;
import org.apache.pdfbox.pdmodel.interactive.documentnavigation.destination.PDDestination;
import org.apache.pdfbox.pdmodel.interactive.documentnavigation.destination.PDPageDestination;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class PageExtractor {
private final Logger log = LoggerFactory.getLogger(this.getClass());
public PDDocument extractPage(PDDocument source, Integer pageNumber) throws IOException {
PDDocument targetPdf = new PDDocument();
targetPdf.getDocument().setVersion(source.getVersion());
targetPdf.setDocumentInformation(source.getDocumentInformation());
targetPdf.getDocumentCatalog().setViewerPreferences(source.getDocumentCatalog().getViewerPreferences());
PDPage sourcePage = source.getPage(pageNumber);
PDPage targetPage = targetPdf.importPage(sourcePage);
targetPage.setResources(sourcePage.getResources());
stripUnusedImages(targetPage);
stripPageLinks(targetPage);
return targetPdf;
}
/**
* Collect the images used from a custom PDFStreamEngine (BI and DO operators)
* Create an empty COSDictionary
* Loop through the page's XObjects that are images and add them to the new COSDictionary if they were found in the PDFStreamEngine
* Assign the newly filled COSDictionary to the page's resource as COSName.XOBJECT
*/
protected void stripUnusedImages(PDPage page) throws IOException {
PDResources resources = copyResources(page);
COSDictionary pageObjects = (COSDictionary) resources.getCOSObject().getDictionaryObject(COSName.XOBJECT);
COSDictionary newObjects = new COSDictionary();
Set<String> imageNames = findImageNames(page);
Iterable<COSName> xObjectNames = resources.getXObjectNames();
for (COSName xObjectName : xObjectNames) {
if (resources.isImageXObject(xObjectName)) {
Boolean used = imageNames.contains(xObjectName.getName());
if (used) {
newObjects.setItem(xObjectName, pageObjects.getItem(xObjectName));
} else {
log.info("Found unused image: name={}", xObjectName.getName());
}
} else {
newObjects.setItem(xObjectName, pageObjects.getItem(xObjectName));
}
}
resources.getCOSObject().setItem(COSName.XOBJECT, newObjects);
page.setResources(resources);
}
/**
* It is necessary to copy the page's resources since it can be shared with other pages. We must ensure changes
* to the resources are scoped to the current page.
*/
protected PDResources copyResources(PDPage page) {
return new PDResources(new COSDictionary(page.getResources().getCOSObject()));
}
protected Set<String> findImageNames(PDPage page) throws IOException {
Set<String> imageNames = new HashSet<>();
PdfImageStreamEngine engine = new PdfImageStreamEngine() {
#Override
void handleImage(Operator operator, List<COSBase> operands) {
COSName name = (COSName) operands.get(0);
imageNames.add(name.getName());
}
};
engine.processPage(page);
return imageNames;
}
/**
* Borrowed from PDFBox page splitter
*
* #see org.apache.pdfbox.multipdf.Splitter#processAnnotations(org.apache.pdfbox.pdmodel.PDPage)
*/
protected void stripPageLinks(PDPage imported) throws IOException {
List<PDAnnotation> annotations = imported.getAnnotations();
for (PDAnnotation annotation : annotations) {
if (annotation instanceof PDAnnotationLink) {
PDAnnotationLink link = (PDAnnotationLink) annotation;
PDDestination destination = link.getDestination();
if (destination == null && link.getAction() != null) {
PDAction action = link.getAction();
if (action instanceof PDActionGoTo) {
destination = ((PDActionGoTo) action).getDestination();
}
}
if (destination instanceof PDPageDestination) {
// TODO preserve links to pages within the splitted result
((PDPageDestination) destination).setPage(null);
}
}
// TODO preserve links to pages within the splitted result
annotation.setPage(null);
}
}
}
The stream reader used to analyze the page's images:
package org.apache.pdfbox.examples;
import org.apache.pdfbox.contentstream.PDFStreamEngine;
import org.apache.pdfbox.contentstream.operator.Operator;
import org.apache.pdfbox.contentstream.operator.OperatorProcessor;
import org.apache.pdfbox.cos.COSBase;
import org.apache.pdfbox.cos.COSName;
import org.apache.pdfbox.pdmodel.graphics.PDXObject;
import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
import org.apache.pdfbox.pdmodel.graphics.form.PDTransparencyGroup;
import java.io.IOException;
import java.util.List;
abstract public class PdfImageStreamEngine extends PDFStreamEngine {
PdfImageStreamEngine() {
addOperator(new DrawObjectCounter());
}
abstract void handleImage(Operator operator, List<COSBase> operands);
protected class DrawObjectCounter extends OperatorProcessor {
#Override
public void process(Operator operator, List<COSBase> operands) throws IOException {
if (operands != null && isImage(operands.get(0))) {
handleImage(operator, operands);
}
}
protected Boolean isImage(COSBase base) throws IOException {
if (!(base instanceof COSName)) {
return false;
}
COSName name = (COSName)base;
if (context.getResources().isImageXObject(name)) {
return true;
}
PDXObject xObject = context.getResources().getXObject(name);
if (xObject instanceof PDTransparencyGroup) {
context.showTransparencyGroup((PDTransparencyGroup)xObject);
} else if (xObject instanceof PDFormXObject) {
context.showForm((PDFormXObject)xObject);
}
return false;
}
#Override
public String getName() {
return "Do";
}
}
}

pdfbox cannot find symbol

I'm using code suggested a while ago by #ASu :
package pdf_form_filler;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
import org.apache.pdfbox.pdmodel.interactive.form.*;
import java.io.File;
import java.util.*;
public class pdf_form_filler {
public static void listFields(PDDocument doc) throws Exception {
PDDocumentCatalog catalog = doc.getDocumentCatalog();
PDAcroForm form = catalog.getAcroForm();
List<PDFieldTreeNode> fields = form.getFields();
for(PDFieldTreeNode field: fields) {
Object value = field.getValue();
String name = field.getFullyQualifiedName();
System.out.print(name);
System.out.print(" = ");
System.out.print(value);
System.out.println();
}
}
public static void main(String[] args) throws Exception {
File file = new File("test.pdf");
PDDocument doc = PDDocument.load(file);
listFields(doc);
}
}
However, i keep getting a cannot find symbol error for PDFieldTreeNode. I have the latest pdfbox (2.0.4) but I cannot find the class in it anyway. I tried using this with PDField instead but then get error for .getValue
To get all terminal fields, do this:
Iterator<PDField> fieldIterator = catalog.getAcroForm();
while (fieldIterator.hasNext())
{
PDField pdField = fieldIterator.next();
// do stuff with the field
}
form.getFields() only gets the top level fields, including nonterminal fields (i.e. fields with no value but children).
A more advanced example can be found in the PrintFields.java example.

apache poi ppt to png emf

I would like to ask about library APACHE POI. I have a .pptx file and with the example which I found on internet I split each slide into separate images. This works great when slide containts .png, .jpeg images but as soon as I have .emf files in slide this image disappear. So I want to have the same copy of slide but with .emf file as well. Is this possible?
version: Apache POI 3.12
Thanks a lot
As mentioned in the comments, EMF is not supported out of the box ... and as FreeHep decided to have a LGPL license it's unlikely that we will include in our release.
In POI you basically have two options to provide a custom image renderer:
implement and register your ImageRenderer implementation in the Graphics2D context via setRenderingHint(Drawable.IMAGE_RENDERER, new MyImageRendener()). The drawback is, that it will be called for any images and you loose the default handling for bitmap/wmf images
or provide a custom DrawFactory which serves your own DrawPictureShape implementation - as in the example below - which only diverts to the EMF renderer when necessary
(looking at this example, the current POI ImageRenderer handling doesn't look flexible and instead of having a global handler, it might be better to register a handler per content-type ...)
Apart of the usual POI component I've included freehep-graphics2d, freehep-graphicsbase, freehep-graphicsio, freehep-graphicsio-emf and freehep-io.
The example is far from being finished, but I guess you get at least a kick-start:
package org.apache.poi.xslf;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.RenderingHints;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import javax.imageio.ImageIO;
import org.apache.poi.sl.draw.DrawFactory;
import org.apache.poi.sl.draw.DrawPictureShape;
import org.apache.poi.sl.draw.Drawable;
import org.apache.poi.sl.draw.ImageRenderer;
import org.apache.poi.sl.usermodel.PictureData;
import org.apache.poi.sl.usermodel.PictureData.PictureType;
import org.apache.poi.sl.usermodel.PictureShape;
import org.apache.poi.util.POILogFactory;
import org.apache.poi.util.POILogger;
import org.apache.poi.xslf.usermodel.XMLSlideShow;
import org.apache.poi.xslf.usermodel.XSLFPictureShape;
import org.apache.poi.xslf.usermodel.XSLFSlide;
import org.freehep.graphicsio.emf.EMFInputStream;
import org.freehep.graphicsio.emf.EMFRenderer;
import org.junit.Test;
public class TestEMFRender {
private final static POILogger LOG = POILogFactory.getLogger(TestEMFRender.class);
#Test
public void render() throws Exception {
XMLSlideShow ppt = getDummy();
Dimension pgsize = ppt.getPageSize();
BufferedImage img = new BufferedImage((int)pgsize.getWidth(), (int)pgsize.getHeight(), BufferedImage.TYPE_INT_ARGB);
Graphics2D graphics = initGraphicsCtx(img);
// draw stuff
ppt.getSlides().get(0).draw(graphics);
// save the result
File outfile = new File("bla.png");
ImageIO.write(img, "PNG", outfile);
// cleanup
graphics.dispose();
img.flush();
ppt.close();
}
static XMLSlideShow getDummy() throws IOException {
XMLSlideShow ppt = new XMLSlideShow();
FileInputStream fis = new FileInputStream("kiwilogo.emf");
PictureData pd = ppt.addPicture(fis, PictureType.EMF);
fis.close();
XSLFSlide sl = ppt.createSlide();
XSLFPictureShape ps = sl.createPicture(pd);
ps.setAnchor(new Rectangle2D.Double(100, 100, 100, 100));
return ppt;
}
static Graphics2D initGraphicsCtx(BufferedImage img) {
Graphics2D graphics = img.createGraphics();
// default rendering options
graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
graphics.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
graphics.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
// custom draw factory
DrawFactory df = new DrawFactory(){
public DrawPictureShape getDrawable(PictureShape<?,?> shape) {
return new DrawPictureShapeEmf(shape);
}
};
graphics.setRenderingHint(Drawable.DRAW_FACTORY, df);
df.fixFonts(graphics);
return graphics;
}
static class DrawPictureShapeEmf extends DrawPictureShape {
public DrawPictureShapeEmf(PictureShape<?,?> shape) {
super(shape);
}
#Override
public void drawContent(Graphics2D graphics) {
PictureData data = getShape().getPictureData();
if(data == null) return;
Rectangle2D anchor = getAnchor(graphics, getShape());
Insets insets = getShape().getClipping();
try {
String contentType = data.getContentType();
ImageRenderer renderer =
PictureType.EMF.contentType.equals(contentType)
? new ImgRenderer()
: super.getImageRenderer(graphics, contentType);
renderer.loadImage(data.getData(), data.getContentType());
renderer.drawImage(graphics, anchor, insets);
} catch (IOException e) {
LOG.log(POILogger.ERROR, "image can't be loaded/rendered.", e);
}
}
}
static class ImgRenderer implements ImageRenderer {
EMFRenderer emfRenderer;
public void loadImage(InputStream data, String contentType) throws IOException {
emfRenderer = new EMFRenderer(new EMFInputStream(data));
}
public void loadImage(byte[] data, String contentType) throws IOException {
loadImage(new ByteArrayInputStream(data), contentType);
}
public Dimension getDimension() {
return emfRenderer.getSize();
}
public void setAlpha(double alpha) {
}
public BufferedImage getImage() {
return null;
}
public BufferedImage getImage(Dimension dim) {
return null;
}
public boolean drawImage(Graphics2D ctx, Rectangle2D graphicsBounds) {
return drawImage(ctx, graphicsBounds, null);
}
public boolean drawImage(Graphics2D ctx, Rectangle2D graphicsBounds, Insets clip) {
AffineTransform at = ctx.getTransform();
try {
Dimension emfDim = emfRenderer.getSize();
// scale output bounds to image bounds
ctx.translate(graphicsBounds.getX(), graphicsBounds.getY());
ctx.scale(graphicsBounds.getWidth()/emfDim.getWidth(), graphicsBounds.getHeight()/emfDim.getHeight());
// TODO: handle clipping
emfRenderer.paint(ctx);
return true;
} catch (RuntimeException e) {
// TODO: logging
return false;
} finally {
ctx.setTransform(at);
}
}
}
}

Convert Byte array to pdf for printing

I have a byte array and I need to send this byte array over the print server socket for printing as pdf. How do I convert this byte array into pdf bytes ?.
Basically, I created a pdf template and using PdfStamper, I am generating the pdf
PdfStamper stamper = new PdfStamper(pdfTemplate, out);
and then convering the ByteArrayOutputStream to byte array (out.toByteArray()). I am sending this byte array to another service which just picks the byte array and send it over the socket for printing. I tried to print this and this prints nothing but a blank page.
I guess, the printer is not recognizing this as a pdf. How do I tell printer that my byte array is pdf (How do I convert the byte array to pdf printable format)
calling this from method where I m passing my byte array and
DocFlavor flavor = DocFlavor.BYTE_ARRAY.AUTOSENSE;
You have to call this method by passing flavor and byte array Like this
new PrintTest().print(byteArray, flavor);
import java.awt.Graphics;
import java.awt.print.PageFormat;
import java.awt.print.Printable;
import java.awt.print.PrinterException;
import java.awt.print.PrinterJob;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.util.Set;
import javax.print.Doc;
import javax.print.DocFlavor;
import javax.print.DocPrintJob;
import javax.print.PrintException;
import javax.print.PrintService;
import javax.print.SimpleDoc;
import javax.print.attribute.DocAttributeSet;
import javax.print.attribute.standard.PrinterStateReason;
import javax.print.attribute.standard.PrinterStateReasons;
import javax.print.attribute.standard.Severity;
import javax.print.event.PrintJobAttributeEvent;
import javax.print.event.PrintJobAttributeListener;
import javax.print.event.PrintJobEvent;
import javax.print.event.PrintJobListener;
import javax.print.event.PrintServiceAttributeEvent;
import javax.print.event.PrintServiceAttributeListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class PrintTest implements PrintServiceAttributeListener,PrintJobListener,Doc, Printable, PrintJobAttributeListener {
protected Logger logger = LoggerFactory.getLogger(PrintTest.class);
public void print(final byte[] byteArray, final DocFlavor flavor) {
Thread newThread = new Thread(new Runnable() {
public void run() {
PrintService ps = PrinterJob.getPrinterJob().getPrintService();
ps.addPrintServiceAttributeListener(PrintTest.this);
DocPrintJob docJob = ps.createPrintJob();
docJob.addPrintJobAttributeListener(PrintTest.this, null);
docJob.addPrintJobListener(PrintTest.this);
Doc document = new SimpleDoc(byteArray, flavor, null);
try {
docJob.print(document,null);
}
catch (PrintException e) {
e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
}
}
});
newThread.start();
/**
PrintServiceAttributeSet attSet = ps.getAttributes();
PrinterStateReasons psr = ps.getAttribute(PrinterStateReasons.class);
if (psr != null) {
Set<PrinterStateReason> errors = psr.printerStateReasonSet(Severity.REPORT);
for (PrinterStateReason reason : errors)
System.out.printf(" Reason : %s",reason.getName());
logger.info();
} */
}
public void attributeUpdate(PrintServiceAttributeEvent psae) {
logger.info("attributeUpdate: "+psae.getAttributes());
}
public void printDataTransferCompleted(PrintJobEvent pje) {
logger.info("Transfer completed");
}
public void printJobCompleted(PrintJobEvent pje) {
logger.info("Completed");
}
public void printJobFailed(PrintJobEvent pje) {
logger.info("Failed");
PrinterStateReasons psr = pje.getPrintJob().getPrintService().getAttribute(PrinterStateReasons.class);
if (psr != null) {
Set<PrinterStateReason> errors = psr.printerStateReasonSet(Severity.REPORT);
for (PrinterStateReason reason : errors)
logger.info(" Reason : %s",reason.getName());
logger.info("\n");
}
}
public void printJobCanceled(PrintJobEvent pje) {
logger.info("Canceled");
}
public void printJobNoMoreEvents(PrintJobEvent pje) {
logger.info("No more events");
logger.info("printJobNoMoreEvents: "+pje.getPrintEventType());
}
public void printJobRequiresAttention(PrintJobEvent pje) {
logger.info("Job requires attention");
PrinterStateReasons psr = pje.getPrintJob().getPrintService().getAttribute(PrinterStateReasons.class);
if (psr != null) {
Set<PrinterStateReason> errors = psr.printerStateReasonSet(Severity.REPORT);
for (PrinterStateReason reason : errors)
logger.info(" Reason : %s",reason.getName());
logger.info("\n");
}
}
public DocFlavor getDocFlavor() {
return DocFlavor.SERVICE_FORMATTED.PRINTABLE; //To change body of implemented methods use File | Settings | File Templates.
}
public Object getPrintData() throws IOException {
return this;
}
public DocAttributeSet getAttributes() {
return null; //To change body of implemented methods use File | Settings | File Templates.
}
public Reader getReaderForText() throws IOException {
return null; //To change body of implemented methods use File | Settings | File Templates.
}
public InputStream getStreamForBytes() throws IOException {
return null; //To change body of implemented methods use File | Settings | File Templates.
}
public int print(Graphics graphics, PageFormat pageFormat, int pageIndex) throws PrinterException {
return pageIndex == 0 ? PAGE_EXISTS : NO_SUCH_PAGE; //To change body of implemented methods use File | Settings | File Templates.
}
public void attributeUpdate(PrintJobAttributeEvent pjae) {
logger.info("Look out");
}
}

JavaFX How to change ProgressBar color dynamically?

I was trying to solve my problem with colored progress bars in this thread. The solution was present, but then I ran into another problem: I can't change color dynamically from my code. I want to do it right from my code, not with pre-defined .css. Generally I can do it, but I run into some difficulties when I try to do it with more than one progess bar.
public class JavaFXApplication36 extends Application {
#Override
public void start(Stage primaryStage) {
AnchorPane root = new AnchorPane();
ProgressBar pbRed = new ProgressBar(0.4);
ProgressBar pbGreen = new ProgressBar(0.6);
pbRed.setLayoutY(10);
pbGreen.setLayoutY(30);
pbRed.setStyle("-fx-accent: red;"); // line (1)
pbGreen.setStyle("-fx-accent: green;"); // line (2)
root.getChildren().addAll(pbRed, pbGreen);
Scene scene = new Scene(root, 150, 50);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
}
I always get 2 red progressbars with it! It seems that code in line (1) changes the style of ProgressBar class, not the instance.
Another strange moment is that deleting line (1) don't result in 2 green progress bars. So I can figure that line (2) is completely useless!! WHY?! That's definitely getting odd.
Is there any way to set different colors for separate progressbars?
See also the StackOverflow JavaFX ProgressBar Community Wiki.
There is a workaround you can use until a bug to fix the sample code in your question is filed and fixed.
The code in this answer does a node lookup on the ProgressBar contents, then dynamically modifies the bar colour of the progress bar to any value you like.
import javafx.application.Application;
import javafx.beans.value.*;
import javafx.geometry.Pos;
import javafx.scene.*;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
public class ProgressBarDynamicColor extends Application {
public static void main(String[] args) { launch(args); }
#Override public void start(Stage stage) {
PickedColorBar aquaBar = new PickedColorBar(0.4, Color.AQUA);
PickedColorBar fireBar = new PickedColorBar(0.6, Color.FIREBRICK);
HBox layout = new HBox(20);
layout.getChildren().setAll(aquaBar, fireBar);
layout.setStyle("-fx-background-color: -fx-box-border, cornsilk; -fx-padding: 15;");
stage.setScene(new Scene(layout));
stage.show();
aquaBar.wasShown();
fireBar.wasShown();
}
class PickedColorBar extends VBox {
private final ProgressBar bar;
private final ColorPicker picker;
private boolean wasShownCalled = false;
final ChangeListener<Color> COLOR_LISTENER = new ChangeListener<Color>() {
#Override public void changed(ObservableValue<? extends Color> value, Color oldColor, Color newColor) {
setBarColor(bar, newColor);
}
};
public PickedColorBar(double progress, Color initColor) {
bar = new ProgressBar(progress);
picker = new ColorPicker(initColor);
setSpacing(10);
setAlignment(Pos.CENTER);
getChildren().setAll(bar, picker);
}
// invoke only after the progress bar has been shown on a stage.
public void wasShown() {
if (!wasShownCalled) {
wasShownCalled = true;
setBarColor(bar, picker.getValue());
picker.valueProperty().addListener(COLOR_LISTENER);
}
}
private void setBarColor(ProgressBar bar, Color newColor) {
bar.lookup(".bar").setStyle("-fx-background-color: -fx-box-border, " + createGradientAttributeValue(newColor));
}
private String createGradientAttributeValue(Color newColor) {
String hsbAttribute = createHsbAttributeValue(newColor);
return "linear-gradient(to bottom, derive(" + hsbAttribute+ ",30%) 5%, derive(" + hsbAttribute + ",-17%))";
}
private String createHsbAttributeValue(Color newColor) {
return
"hsb(" +
(int) newColor.getHue() + "," +
(int) (newColor.getSaturation() * 100) + "%," +
(int) (newColor.getBrightness() * 100) + "%)";
}
}
}
The code uses inlined string processing of css attributes to manipulate Region backgrounds. Future JavaFX versions (e.g. JDK8+) will include a public Java API to manipulate background attributes, making obsolete the string processing of attributes from the Java program.
Sample program output: