How to show cross lines and red color for price drop in JFreeChart? - jfreechart

My app is simplified below :
import java.awt.*;
import java.awt.event.*;
import java.text.*;
import javax.swing.*;
import org.jfree.chart.*;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.entity.*;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.XYBarRenderer;
import org.jfree.data.time.*;
import org.jfree.data.xy.XYDataset;
import org.jfree.chart.labels.*;
import org.jfree.chart.panel.*;
import org.jfree.chart.plot.*;
public class PriceVolume_Chart extends JPanel implements ChartMouseListener // A demo application for price-volume chart.
{
ChartPanel panel;
TimeSeries Price_series=new TimeSeries("Price");
TimeSeries Volume_Series=new TimeSeries("Volume");
Crosshair xCrosshair,yCrosshair;
public PriceVolume_Chart(String Symbol)
{
JFreeChart chart=createChart(Symbol);
panel=new ChartPanel(chart,true,true,true,false,true);
panel.setPreferredSize(new java.awt.Dimension(1000,500));
panel.addChartMouseListener(this);
CrosshairOverlay crosshairOverlay=new CrosshairOverlay();
xCrosshair=new Crosshair(Double.NaN,Color.GRAY,new BasicStroke(0f));
xCrosshair.setLabelVisible(true);
yCrosshair=new Crosshair(Double.NaN,Color.GRAY,new BasicStroke(0f));
yCrosshair.setLabelVisible(true);
crosshairOverlay.addDomainCrosshair(xCrosshair);
crosshairOverlay.addRangeCrosshair(yCrosshair);
panel.addOverlay(crosshairOverlay);
add(panel);
}
private JFreeChart createChart(String Symbol)
{
createPriceDataset(Symbol);
XYDataset priceData=new TimeSeriesCollection(Price_series);
JFreeChart chart=ChartFactory.createTimeSeriesChart(Symbol,
"Date",
getYLabel("Price ( $ )"),
priceData,
true,
true,
true
);
XYPlot plot=chart.getXYPlot();
plot.setBackgroundPaint(new Color(192,196,196));
NumberAxis rangeAxis1=(NumberAxis)plot.getRangeAxis();
rangeAxis1.setLowerMargin(0.40); // Leave room for volume bars
// plot.getRenderer().setDefaultToolTipGenerator(new StandardXYToolTipGenerator(StandardXYToolTipGenerator.DEFAULT_TOOL_TIP_FORMAT,DateFormat.getDateInstance(), NumberFormat.getCurrencyInstance()));
plot.getRenderer().setDefaultToolTipGenerator(new StandardXYToolTipGenerator(StandardXYToolTipGenerator.DEFAULT_TOOL_TIP_FORMAT,new SimpleDateFormat("yyyy-MM-d"),NumberFormat.getCurrencyInstance()));
// DecimalFormat format=new DecimalFormat("00.00");
// rangeAxis1.setNumberFormatOverride(format);
// rangeAxis1.setNumberFormatOverride(NumberFormat.getCurrencyInstance());
NumberAxis rangeAxis2=new NumberAxis("Volume");
rangeAxis2.setUpperMargin(1.00); // Leave room for price line
rangeAxis2.setNumberFormatOverride(NumberFormat.getNumberInstance());
plot.setRangeAxis(1,rangeAxis2);
plot.setDataset(1,new TimeSeriesCollection(Volume_Series));
plot.setRangeAxis(1,rangeAxis2);
plot.mapDatasetToRangeAxis(1,1);
XYBarRenderer renderer2=new XYBarRenderer(0.20);
renderer2.setShadowVisible(false);
renderer2.setDefaultToolTipGenerator(new StandardXYToolTipGenerator(StandardXYToolTipGenerator.DEFAULT_TOOL_TIP_FORMAT,new SimpleDateFormat("yyyy-MM-d"),new DecimalFormat("0,000")));
plot.setRenderer(1,renderer2);
return chart;
}
private void createPriceDataset(String Symbol)
{
String Lines[]=new String[21],Items[],Date;
int Year,Month,Day;
long Volume;
double Price;
Lines[0]="Date,Open,High,Low,Close,Adj Close,Volume";
Lines[1]="2019-09-23,129.589996,130.710007,128.240005,129.300003,126.555969,553700";
Lines[2]="2019-09-24,129.309998,129.529999,125.500000,126.750000,124.060089,732900";
Lines[3]="2019-09-25,126.570000,128.500000,126.190002,127.879997,125.166100,422000";
Lines[4]="2019-09-26,127.849998,128.589996,127.169998,127.779999,125.068230,376100";
Lines[5]="2019-09-27,128.669998,129.289993,126.389999,126.419998,123.737083,332900";
Lines[6]="2019-09-30,126.589996,128.789993,125.849998,128.130005,125.410797,456700";
Lines[7]="2019-10-01,129.039993,130.899994,125.480003,126.040001,123.365158,322700";
Lines[8]="2019-10-02,125.059998,125.180000,121.620003,123.120003,120.507126,577100";
Lines[9]="2019-10-03,122.650002,123.320000,119.089996,122.559998,119.959007,581300";
Lines[10]="2019-10-04,122.970001,123.949997,121.320000,123.879997,121.250992,315700";
Lines[11]="2019-10-07,123.139999,124.610001,122.669998,122.879997,120.272217,510300";
Lines[12]="2019-10-08,121.720001,121.879997,118.089996,118.660004,116.141777,616600";
Lines[13]="2019-10-09,119.410004,119.610001,116.680000,118.419998,115.906868,603300";
Lines[14]="2019-10-10,119.089996,121.209999,117.080002,118.209999,115.701324,483300";
Lines[15]="2019-10-11,120.330002,123.040001,119.720001,122.550003,119.949226,700500";
Lines[16]="2019-10-14,122.550003,123.720001,120.940002,122.540001,119.939430,492900";
Lines[17]="2019-10-15,122.849998,124.220001,121.230003,123.699997,121.074814,598200";
Lines[18]="2019-10-16,123.889999,124.849998,122.800003,123.209999,120.595207,663600";
Lines[19]="2019-10-17,123.449997,124.889999,122.790001,123.360001,120.742035,563200";
Lines[20]="2019-10-18,123.050003,124.620003,122.459999,123.540001,120.918213,650300";
for (int i=1;i<Lines.length;i++)
{
Items=Lines[i].split(",");
Date=Items[0].replace("-0","-");
Price=Double.parseDouble(Items[5]);
Volume=Long.parseLong(Items[6]);
Items=Date.split("-");
Year=Integer.parseInt(Items[0]);
Month=Integer.parseInt(Items[1]);
Day=Integer.parseInt(Items[2]);
Price_series.add(new Day(Day,Month,Year),Price);
Volume_Series.add(new Day(Day,Month,Year),Volume);
}
}
#Override
public void chartMouseClicked(ChartMouseEvent event)
{
// ignore
}
public void chartMouseMoved(ChartMouseEvent cmevent)
{
ChartEntity chartentity=cmevent.getEntity();
if (chartentity instanceof XYItemEntity)
{
XYItemEntity e=(XYItemEntity)chartentity;
XYDataset d=e.getDataset();
int s=e.getSeriesIndex();
int i=e.getItem();
double x=d.getXValue(s,i);
double y=d.getYValue(s,i);
this.xCrosshair.setValue(x);
this.yCrosshair.setValue(y);
}
}
String getYLabel(String Text)
{
String Result="";
for (int i=0;i<Text.length();i++) Result+=Text.charAt(i)+(i<Text.length()-1?"\u2009":"");
// Out(Result);
return Result;
}
private static void out(String message) { System.out.print(message); }
private static void Out(String message) { System.out.println(message); }
// Create the GUI and show it. For thread safety, this method should be invoked from the event-dispatching thread.
static void Create_And_Show_GUI()
{
final PriceVolume_Chart demo=new PriceVolume_Chart("ADS");
JFrame frame=new JFrame("PriceVolume_Chart Frame");
frame.add(demo);
frame.addWindowListener( new WindowAdapter()
{
public void windowActivated(WindowEvent e) { }
public void windowClosed(WindowEvent e) { }
public void windowClosing(WindowEvent e) { System.exit(0); }
public void windowDeactivated(WindowEvent e) { }
public void windowDeiconified(WindowEvent e) { demo.repaint(); }
public void windowGainedFocus(WindowEvent e) { demo.repaint(); }
public void windowIconified(WindowEvent e) { }
public void windowLostFocus(WindowEvent e) { }
public void windowOpening(WindowEvent e) { demo.repaint(); }
public void windowOpened(WindowEvent e) { }
public void windowResized(WindowEvent e) { demo.repaint(); }
public void windowStateChanged(WindowEvent e) { demo.repaint(); }
});
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args)
{
// Schedule a job for the event-dispatching thread : creating and showing this application's GUI.
SwingUtilities.invokeLater(new Runnable() { public void run() { Create_And_Show_GUI(); } });
}
}
=====================================================================
With the help of #trashgod I was able to add the cross-hair to the chart. However I still want to know how to achieve the following :
[1] The value shown for volume when cross-hair moves, is in-correct, how to show correct volume value ?
[2] How to have solid non-transparent background for the dynamic values shown near the axis ? Now it's transparent, hard to read.
[3] How to set the cross-hair at startup on a certain [ e.g. 20th ] date ?
[4] How to show red color for volumes when price drops ?
Here is an image of what I'm looking for :

Show cross lines when my mouse points to different locations on the image?
You were able to adapt the approach shown in CrosshairOverlayDemo1, suggested here.
The value shown for volume when cross-hair moves, is incorrect, how to show correct volume value?
Your implementation of ChartMouseListener accesses the x and y values from the price dataset. The x value is the date; the y value is the price. Absent formatting, the crosshair correctly displays the number of milliseconds from the epoch. You probably want to fetch the corresponding volume.
How to have solid non-transparent background for the dynamic values shown near the axis? Now it's transparent, hard to read.
You can modify the crosshair's appearance as shown here. For the background, use an opaque color or specify an alpha value of 255 to construct a solid color.
How to set the cross-hair at startup on a certain e.g. 20th date?
You can use Robot, as shown here, but I prefer not to surprise the user in this way.
How to show red color for volumes when price drops?
You can override getItemPaint(), as shown here here to change the rendered color as a function of the volume.
In general, you'll find it easier to examine such issues in isolated examples that can be integrated into your code, as you did with the crosshair overlay demo.

Related

Map does not fire Pointer Pressed/Release events

I have a MapContainer instance that does not respond to attached PointerRelease listeners. I'm trying to detect when the map has stopped moving once it's been dragged by the user. In the code below the first two listeners do not produce any output. Third/Fourth do.
MapContainer mc = new MapContainer("mykey");
mc.addPointerPressedListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent evt) {
System.out.println("Don't see this");
}
});
mc.addPointerReleasedListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent evt) {
System.out.println("Don't see this either");
}
});
mc.addTapListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent evt) {
System.out.println("See This :-)");
}
});
mc.addMapListener(new MapListener() {
#Override
public void mapPositionUpdated(Component source, int zoom, Coord centerCoord) {
System.out.println("See this too!");
}
});
addTapListener is correct and implemented in MapContainer. The low level pointer events won't work properly for that container because the map logic is implemented natively and thus these events are consumed.
You might be able to track them by binding a listener to the parent form but that's problematic as events/gestures might have platform specific interpretations.

How can I draw this kind of Jfreechart

I am using Dynamicreports4.1.1, whose chart based on Jfreechart.
I want to draw chart like image below:
It somewhat like Candlestickchart or stackedchart or layeredchart.
I choose the stackedchart to customize, it like this:
The code:
public class StackedBarChartReportTest {
public StackedBarChartReportTest() {
build();
}
private void build() {
FontBuilder boldFont = stl.fontArialBold().setFontSize(12);
TextColumnBuilder<String> itemColumn = col.column("Item", "item", type.stringType());
TextColumnBuilder<BigDecimal> bestColumn = col.column("Best", "best", type.bigDecimalType());
TextColumnBuilder<BigDecimal> worstColumn = col.column("Worst", "worst", type.bigDecimalType());
try {
report()
.setTemplate(Templates.reportTemplate)
.columns(itemColumn, bestColumn, worstColumn)
.title(Templates.createTitleComponent("StackedBarChart"))
.summary(
cht.stackedBarChart()
.customizers(new ChartCustomizer())
.setTitle("Stacked bar chart")
.setTitleFont(boldFont)
.setCategory(itemColumn)
.setShowValues(true)
.series(
cht.serie(bestColumn), cht.serie(worstColumn))
.setCategoryAxisFormat(
cht.axisFormat().setLabel("Item")))
.pageFooter(Templates.footerComponent)
.setDataSource(createDataSource())
.show();
} catch (DRException e) {
e.printStackTrace();
}
}
private class ChartCustomizer implements DRIChartCustomizer, Serializable {
private static final long serialVersionUID = 1L;
#Override
public void customize(JFreeChart chart, ReportParameters reportParameters) {
BarRenderer renderer = (BarRenderer) chart.getCategoryPlot().getRenderer();
// here customize,but have no idea...
}
}
private JRDataSource createDataSource() {
DRDataSource dataSource = new DRDataSource("item", "best", "worst");
dataSource.add("1Y", new BigDecimal(-10), new BigDecimal(-14.5));
dataSource.add("2Y", new BigDecimal(10), new BigDecimal(4));
dataSource.add("3Y", new BigDecimal(12), new BigDecimal(-2));
return dataSource;
}
public static void main(String[] args) {
new StackedBarChartReportTest();
}
Have no idea how to start or Is there a chart like this?

JavaFX 8: Checkbox in TableView and Add-on into to selected Item?

I would like to create a payroll program such that when the user tick a CheckBox in TableView, the name (String) will be carried on to the panel on the right and with TextFields to enter more info such as this:
I tried to follow the MVC hierarchy thus far as I code:
PayrollMainApp.java
public class PayrollMainApp extends Application {
private Stage primaryStage;
private BorderPane rootLayout;
private ObservableList<Employee> selectEmployeeTable = FXCollections.observableArrayList();
public PayrollMainApp(){
selectEmployeeTable.add(new Employee(false,"Hans Muster"));
selectEmployeeTable.add(new Employee(true,"Ruth Mueller"));
selectEmployeeTable.add(new Employee(false,"Heinz Kurz"));
selectEmployeeTable.add(new Employee(false,"Cornelia Meier"));
selectEmployeeTable.add(new Employee(false,"Werner Meyer"));
selectEmployeeTable.add(new Employee(false,"Lydia Kunz"));
selectEmployeeTable.add(new Employee(false,"Anna Best"));
selectEmployeeTable.add(new Employee(false,"Stefan Meier"));
selectEmployeeTable.add(new Employee(false,"Martin Mueller"));
}
public ObservableList<Employee> getSelectEmployeeTable(){
return selectEmployeeTable;
}
#Override
public void start(Stage primaryStage) {
this.primaryStage = primaryStage;
this.primaryStage.setTitle("PayrollApp");
initRootLayout();
showEmployeeOverview();
}
/**
* Initializes the root layout.
*/
public void initRootLayout() {
try {
// Load root layout from fxml file.
FXMLLoader loader = new FXMLLoader();
loader.setLocation(PayrollMainApp.class.getResource("view/RootLayout.fxml"));
rootLayout = (BorderPane) loader.load();
// Show the scene containing the root layout.
Scene scene = new Scene(rootLayout);
primaryStage.setScene(scene);
primaryStage.show();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* Shows the person overview inside the root layout.
*/
public void showEmployeeOverview() {
try {
// Load person overview.
FXMLLoader loader = new FXMLLoader();
loader.setLocation(PayrollMainApp.class.getResource("view/EmployeeOverview.fxml"));
AnchorPane personOverview = (AnchorPane) loader.load();
// Set person overview into the center of root layout.
rootLayout.setCenter(personOverview);
// Give the controller access to the main app
EmployeeOverviewController controller = loader.getController();
controller.setMainApp(this);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* Returns the main stage.
* #return
*/
public Stage getPrimaryStage() {
return primaryStage;
}
public static void main(String[] args) {
launch(args);
}
}
Employee.java
public class Employee {
private BooleanProperty checkedBox = new SimpleBooleanProperty(false);
private StringProperty employeeName = new SimpleStringProperty();
public Employee(){
super();
}
public Employee(boolean checkedBox, String employeeName){
this.checkedBox = new SimpleBooleanProperty(false);
this.employeeName = new SimpleStringProperty(employeeName);
}
public BooleanProperty checkedBoxProperty(){
return this.checkedBox;
}
public StringProperty employeeNameProperty(){
return this.employeeName;
}
public java.lang.Boolean getSelectBox() {
return this.checkedBoxProperty().get();
}
public StringProperty getEmployeeName() {
return employeeName;
}
public void setSelectBox(final java.lang.Boolean checkedBox){
this.checkedBoxProperty().set(checkedBox);
}
public void setEmployeeName(StringProperty employeeName) {
this.employeeName = employeeName;
}
}
EmployeeOverviewController.java
public class EmployeeOverviewController {
#FXML
private TableView<Employee> selectEmployeeTable;
#FXML
private TableColumn<Employee, String> employeeNameColumn;
#FXML
private TableColumn<Employee, Boolean> checkBoxColumn;
private PayrollMainApp mainApp;
public EmployeeOverviewController() {
}
#FXML
public void initialize() {
checkBoxColumn.setCellValueFactory(cellData -> cellData.getValue().checkedBoxProperty());
checkBoxColumn.setCellFactory(param -> new CheckBoxTableCell<Employee, Boolean>());
employeeNameColumn.setCellValueFactory(cellData -> cellData.getValue().employeeNameProperty());
}
public void setMainApp(PayrollMainApp mainApp){
this.mainApp = mainApp;
//Add observable list data to the table
selectEmployeeTable.setItems(mainApp.getSelectEmployeeTable());
}
}
And a util class to make the checkBox visible in the table:
SelectBoxCellFactory.java
public class SelectBoxCellFactory implements Callback {
#Override
public TableCell call(Object param) {
CheckBoxTableCell<Employee,Boolean> checkBoxCell = new CheckBoxTableCell();
return checkBoxCell;
}
}
Here is my output thus far:
I know this has a table in it as compared to the previous output. Honestly I'm still indecisive as to use which, because I think using TextFields would make it look better. But all I hope for now is that this design is not impossible to code...
I really hope you can help me... Thank you for your help in advance.
It's probably easiest to use a TableView for the right panel. You can create a FilteredList from your original list:
FilteredList<Employee> selectedEmployees
= new FilteredList<>(selectEmployeeTable, Employee::getSelectBox);
and then use that for your second table.
If you prefer to use text fields (in what looks like a GridPane?) you can still use the filtered list above, but you will need to register a listener with it and update the layout "by hand" when items are added and removed.

How I can stop an animated GIF in JavaFX?

I want to use an animated GIF in my project, but I dont know how I can stop the loop animation. I mean, I want the GIF to play 1 time only.
Thanks!
I haven't done GIF animation, wasn't even aware that JavaFX would have methods for starting and stopping them. If you wish to do ANY animation using images, I rather suggest you do it frame by frame yourself. This way you have full control over it and you can have more than just 256 colors in your image.
I read a very good article about Creating a Sprite Animation with JavaFX in Mike's blog.
It's very easy to do. You simply extend the Transition class, add an ImageView to it and implement the Transition's Interpolate method.
Edit: oh, and by the way, GIFs have a loop flag which tells them to either play in a loop or not to play in a loop. In other words: In theory you could modify the GIF file's loop property. In theory only, because I just tried with specifying to play only once and in JavaFX it still played in an endless loop while in FireFox it played once. By the way, JavaFX doesn't seem to support animated PNGs (APNG) which would support more than 256 colors. So the automatic image animation capabilities are very limited. Best to do the animation by yourself.
I hope someone comes up with something better, but here's an example code about how you could get full control over your gif.
import java.awt.image.BufferedImage;
import java.net.URISyntaxException;
import javafx.animation.Interpolator;
import javafx.animation.Transition;
import javafx.application.Application;
import javafx.embed.swing.SwingFXUtils;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.image.WritableImage;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
import javafx.util.Duration;
/**
* Requires GifDecoder from here: http://www.java2s.com/Code/Java/2D-Graphics-GUI/DecodesaGIFfileintooneormoreframes.htm
*/
public class AnimatedGifDemo extends Application {
#Override
public void start(Stage primaryStage) throws URISyntaxException {
HBox root = new HBox();
// TODO: provide gif file, ie exchange banana.gif with your file
Animation ani = new AnimatedGif(getClass().getResource("banana.gif").toExternalForm(), 1000);
ani.setCycleCount(10);
ani.play();
Button btPause = new Button( "Pause");
btPause.setOnAction( e -> ani.pause());
Button btResume = new Button( "Resume");
btResume.setOnAction( e -> ani.play());
root.getChildren().addAll( ani.getView(), btPause, btResume);
Scene scene = new Scene(root, 1600, 900);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
public class AnimatedGif extends Animation {
public AnimatedGif( String filename, double durationMs) {
GifDecoder d = new GifDecoder();
d.read( filename);
Image[] sequence = new Image[ d.getFrameCount()];
for( int i=0; i < d.getFrameCount(); i++) {
WritableImage wimg = null;
BufferedImage bimg = d.getFrame(i);
sequence[i] = SwingFXUtils.toFXImage( bimg, wimg);
}
super.init( sequence, durationMs);
}
}
public class Animation extends Transition {
private ImageView imageView;
private int count;
private int lastIndex;
private Image[] sequence;
private Animation() {
}
public Animation( Image[] sequence, double durationMs) {
init( sequence, durationMs);
}
private void init( Image[] sequence, double durationMs) {
this.imageView = new ImageView(sequence[0]);
this.sequence = sequence;
this.count = sequence.length;
setCycleCount(1);
setCycleDuration(Duration.millis(durationMs));
setInterpolator(Interpolator.LINEAR);
}
protected void interpolate(double k) {
final int index = Math.min((int) Math.floor(k * count), count - 1);
if (index != lastIndex) {
imageView.setImage(sequence[index]);
lastIndex = index;
}
}
public ImageView getView() {
return imageView;
}
}
}
It provides a pause/resume button for testing. What you need in addition is the Gif Decoder code and an animated banana.gif.

Many objects with global and local state

I'm looking for the best Design for the following situation.
We have many objects form one class, for instance a picture frame. Now each of the picture frames can display 3 types of picture. 1) a face 2) a screenshot 3) empty
Thats easy:
public enum PictureMode
{
Face,
Screen,
None
}
public class PictureFrame {
private PictureMode mode;
public PictureMode Mode
{
get { retrun mode; }
set { /* set currentPicture to the correct one */ }
}
private Image currentPicture;
private Image face;
private Image screen;
private Image empty;
public PictureFrame(Image face, Image screen) {
this.face = face;
this.screen = screen;
mode = PictureMode.None; // Maybe this is our default.
}
}
We can now create some PictureFrames with different pictures and easily change the mode for each one.
Now I want to add a global setter for all PictureFrames. Then each new PictureFrame should take the global setting as the default one. It can later be set to an different through.
Here is my solution, but I want to discuss if there is a better one.
I added a static field PictureFrame.Instances to the PictureFrame class where all PictureFrames are reachable. Now I can iterate over all the PictureFrames to apply the new global mode to all frames.
In addition I have a second static field PictureFrame.GlobalImageMode where I set the global mode if I change it on all Frames and read it in the Constructor of the PictureFrame. The setter for the GlobalImageMode can be static in the PictureFrame class, too.
Just wild shot here...: Why don't you always use getter for current frame mode with a condition in it:
class PictureFrame {
private PictureMode instanceMode;
private static PictureMode? globalMode;
private PictureMode CurrentMode {
get {
return globalMode ?? instanceMode;
}
}
}
If I understand the problem statement correctly, I think this is similar to what you need:
public class Face extends Image { }
public class Screen extends Image { }
public class PictureFrame {
private Image picture = null;
public PictureFrame(Image newPicture) {
this.setPicture(newPicture);
}
public setPicture(Image newPicture) {
this.picture = newPicture;
}
}
public class PictureFactory {
private static Image defaultPicture = null;
public static void setDefaultPicture(Image newPicture) {
PictureFactory.defaultPicture = newPicture;
}
public static Image getDefaultPicture() {
return PictureFactory.defaultPicture;
}
public static PictureFrame getNewPictureFrame() {
return new PictureFrame(PictureFactory.defaultPicture);
}
}
public class PictureFrameManager {
private static PictureManager INSTANCE = new PictureManager();
private Vector<PictureFrame> frames = new Vector<PictureFrame>();
public static PictureFrameManager getInstance() {
return PictureManager.INSTANCE;
}
private PictureFrameManager() {}
private void addPictureFrame(PictureFrame frame) {
this.frames.add(frame);
}
private void setFramesToDefault() {
Image defaultPicture = PictureFactory.getDefaultPicture();
Enumeration<PictureFrame> iFrames = frames.elements();
while(iFrames.hasMoreElements()) {
iFrames.nextElement().setPicture(defaultPicture);
}
}
}
You use it via:
Face face = new Face();
//...do something to load the face object here
PictureFactory.setDefaultPicture(face);
PictureFrame frame = PictureFactory.getNewPictureFrame();
PictureFrameManager manager = PictureFrameManager.getInstance();
manager.addPictureFrame(frame);
Screen screen = new Screen();
//...do something to load the screen object here
PictureFactory.setDefaultPicture(screen);
manager.setFramesToDefault();
Alternately, if you don't want to extend Image or you want to have multiple modes, you could create a decorator object to wrap the image in and say what mode it is.

Resources