How I can stop an animated GIF in JavaFX? - loops

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.

Related

How to show cross lines and red color for price drop in 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.

MapContainer show only on click/touch

I am trying to use naive map.
I have places a MapContainer insider a Form Component but the From shows blank without the map inside it, on the simulator. When I click on the viewport the map shows as long as the mouse button remains down. When I release it disappear again.
Is it a real problem or is it a misfunction of the simulator? If it is a real problem what am I doing wrong?
below is the class I am using:
package com.mainsys.zappeion;
import com.codename1.googlemaps.MapContainer;
import com.codename1.maps.Coord;
import com.codename1.ui.Form;
import com.codename1.ui.layouts.BorderLayout;
/**
*
* #author Christoforos
*/
public class ZappeionMap extends com.codename1.ui.Form {
private Form current;
public ZappeionMap() {
super("Ζάππειον", new BorderLayout());
}
#Override
public void show() {
if(current != null){
current.show();
return;
}
final MapContainer cnt = new MapContainer();
this.addComponent(BorderLayout.CENTER, cnt);
cnt.setCameraPosition(new Coord(41.889, -87.622));
super.show();
}
}
/********** Implementing Shai's answer ******************/
I changed my code to what Shai suggested show my class now is:
package com.mainsys.zappeion;
import com.codename1.googlemaps.MapContainer;
import com.codename1.location.Location;
import com.codename1.location.LocationManager;
import com.codename1.maps.Coord;
import com.codename1.ui.BrowserComponent;
import com.codename1.ui.FontImage;
import com.codename1.ui.Form;
import com.codename1.ui.layouts.BorderLayout;
import com.codename1.ui.plaf.Style;
/**
*
* #author Christoforos
*/
public class ZappeionMap extends com.codename1.ui.Form {
private Form current;
private static final String HTML_API_KEY = "AIzaSyDHlFJK561bQVs0AyBm1M5xWS_YCHNuPfc";
public ZappeionMap() {
super("Ζάππειον", new BorderLayout());
final MapContainer cnt = new MapContainer( HTML_API_KEY );
this.addComponent(BorderLayout.CENTER, cnt);
cnt.setCameraPosition(new Coord(41.889, -87.622));
}
}
It still having the same problem. The screen is blank. The map only shown when I click on the screen.
I also noticed something else. On the debuger I get the message:
WARNING: Apple will no longer accept http URL connections from applications you tried to connect to http://tile.openstreetmap.org/4/2/9.png to learn more check out https://www.codenameone.com/blog/ios-http-urls.html
Why is it trying to connect to http://tile.openstreetmao.org. It is supposed to work with google maps not with openstreet maps.
One more information, maybe it is worh something. I test it on real device. The screen is still blink but when I touch the screen it does not show anythink, in contrast with the simulater that when I click on the screen the map appears.
I am using netbeans 8.2 on centos 7
Any help is appreciated.
Thank you Christoforos.
Why are you overriding the show method of form and don't construct the UI n the constructor?
It looks like you copied some code from the lifecycle class and mixed it with a form subclass e.g. the current variable.
This is closer to correct:
public class ZappeionMap extends com.codename1.ui.Form {
private Form current;
public ZappeionMap() {
super("Ζάππειον", new BorderLayout());
final MapContainer cnt = new MapContainer();
this.addComponent(BorderLayout.CENTER, cnt);
cnt.setCameraPosition(new Coord(41.889, -87.622));
}
}

Codename One: 2d Drawing app

I would like to know, where do I initialise my DrawingCanvas class (referred in 2d drawing app) using the GUI Builder (old one).
Also, how would I change the stroke color for each button that refers to the GUI Builder?
package userclasses;
import com.codename1.ui.Component;
import com.codename1.ui.Form;
import com.codename1.ui.events.ActionEvent;
import generated.StateMachineBase;
import com.codename1.ui.util.Resources;
/**
*
* #author Your name here
*/
public class StateMachine extends StateMachineBase{
public StateMachine(String resFile) {
super(resFile);
// do not modify, write code in initVars and initialize class members there,
// the constructor might be invoked too late due to race conditions that might occur
}
/**
* this method should be used to initialize variables instead of
* the constructor/class scope to avoid race conditions
*/
#Override
protected void initVars(Resources res) {
}
}
The source for that class is in the graphics section of the Codename One developer guide, pasted below:
The center of the app is the DrawingCanvas class, which extends Component.
public class DrawingCanvas extends Component {
GeneralPath p = new GeneralPath();
int strokeColor = 0x0000ff;
int strokeWidth = 10;
public void addPoint(float x, float y){
// To be written
}
#Override
protected void paintBackground(Graphics g) {
super.paintBackground(g);
Stroke stroke = new Stroke(
strokeWidth,
Stroke.CAP_BUTT,
Stroke.JOIN_ROUND, 1f
);
g.setColor(strokeColor);
// Draw the shape
g.drawShape(p, stroke);
}
#Override
public void pointerPressed(int x, int y) {
addPoint(x-getParent().getAbsoluteX(), y-getParent().getAbsoluteY());
}
}
Notice that this is the base skeleton and more code is added later in that chapter...

How to temporarily remove items from a ChoiceBox (or maybe a ComboBox)?

I've come across a somewhat perplexing conundrum. I've recently started to figure out how to work with listeners, thanks to some people on here, and I'm trying to find a way to use them in conjunction with choice/comboboxes to do something tricky. What I want to have happen is, when the user makes a selection from one of six linked boxes containing six choices, it removes that option from the other 5 boxes until either A: the option is changed to one of the remaining ones, or B: the option is changed to a null or default setting (to prevent getting "locked in" after picking, or maybe I can just make a reset button for that purpose). I've got a ChangeListener on each choicebox now, but various things I've tried (switch statements, assigning each answer a boolean, various attempts to use .getItems().remove() in vain, I've been at this a while) Has anyone figured or seen an example of how this could be done? Thanks in advance for any advice, you guys(and gals) have helped me learn by leaps and bounds these past few weeks.
if you want something like this:
I had this code in my program. it is nor really efficient, but was fine for me on a small set of data.
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.control.ChoiceBox;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
public class ConnectedComboBox<T> implements ChangeListener<T> {
private ObservableList<T> items;
private List<ChoiceBox<T>> comboBoxList = new ArrayList<>();
public ConnectedComboBox(ObservableList<T> items){
this.items = items;
if (this.items == null) this.items = FXCollections.observableArrayList();
}
public void addComboBox(ChoiceBox<T> comboBox){
comboBoxList.add(comboBox);
comboBox.valueProperty().addListener(this);
updateSelection();
}
public void removeComboBox(ChoiceBox<T> comboBox){
comboBoxList.remove(comboBox);
comboBox.valueProperty().removeListener(this);
updateSelection();
}
// this boolean needed because we can set combobox Value in updateSelection()
// this will trigger a value listener and update selection one more time => stack overflow
// this behavior occurs only if we have more than one equal item in source ObservableList<T> items list.
private boolean updating = false;
private void updateSelection() {
if (updating) return;
updating = true;
List<T> availableChoices = items.stream().collect(Collectors.toList());
for (ChoiceBox<T> comboBox: comboBoxList){
if (comboBox.getValue()!= null) {
availableChoices.remove(comboBox.getValue());
}
}
for (ChoiceBox<T> comboBox: comboBoxList){
T selectedValue = comboBox.getValue();
ObservableList<T> items = comboBox.getItems();
items.setAll(availableChoices);
if (selectedValue != null) {
items.add(selectedValue);
comboBox.setValue(selectedValue);
}
}
updating = false;
}
#Override
public void changed(ObservableValue<? extends T> observable, T oldValue, T newValue) {
updateSelection();
}
}
And here is how you use it:
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.ChoiceBox;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
public class MainFX extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
HBox root = new HBox();
root.setSpacing(10);
ObservableList<String> values = FXCollections.observableArrayList("One", "Two", "Three", "Four","Five");
ChoiceBox<String> combo1 = new ChoiceBox<>();
combo1.setPrefWidth(100);
ChoiceBox<String> combo2 = new ChoiceBox<>();
combo2.setPrefWidth(100);
ChoiceBox<String> combo3 = new ChoiceBox<>();
combo3.setPrefWidth(100);
root.getChildren().addAll(combo1,combo2,combo3);
ConnectedComboBox<String> connectedComboBox = new ConnectedComboBox<>(values);
connectedComboBox.addComboBox(combo1);
connectedComboBox.addComboBox(combo2);
connectedComboBox.addComboBox(combo3);
primaryStage.setScene(new Scene(root,600,600));
primaryStage.show();
}
public static void main(String[] args){
launch(args);
}
}

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