Extending Swing: Creating Your Own Components

Extending Swing: Creating Your Own Components Elie Levy, IT Consultant TS-4982 Learn a process you can follow to create any GUI component using Swin...
Author: Mark Lester
0 downloads 0 Views 571KB Size
Extending Swing: Creating Your Own Components Elie Levy, IT Consultant TS-4982

Learn a process you can follow to create any GUI component using Swing in one hour!

2008 JavaOneSM Conference | java.sun.com/javaone |

2

“Example isn't another way to teach, it is the only way to teach”

Albert Einstein

2008 JavaOneSM Conference | java.sun.com/javaone |

3

Agenda Recipe for creating custom components Apply the Recipe: Case 1 - Composing Existing Components Apply the Recipe: Case 2 - Start from Scratch

2008 JavaOneSM Conference | java.sun.com/javaone |

4

Recipe for Creating a Custom Component Base for the Component • Compose Existing Components • Start from Scratch Split the problem into smaller pieces, or objects For each of the parts: • Identify States and how to paint them • Define Transitions and Animate them when Appropriate

2008 JavaOneSM Conference | java.sun.com/javaone |

5

Agenda Recipe for creating custom components Apply the Recipe: Case 1 - Composing Existing Components Apply the Recipe: Case 2 - Starting from Scratch

2008 JavaOneSM Conference | java.sun.com/javaone |

6

Custom Component Demo: Composing Existing Components

2008 JavaOneSM Conference | java.sun.com/javaone |

7

Let’s Apply Our Recipe Base for the Component • We can Reuse the JTable functionality Split the problem into smaller pieces, or objects: • The JTable is painted using CellRenderers • We can create a CalendarCellRenderer

2008 JavaOneSM Conference | java.sun.com/javaone |

8

Using the JTable Table Header

2008 JavaOneSM Conference | java.sun.com/javaone |

9

Customizing the JTable Header

TableColumnModel model = calendarTable.getColumnModel(); TableColumn col = model.getColumn(0); // The HeaderCellRenderer is an implementation of // the TableCellRenderer interface col.setHeaderRenderer(new HeaderCellRenderer());

2008 JavaOneSM Conference | java.sun.com/javaone | 10

Using the JTable Each Cell Has a Renderer: CalendarCellRenderer

2008 JavaOneSM Conference | java.sun.com/javaone | 11

JTable CellRenderer public class WeekCellRenderer extends JPanel implements TableCellRenderer // get a component that paints the contents of the // cell. Note that this component is NOT added to the // table. It is used only for painting its contents. public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int col) { this.activities = (Activity[]) value; this.row = row; this.col = col; this.isSelected = isSelected; return this; } 2008 JavaOne Conference | java.sun.com/javaone | } SM

12

Let’s Apply Our Recipe (Cont.) Base for the Component • We can Reuse the JTable functionality Split the problem into smaller pieces, or objects: • CalendarCell For each of the parts: • Identify States and How to Paint them • Define Transitions and Animate them when Appropriate

2008 JavaOneSM Conference | java.sun.com/javaone | 13

We need to paint the Activities for the Day

Activity

2008 JavaOneSM Conference | java.sun.com/javaone | 14

Identify States and How to Paint Them: CalendarCellRenderer - Case 1 Activity Starts in Cell Activity Finishes After the Cell Boundaries

2008 JavaOneSM Conference | java.sun.com/javaone | 15

Identify States and How to Paint Them: CalendarCellRenderer - Case 2

Activity Starts Before the Cell Boundaries Activity Finishes After the Cell Boundaries

2008 JavaOneSM Conference | java.sun.com/javaone | 16

Identify States and How to Paint Them: CalendarCellRenderer - Case 3

Activity Starts Before the Cell Boundaries Activity Finishes Before Cell Boundaries

2008 JavaOneSM Conference | java.sun.com/javaone | 17

Identify States and How to Paint Them: CalendarCellRenderer - Case 4

Activity Starts After the Cell Boundaries Activity Finishes Before Cell Boundaries

2008 JavaOneSM Conference | java.sun.com/javaone | 18

Identify States and How to Paint Them: CalendarCellRenderer - Case 5 First Column Displays the Time CellRenderer

2008 JavaOneSM Conference | java.sun.com/javaone | 19

State Design Pattern StatePainter paint();

StatePainter1 paint() { …. }

StatePainter2 paint() { …. }

StatePainter3 paint() { …. }

StatePainter4 paint() { …. }

2008 JavaOneSM Conference | java.sun.com/javaone | 20

State Design Pattern public interface ActivityStatePainter { public void paint(Graphics g, Activity activity); } public class WeekCellRenderer extends JComponent { private ActivityStatePainter statePainter[]; public ActivityStatePainter getStatePainter(Activity a) { // we do a set of if statements to determine // the state painter, and return the one } }

2008 JavaOneSM Conference | java.sun.com/javaone | 21

public class WeekCellRenderer … { @Override public void paint(Graphics g, Activity a) { // We iterate over the activities // in the model for the “day” // use the paint that corresponds to the // state we are for (Activity activity : activities) { painter = getStatePainter(activity); painter.paint(g,activity); } } ... }

2008 JavaOneSM Conference | java.sun.com/javaone | 22

Custom Component Demo: Calendar Component Transitions

2008 JavaOneSM Conference | java.sun.com/javaone | 23

Defining Transitions What events are going produce a change of state?

Mouse Move Event

MouseMotionListener

We need to determine on what Activity the mouse is located

2008 JavaOneSM Conference | java.sun.com/javaone | 24

public class TableMouseListener … { public void mousePressed(MouseEvent event) { Point p = event.getPoint(); int row = table.rowAtPoint(p); int column = table.columnAtPoint(p); WeekCellRenderer tableCellRenderer = This code (WeekCellRenderer) table goes in a method .getCellRenderer(row, column); that returns a Rectangle rect = structure with: table.getCellRect(row, column, true); value,x,y,row, int x = (int) (event.getX() - rect.getX()); column int y = (int) (event.getY() - rect.getY()); Object value = tableModel.getValueAt(row, column); tableCellRenderer.selectActivityAt(value,x,y, row,column); table.repaint(); } } 2008 JavaOneSM Conference | java.sun.com/javaone | 25

public class TableMouseListener … { public void mouseDragged(MouseEvent event) { // We use the same logic as the previous // slide getValueXYRowColumn(event); tableCellRenderer.mouseDraggingAt(value,x,y, row,column); table.repaint(); } }

2008 JavaOneSM Conference | java.sun.com/javaone | 26

public class WeekCellRenderer … { public void mouseDraggingAt(…,int row,…) { int selectedHourFrom = selectedActivity.getFromHour(); if (row+1!=selectedHourFrom) { selectedActivity.setFromHour(row+1); } } }

2008 JavaOneSM Conference | java.sun.com/javaone | 27

Agenda Recipe for creating custom components Apply the Recipe: Case 1 - Composing Existing Components Apply the Recipe: Case 2 - Starting from Scratch

2008 JavaOneSM Conference | java.sun.com/javaone | 28

Custom Component Demo: Starting from Scratch

2008 JavaOneSM Conference | java.sun.com/javaone | 29

Introduction to Java Components

Container

2008 JavaOneSM Conference | java.sun.com/javaone | 30

Introduction to Java Components

Component

Container

2008 JavaOneSM Conference | java.sun.com/javaone | 31

Introduction to Java Components Container.add(Component) Container Component

2008 JavaOneSM Conference | java.sun.com/javaone | 32

Introduction to Java Components LayoutManager { component.setBounds(..) } Container (x,y) width Component

height

2008 JavaOneSM Conference | java.sun.com/javaone | 33

Introduction to Java Components Container

LayoutManager

Graphics

Component 1

Component 2

paint()

paint()

2008 JavaOneSM Conference | java.sun.com/javaone | 34

The Secret Recipe to Component Creation Base for the Component • JPanel: Good for painting in-bounds • GlassPane: Going beyond component boundaries

2008 JavaOneSM Conference | java.sun.com/javaone | 35

Base for the Component Going beyond the component boundaries

2008 JavaOneSM Conference | java.sun.com/javaone | 36

The Secret Recipe to Component Creation Base for the Component • JPanel: Good for painting in-bounds • GlassPane: Going beyond component boundaries Split the problem into smaller pieces, or objects: • The Bar • The Icons For each of the parts: • Identify States and how to paint them • Define Transitions and Animate them when Appropriate

2008 JavaOneSM Conference | java.sun.com/javaone | 37

Identify States There are 4 states for an Icon:

2

3

4

3

2

1

1

1

1

1

Observation: The state of an Icon depends on the location of the mouse 2008 JavaOneSM Conference | java.sun.com/javaone | 38

IconOnBar: Work in progress... public class IconOnBar { // find an appropriate way of representing the state private int state; private BufferedImage image; public BufferedImage getResizedIcon() { … } public Dimension calculateCurrentSize() { return new Dimension(image.getWidth()*state, image.getHeight()*state); } } 2008 JavaOneSM Conference | java.sun.com/javaone | 39

Identify States There are also 4 states for the bar:

2008 JavaOneSM Conference | java.sun.com/javaone | 40

DockBar: Work in progress... public class DockBar { // find an appropriate way of representing the state private int currentWidth; public int calculateCurrentWidth() { int currentWidth = 0; for (IconOnBar iconOnBar : iconsOnBar) { Dimension iconSize = iconOnBar.calculateCurrentSize(); currentWidth += iconSize.getWidth() + SPACE; } return currentWidth; } } 2008 JavaOneSM Conference | java.sun.com/javaone | 41

Defining Transitions What events are going produce a change of state? So far, In the Dock Bar? Mouse Move Event

MouseMotionListener

We need to determine on what Icon the mouse is located

2008 JavaOneSM Conference | java.sun.com/javaone | 42

public void mouseMoved(MouseEvent e) { ... for (IconOnBar iconOnBar : iconsOnBar) { Rectangle iconRect = iconOnBar.calculateCurrentRect(); if ((!iconOnBar.equals(glass.getLastMouseOver())&& (iconRect.contains(e.getPoint())) { iconOnBar.mouseEntered(); glass.setLastMouseOver(iconOnBar); return; } } ... }

2008 JavaOneSM Conference | java.sun.com/javaone | 43

How does the painting happens? public class DockBarGlassPane extends JPanel { public void paintComponent(Graphics g) { paintBar(g); paintIcons(g); } public void paintBar(Graphics g) { int width = bar.getCurrentWidth(); // we generate the bar image (in a few slides) // using the current width // then its just: g.drawImage(barImage,x,y,width,height); } ... }

2008 JavaOneSM Conference | java.sun.com/javaone | 44

How does the painting happens? (Cont.) public class DockBarGlassPane extends JPanel { ... public void paintIcons(Graphics g) { for (IconOnBar iconOnBar : iconsOnBar) { Rectangle = iconOnBar.getCurrentRect(); // we generate the icon image (in a few slides) // using the current width // then its just: g.drawImage(iconImage,x,y,width,height); } ... }

2008 JavaOneSM Conference | java.sun.com/javaone | 45

How do we make the transition continuous?

Answer: Animations!

2008 JavaOneSM Conference | java.sun.com/javaone | 46

My First Animations...

2008 JavaOneSM Conference | java.sun.com/javaone | 47

We will use the Timing Framework: https://timingframework.dev.java.net/

2008 JavaOneSM Conference | java.sun.com/javaone | 48

Timing Framework Animations based on “Time” • State changes based on time Not a sequence of steps Takes care of different environments and machines How does it do it? • It calls back with the progress of the animation

2008 JavaOneSM Conference | java.sun.com/javaone | 49

Making Smooth Transitions: Timing Framework in Action public void startTransition() { transitionAnimator = PropertySetter.createAnimator(400, glass, “progress”, 0f, 1f); transitionAnimator.setAcceleration(0.3f); transitionAnimator.setDeceleration(0.2f); transitionAnimator.start(); } public void setProgress(float progress) { this.progress = progress; repaint(); }

2008 JavaOneSM Conference | java.sun.com/javaone | 50

Making Smooth Transitions public class IconOnBar { // we keep track of the previous state private int oldState; private int currentState; // Because all the painting code looks at this // method for the size, no more changes are necessary public Dimension calculateCurrentSize() { float progress = dock.getTransitionProgress(); float proportion = oldState + (currentState - oldState)*progress int w = width * proportion; int h = height * proportion; return new Dimension(w,h); } } 2008 JavaOneSM Conference | java.sun.com/javaone | 51

Painting the Icons on the Bar

Simple painting of the image using the “current size”

We have reflection in the bar

We paint a Label for the Icon when the Mouse is over it

2008 JavaOneSM Conference | java.sun.com/javaone | 52

Painting the Icons on the Bar (Cont.) public class IconOnBar { private BufferedImage icon; private BufferedImage mirror; public IconOnBar(String label, BufferedImage icon) { this.label = label; this.icon = icon; ReflectionRenderer renderer = new ReflectionRenderer(); mirror = renderer.createReflection(icon); } }

2008 JavaOneSM Conference | java.sun.com/javaone | 53

Painting the Icons on the Bar (Cont.) public void paintComponent(Graphics g) { … g.setColor(Color.BLACK); g.drawStrasding(iconOnBar.getLabel(), labelX + 1, labelY + 1); g.drawString(iconOnBar.getLabel(), labelX - 1, labelY - 1); g.drawString(iconOnBar.getLabel(), labelX - 1, labelY + 1); g.drawString(iconOnBar.getLabel(), labelX + 1, labelY); g.drawString(iconOnBar.getLabel(), labelX, labelY - 1); g.drawString(iconOnBar.getLabel(), labelX, labelY + 1); g.setColor(Color.white); g.drawString(iconOnBar.getLabel(), labelX, labelY); } 2008 JavaOneSM Conference | java.sun.com/javaone | 54

Painting the Bar: Gradient Paint for Filling the Background

2px

+

AlphaComposite SrcAtop

Java 2D Shape with Antialias Round Corners

2008 JavaOneSM Conference | java.sun.com/javaone | 55

Painting the Bar: Gradient // We create a gradient image of only 2 pixels height BufferedImage gradientImage = new BufferedImage(width, 2, BufferedImage.TYPE_INT_RGB); Graphics2D g2d = gradientImage.createGraphics(); Point2D start = new Point2D.Double(15, 0); Point2D end = new Point2D.Double(width, 0); float fractions[] = new float[] { 0f, 0.5f, 1 }; LinearGradientPaint paint = new LinearGradientPaint(start, end, fractions, colors); g2d.setPaint(paint); g2d.fillRect(0, 0, width, 2); g2d.dispose();

2008 JavaOneSM Conference | java.sun.com/javaone | 56

Painting the Bar // We create a Shape with Round Corners Shape rpolygon = new RoundShape(polygon, 5); // All image with zero alpha g2d.setComposite(AlphaComposite.Clear); g2d.fillRect(0, 0, width, dockBar.getHeight()); // Render our shape into the image g2d.setComposite(AlphaComposite.Src); g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g2d.setColor(Color.WHITE); g2d.fill(rpolygon); // fill the shape with the gradient g2d.setComposite(AlphaComposite.SrcAtop); g2d.drawImage(gradientImage, 0, 0, width, dockBar.getHeight(),null); 2008 JavaOneSM Conference | java.sun.com/javaone | 57

Custom Component Demo: Bouncing Effect

2008 JavaOneSM Conference | java.sun.com/javaone | 58

Bouncing Physics Equation

Did you believe that?

2008 JavaOneSM Conference | java.sun.com/javaone | 59

Identify States: Bouncing public class IconOnBar { // New State Information: private float bouncingProgress; public Rectange calculateCurrentRect() { … y = regularY - bouncingProgress*height; … } public Rectangle calculateMirrorRect() { … y = regularY + bouncingProgress*height; … } }

2008 JavaOneSM Conference | java.sun.com/javaone | 60

Defining the Transition // These are the different heights the icon will bounce to float heights[] = new int[] { 3f, 2f, 1f, 0.5f }; for (int i=0;i