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