Sunday 17 March 2013

[JAVA] Create a Simple Binary Clock

Hello Little Programmer
Today i'll show you , How to creating a Binary Clock :D
Lets Begin

Table of Content: 
1.What is a Binary Clock and How To read it?
2.Getting started
3.Creating JFrame and other needed things

4.Creating the "Clock"-Class
5.Painting the white ovals on the Canvas
6.Painting the Time on the Canvas
7.Decimal to Binary Algorithm
8.Optimizing the Clock
9.The End

What is a Binary Clock and How To read it?
Read This http://en.wikipedia....ki/Binary_clock

Getting Started
The binary clock we will create in this tutorial will show the time in the classic-binary clock layout. It will show seconds, minutes and hours. After finishing the tutorial, it will probably look like this:




But enough of the talking, lets code!

Creating JFrame and other needed things
First, we need the main Method. So lets create a Class for it. I'll call it "Pannel":

public class Pannel {
 public static void main(String[] args){
  // Do something...
 }
}


Now that we have our Main-Method, we're going to break out of the "static"-context by creating a new Class which will create our JFrame and Canvas. I'll call this Class "PannelContent", the Method contentPacker will create our GUI:

import java.awt.BorderLayout;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;

import javax.swing.JFrame;

public class PannelContent {
 private JFrame f;

 public PannelContent(){
  this.contentPacker();
 }
 
 public void contentPacker(){
  f = new JFrame("Binary Clock");
  f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
  f.setResizable(false);
  f.addWindowListener(new WindowAdapter() {
   public void windowClosed(WindowEvent w){
    System.exit(0);
   }
  });
  f.setSize(334, 176);
  f.setVisible(true);
 }

}


Now we have a Class "Pannel", containig our main-Method, a class called "PannelContent" containig a Method contentPacker which creats our GUI. But wait, what about the Canvas?

Creating the "Clock"-Class 
We're not realy going to use a Canvas, but wehre going to extend the Canvas-Class with our new "Clock"-Class. This looks something like this:

public class Clock extends Canvas{
 
 public void paint(final Graphics g){
  // Paint here!
 }

}

As you might have noticed, we extended the "Canvas"-Class and now we can use the paint()-Method, which will actually paint on our JFrame.

To do that, we need to add the new "Clock"-Class as an element to our GUI:

import java.awt.BorderLayout;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;

import javax.swing.JFrame;

public class PannelContent {
 private JFrame f;
 // Our Class "Clock":
 private Clock clock;

 public PannelContent(){
  this.contentPacker();
 }
 
 public void contentPacker() throws InterruptedException{
  f = new JFrame("Binary Clock");
  f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
  f.setResizable(false);
  f.addWindowListener(new WindowAdapter() {
   public void windowClosed(WindowEvent w){
    System.exit(0);
   }
  });
  {
   // Adding our "Clock"-Class to the JFrame:
   clock = new Clock();
   f.getContentPane().add(clock, BorderLayout.CENTER);
  }
  f.setSize(334, 176);
  f.setVisible(true);
 }

}

So now we have our Canvas-field to draw on. To show the GUI, you need to call the "PannelContent"-Class in the "Pannel"-Class. The "Pannel"-Class should look like this:
public class Pannel {
 public static void main(String[] args){
  new PannelContent();
 }
}

If you're able to show the Frame (which will be white at all) without any Errors or something, we can now go on to Draw the white Ovals on our Canvas.

Painting the white ovals on the Canvas
So now that we have everything working till here, we can go and paint something on the Canvas.

Note: In this Tutorial, we're going to use a JFrame with the size of 334x176 Pixel. The painted Clock will look just nice on this size, but if you want to change the size of the Frame, you must also change the size of the Clock-Elements!

When you paint on an Canvas, everything has an X- and Y-coordinate. The point (0|0) is the upper left corner of the Canvas, while the point (334|176) is the lower right corner of our Canvas. We're now going to create three 3D-Arrays with the X- and Y-coordinates of the single Clock-Elements. We don't need to change them later, so they'll be final. Create them for the whole class, like this:

private final int[][][] time_sec = {
  {//   x    y
   {250, 100},
   {250, 70},
   {250, 40},
   {250, 10}
  },{
   {220, 100},
   {220, 70},
   {220, 40}
  }
};
private final int[][][] time_min = {
  {
   {170, 100},
   {170, 70},
   {170, 40},
   {170, 10}
  },{
   {140, 100},
   {140, 70},
   {140, 40}
  }
};
// Hours
private final int[][][] time_std = {
  {
   {90, 100},
   {90, 70},
   {90, 40},
   {90, 10}
  },{
   {60, 100},
   {60, 70}
  }
};

public Clock(){
 // This is the Constructor...
}



Okay, now that we saved our Positions, we can paint the white ovals. For this, we'll create a new Method called drawFields(). The Method will paint the ovals in the Graphics object which we'll get from our paint()-Method. It takes the X- and Y-coordinates from our arrays:


public boolean drawFields(Graphics g){
 // Set the Color to White:
 g.setColor(Color.WHITE);
 // Paint the Fields for the Seconds:
 for (int a = 0; a < 2; a++){
  for (int i = 0; i < time_sec[1].length; i++){
   //      x-Coorodinate y-Coordinate
   g.drawOval(time_sec[a][i][0], time_sec[a][i][1], 20, 20);
  }
 }
 g.drawOval(time_sec[0][3][0], time_sec[0][3][1], 20, 20);
 // Paint the Fields for the Minutes:
 for (int a = 0; a < 2; a++){
  for (int i = 0; i < time_min[1].length; i++){
   //      x-Coorodinate y-Coordinate
   g.drawOval(time_min[a][i][0], time_min[a][i][1], 20, 20);
  }
 }
 g.drawOval(time_min[0][3][0], time_min[0][3][1], 20, 20);
 // Paint the Fields for the Hours:
 for (int a = 0; a < 2; a++){
  for (int i = 0; i < time_std[1].length; i++){
   //      x-Coorodinate y-Coordinate
   g.drawOval(time_std[a][i][0], time_std[a][i][1], 20, 20);
  }
 }
 g.drawOval(time_std[0][2][0], time_std[0][2][1], 20, 20);
 g.drawOval(time_std[0][3][0], time_std[0][3][1], 20, 20);
 return true;
}

You might have noticed that the method always paints one Oval after the Loops. Thats because our Array doesn't has 4 fields on both sites. E.g. the right side of the minute pointer needs to be able to point numbers up to 9. The right side just needs numbers up to 5. So we paint the 3 Elements on every side and the single Element from the right side alone.

Now we edit our paint()-Method to paint the white ovals:

public void paint(final Graphics g){
 // Paint the Super-Class:
 super.paint(g);
 // set Background Color to Black:
 this.setBackground(Color.BLACK);
 // Call the Method to draw the white ovals:
 this.drawFields(g);
}

If you start the Application now, it should show the white ovals on a black field. Not more, not less

So now that we have the basic Layout, we can go and take care of the red ovals, which will show the current time. I'll call these ovals "lights".

Painting the Time on the Canvas
We first need the current Seconds. To get those, we'll create a Date-Variable for the whole Class. We'll set it in our Constructor and refresh it later in the paint()-Method:

// The Variable containig the Time:
private Date zeit;
// "Zeit" means "Time" in German ;)/>

public Clock(){
 zeit = new Date();
}

Note: I called the Variable "zeit", you can shure call it like you want!

Now, we can retrieve the current seconds by using the getSeconds()-Method of the Date-Class (which is deprecated, i know). This will give us an int-Variable with the current seconds as a decimal number. But we need it as a binary number.

We also want every paragraph of the number separated. To do this, we're going to use a trick. All the necessary steps will be in one Method, i'll call it finishSec():

@SuppressWarnings("deprecation")
public boolean finishSec(Graphics g){
 // Seperate the paragraphs:
 int sek[] = {
   ( zeit.getSeconds() - ((zeit.getSeconds() / 10) *10)),
   ( zeit.getSeconds() / 10)
 };
 for (int i = 0; i < 2; i++){
  int rechner = sek[i];
  // Convert the single paragraph to a binary number:
  int indexArray = 0;
  while (rechner > 0){
   if (rechner % 2 == 1){
    // If the "light" is On, paint it:
    g.setColor(Color.RED);
    g.fillOval( (time_sec[i][indexArray][0] +1), (time_sec[i][indexArray][1] +1), 18, 18);
   }
   rechner = rechner / 2;
   indexArray++;
  }
 }
 // return to the paint()-Method:
 return true;
}

Decimal to Binary Algorithm 
The trick to split the numbers works like this:

Lets say the Number is 58:
58 / 10 = 5.8 // But the Type "int" doesn't store the 0.8 ...
= 5 // so its 5 now.
5 * 10 = 50 // We multiply it with 10..
58 - 50 = 8 // and subtract with 58 to get the 8 as a Single Number
-------------------------
58 / 10 = 5.8 // Getting the 5 as a Single Number...
= 5 // like shown above.

Than we got the two single Numbers. We convert both of them to a binary number. This works like that:

Lets say we want to convert the 5 from above:
5 % 2 = 1 // The Module-Operator gives back the rest of the calculation...
5 - 2 = 3
- 2 = 1 // ... which is 1
-----------------
If it's 1, we paint the "light", if not, we don't paint anything.
After that we continue this until we have 0.

So now we got the Algorithm to calculate the binary numbers for the secods. It paints the "lights" and the returns to the paint()-Method which then calls the Method finishMin() and finishStd() which are pretty much the same for the minutes e.g. hours.

The updated paint()-Method should now look like this:

public void paint(final Graphics g){
 // Paint the Super-Class:
 super.paint(g);
 // set Background Color to Black:
 this.setBackground(Color.BLACK);
 // Call the Method to draw the white ovals:
 this.drawFields(g);
 // Draw the "lights":
 this.finishSec(g);
 this.finishMin(g);
 this.finishStd(g);
}

Now we need to edit the "PannelContent"-Class we created to repaint the Clock. This will be done at the end of the contentPacker()-Method:

while (true){
 clock.repaint();
}

The Method repaint() will call the update()-Method (see below), which then calls the paint()-Method.

The Clock should basicly work now.

Optimizing the Clock
Now, it's almost done. But there are two things that should be changed: First, we'll use a Doublebuffer for the drawing to eliminate the flickering. This will draw the whole Graphics Object in the Memory and than paint it on the Canvas.

To do that, we just need to add two Objects and the update()-Method to our "Clock"-Class:

public void update(Graphics g){
 if (dbimage == null){
  // Create the Image in the Memory:
  dbimage = createImage(this.getSize().width, this.getSize().height);
  // Create a "Graphics"-Object from it:
  dbg = dbimage.getGraphics();
 }
 dbg.setColor(getBackground());
 // Give the it to the paint() method to paint on it:
 paint(dbg);
 // Now draw it on the Canvas:
 g.drawImage(dbimage, 0, 0, this);
}

Now we need the two Variables Image dbimage and Graphics dbg for the whole class:
private Image dbimage;
private Graphics dbg;

public Clock(){
 // Constructor:
 zeit = new Date();
}

That's it. The flickering should be gone.

Now that you're proudly looking at what you've created, I ask you to take a look at your System-Monitor. The CPU will be at nearly 100%. Why? Because we repaint the Clock more then 100 times per second. Is this necessary? No!

So we're going to add a delay of 200 milliseconds before refreshing the Clock again. This are 5 repaintings in one second and your CPU will be Happy

For the Delay we'll use the Thread.sleep()-Method in our while-Loop in the "PannelContent"-Class:

while (true){
 clock.repaint();
 // wait 200 milliseconds 
 Thread.sleep(200);
}

The sleep()-Method throws an InterruptedException which we'll need to throw back to our Constructor and then catch it. This might look like this:

public PannelContent(){
 try {
  this.contentPacker();
 } catch (InterruptedException e) {
  // Catch the Exeption:
  e.printStackTrace();
 }
}

public void contentPacker() throws InterruptedException{
 // ... GUI Stuff ...
 while (true){
  clock.repaint();
  // wait 200 milliseconds
  Thread.sleep(200);
 }
}

The End
So that's it. We're finish. Enjoy your new binary clock.

The whole Eclipse-Project (commented in German) is attached to this post!

At this point I want to say, if you have any proposals how to make it better, don't hesitate to post them! So, thank you for reading this and Sorry for my English

0 comments:

Post a Comment