Wednesday, August 31, 2011

Notifier Design Pattern

Say you want to develop an application which will have many viewers/pages and all these have to be refreshed with the latest data as soon as the data changes in the engine.


A very simple example is - Weather Reports. When the data changes in the engine, the weather report page has to be refreshed to show the latest data.

Let's follow the below steps:
  1. Develop your report dialog - which will show the weather information.
  2. Implement the ChangeListener in the dialog report- This will act as a listener
  3. Implement your report data class, which contains the weather information. This will act a notifier and also as a registry.
  4. Register your listener with the data class so that the viewer will get a notification if the data changes.


Here is the sample code for it:

import java.util.List;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
/**
 *
 * Weather report dialog.
 *
 */
public class WeatherDetailsDialog implements ChangeListener {
  
 private TableViewer weatherViewer;
  
 protected WeatherDetailsDialog() {  
  //your specific data passed through constructor 
 }
 protected void createReport(Composite topComposite) {
   
  Group dependantsViewerGroup = new Group(topComposite, SWT.NONE);
  dependantsViewerGroup.setLayout(new GridLayout());
  GridData gridData3 = new GridData(SWT.FILL, SWT.FILL, true, false);
  dependantsViewerGroup.setLayoutData(gridData3);
  dependantsViewerGroup.setText("Weather Report"); //$NON-NLS-1$
   
  weatherViewer = new TableViewer(
    dependantsViewerGroup, SWT.FULL_SELECTION | SWT.H_SCROLL | SWT.BORDER);
  final Table reportTable = weatherViewer.getTable();
  reportTable.setLinesVisible(false);
  reportTable.setHeaderVisible(true);
  reportTable.setLayoutData(gridData3);
  GridData gridData = new GridData(SWT.FILL, SWT.TOP, true, false);
  gridData.heightHint = 140;
  reportTable.setLayoutData(gridData);
  TableColumn cityName = new TableColumn(reportTable, SWT.LEFT);
  cityName.setText("City"); //$NON-NLS-1$
  cityName.pack();
  cityName.setWidth(100);
   
  TableColumn valueColumn = new TableColumn(reportTable, SWT.LEFT);
  valueColumn.setText("Value"); //$NON-NLS-1$
  valueColumn.pack();
  valueColumn.setWidth(100);
   
  TableColumn descriptionColumn = new TableColumn(reportTable, SWT.LEFT);
  descriptionColumn.setText("Description"); //$NON-NLS-1$
  descriptionColumn.pack();
  descriptionColumn.setWidth(120);
   
  weatherViewer.setContentProvider(new WeatherContentProvider()); 
  weatherViewer.setLabelProvider(new WeatherLabelProvider());
  //Register viewer with the weathe report data.
   
  List<IWeather> reportData = computingEngine.getReportData();
  for (IWeather data : reportData) {
   ((Weather)data).addChangeListener(this);
  }
  weatherViewer.setInput(reportData);
 }
/**
     * Invoked when the target of the listener has changed its state.
     *
     * @param e  a ChangeEvent object
     */
   
 @Override
 public void stateChanged(ChangeEvent arg0) {
  Display.getDefault().asyncExec(new Runnable() {
   @Override
   public void run() {
    weatherViewer.refresh();
   }
  });
 }
}


This will notfiy as soon the data changes.


import java.util.ArrayList;
import java.util.List;
import javax.swing.event.ChangeListener;

public class Weather {

 private List<ChangeListener> registeredListners = new ArrayList<ChangeListener>();
 private ComputingEngine ComputingData;
 private Float newValue;
 private String description;
 
 public Weather(ComputingEngine ComputingData) {
  this.ComputingData = ComputingData;
 }
  
 /**
  * Updating the status change. This will notify to all the registered listeners
  * Ex: It will notify to the viewer/UI about the weather change report
  *   */
  
 public void setValue(Float newValue) {
  this.newValue = newValue;
  notifyListeners();
 }
  
 public ComputingEngine getComputingData() {
  return ComputingData;
 }
  
 public void setDescription(String description) {
  this.description = description;
 }
  
 public String getDescription() {
  return description;
 }
  
 public Float getNewValue() {
  return newValue;
 }
 //Add your listener here
 public void addChangeListener(ChangeListener listener) {
  if (!registeredListners.contains(listener))
   registeredListners.add(listener);
 }
 //removing the listener
 public void removeChangeListener(ChangeListener listener) {
  if (registeredListners.contains(listener))
   registeredListners.remove(listener);
 }
 //This will be triggered when the state changes
 public void notifyListeners() {
  for (ChangeListener listener : registeredListners) {
   listener.stateChanged(null);
  }
 }
}

Tuesday, August 30, 2011

Progress bar in the Table Viewer

Use Case:
If you want to show a progress bar with in the Table viewer and it should represent what percentage of job has finished.

May be you want to show something like this:



Solution:
We should implement the OwnerDrawLabelProvider on the particular column where you want to show the progress bar.

OwnerDrawLabelProvider: Is an abstract implementation of a label provider that handles custom draw. So this can be used to implement progress bar in the table viewer.

Let's take the example:

//Table viewer
tableViewer = new TableViewer(composite, SWT.FULL_SELECTION | SWT.V_SCROLL | SWT.H_SCROLL);
  reportTable = tableViewer.getTable();
  reportTable.setVisible(true);
  reportTable.setLinesVisible(true);
  reportTable.setHeaderVisible(true);
  reportTable.setLayoutData(new GridData(GridData.FILL, GridData.FILL, true, true));

//Progress bar column
TableViewerColumn progressColumn = new TableViewerColumn(tableViewer, SWT.CENTER);
  progressColumn.getColumn().setText(""); //$NON-NLS-1$
  progressColumn.getColumn().setWidth(150);
  progressColumn.getColumn().setToolTipText("Percentage");

//set progress bar lable provider on the progresbar column
progressColumn.setLabelProvider(new ProgressLabelProvider(tableViewer));

//Implemention of OwnerDrawLabelProvider

import java.util.List;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.viewers.OwnerDrawLabelProvider;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableItem;

public class ProgressLabelProvider extends OwnerDrawLabelProvider {
 private TableViewer tableViewer;
 public ProgressLabelProvider(TableViewer tableViewer) {
  this.tableViewer = tableViewer;
 }
 @Override
 protected void measure(Event event, Object element) {
 }
 @Override
 protected void paint(Event event, Object element) {
  
  //Total Units to be executed
   int totalUnits = getObjects().size();
  
    //Identify the completed units by reading specific information
   int completedUnits = getCompletedUnits();
  
  //Calculate Percentage
   int percentage = (completedUnits * 100/totalUnits);
  
  
   Table table = tableViewer.getTable();
   TableItem item = (TableItem) event.item;
         int index = table.indexOf(item);
         Color foreground = gc.getForeground();
         Color background = gc.getBackground();
         gc.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_RED));
         gc.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_YELLOW));
        
         Rectangle bounds = ((TableItem) event.item).getBounds(event.index);

         int width = (bounds.width - 1) * percentage / 100;
         gc.fillGradientRectangle(event.x, event.y, width, event.height, true);
         Rectangle rect2 = new Rectangle(event.x, event.y, width - 1, event.height - 1);
         gc.drawRectangle(rect2);
         gc.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_LIST_FOREGROUND));
         String text = percentage + "%";
         Point size = event.gc.textExtent(text);
         int offset = Math.max(0, (event.height - size.y) / 2);
         gc.drawText(text, event.x + 2, event.y + offset, true);
         gc.setForeground(background);
         gc.setBackground(foreground);
      
  }
 }
 public Image getColumnImage(Object element) {
  return null;
 }
}


Thanks,
Kondal