package de.tutorials;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Observable;
import java.util.Observer;
import org.eclipse.jface.viewers.CellEditor;
import org.eclipse.jface.viewers.ICellModifier;
import org.eclipse.jface.viewers.IStructuredContentProvider;
import org.eclipse.jface.viewers.ITableLabelProvider;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.TextCellEditor;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.RowLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.TableItem;
public class Demo {
private static final Display display = new Display();
private Shell shell = null;
private TableViewer tableViewer = null;
private DatenModell model = new DatenModell();
public Demo() {
dispatch();
}
private void dispatch() {
getShell().open();
while(! getShell().isDisposed()) {
if(!display.readAndDispatch()) display.sleep();
}
display.dispose();
}
private Shell getShell() {
if(shell == null) {
shell = new Shell(display);
shell.setLayout(new FillLayout());
getTableViewer();
}
return shell;
}
private TableViewer getTableViewer() {
if (tableViewer == null) {
tableViewer = new TableViewer(getShell(),SWT.SINGLE | SWT.H_SCROLL | SWT.V_SCROLL | SWT.FULL_SELECTION);
tableViewer.setLabelProvider(new MyTableLabelProvider());
tableViewer.setContentProvider(new MyContentProvider());
tableViewer.setInput(model);
}
return tableViewer;
}
public static void main(String[] args) {
new Demo();
}
/*
*
* Dies ist mein simpler MultiCheckBoxCellEditor. Der ist natürlich sehr statisch. Man könnte im Konstruktor
* ein StringArray mit Labels mitgeben, und er baut sich daraus die Checkboxen dynamisch auf.
* Aber ein bißchen Arbeit wollte ich Dir auch lassen ;-).
*
* Durch dem CellModifier (siehe unten) werden die Methoden doGetValue aufgerufen, wenn der Editor den Focus verliert um die Daten
* aus dem Editor zu lesen, bzw. umgekehrt die Daten mit doSetValue zu übergeben, wenn der Editor den Focus erhält.
*
*/
class MultiCheckBoxCellEditor extends CellEditor {
private Button bit0;
private Button bit1;
private Button bit2;
private Button bit3;
private BitSet bitFeld = null;
public MultiCheckBoxCellEditor() {
super();
}
public MultiCheckBoxCellEditor(Composite parent, int style) {
super(parent, style);
}
public MultiCheckBoxCellEditor(Composite parent) {
super(parent);
}
@Override
protected Control createControl(Composite composite) {
if(bitFeld ==null) bitFeld = new BitSet();
Composite retval = new Composite(composite,SWT.NONE);
retval.setLayout(new RowLayout());
bit0 = new Button(retval,SWT.CHECK);
bit0.setText("bit 0");
bit0.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
bitFeld.set(0, bit0.getSelection());
}
});
bit1 = new Button(retval,SWT.CHECK);
bit1.setText("bit 1");
bit1.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
bitFeld.set(1, bit1.getSelection());
}
});
bit2 = new Button(retval,SWT.CHECK);
bit2.setText("bit 2");
bit2.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
bitFeld.set(2, bit2.getSelection());
}
});
bit3 = new Button(retval,SWT.CHECK);
bit3.setText("bit 3");
bit3.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
bitFeld.set(3, bit3.getSelection());
}
});
return retval;
}
@Override
protected Object doGetValue() {
return bitFeld;
}
@Override
protected void doSetFocus() {
}
@Override
protected void doSetValue(Object object) {
this.bitFeld = (BitSet) object;
bit0.setSelection(bitFeld.get(0));
bit1.setSelection(bitFeld.get(1));
bit2.setSelection(bitFeld.get(2));
bit3.setSelection(bitFeld.get(3));
}
}
/*
* Der CellModifier regelt den datenaustausch zwischen Editor und DatenKlasse. Beide haben ja keine Kenntnis
* voneinander. Deswegen wird dieser Adapter dazwischegeschaltet. In der Literatur lößt die "modify" Methode das
* neuzeichnen der Tabelle aus. Das hat aus meiner Sicht den Nachteil, dass sich die tabelle nur dann neuzeichnet, wenn die datenklasse
* sich via CellEditor ändert. Ich möchte aber, dass sich die Tabelle auch dann neuzeichnet, wenn der Datensatz direkt (durch eine andere Klasse)
* geändert wird. Deswegen habe ich den etwas abwendigeren Weg über den Observer gewählt. siehe unten.
*
*
*
*/
class MyCellModifier implements ICellModifier {
@Override
public boolean canModify(Object arg0, String arg1) {
return true;
}
/*
*
* Diese Methode liefert den beiden Editoren die zu bearbeitenden Daten. In der Spalte "BitFeld" das BitSet Objekt
* in der anderen Spalte den String
*
* (non-Javadoc)
* @see org.eclipse.jface.viewers.ICellModifier#getValue(java.lang.Object, java.lang.String)
*/
@Override
public Object getValue(Object element, String property) {
DatenKlasse datenKlasse = (DatenKlasse) element;
if(property .equalsIgnoreCase("BitFeld"))
return datenKlasse.getFlags();
else
return datenKlasse.getLabel();
}
/*
* Wird durch den Editor aufgerufen, wenn er den Focus verliert bzw. nach validate und dient dazu die Daten aus dem Editor
* in die Datenklasse zurückzuschreiben.
*
* (non-Javadoc)
* @see org.eclipse.jface.viewers.ICellModifier#modify(java.lang.Object, java.lang.String, java.lang.Object)
*/
@Override
public void modify(Object element, String property, Object value) {
DatenKlasse datenKlasse = (DatenKlasse)((TableItem) element).getData();
if(property .equalsIgnoreCase("BitFeld"))
datenKlasse.setFlags((BitSet) value);
else
datenKlasse.setLabel((String) value);
}
}
/*
* Die eigentliche Aufgabe des LabelProviders ist es, Strings für die einzelnen Spalten zur Verfügung zu stellen
* vom TableViewer werden die Datenobjekte mittels ContentProvider (siehe unten) gelesen und einzeln an den Labelprovider übergeben.
* Dann wird durch den TableViewer die Methode "getColumnText" aufgerufen, das Datenobjekt und der Spaltenindex übergegen.
* diese Funktion muss dann den String für die Zelle liefern.
*
* Ich nutze die Klasse auch um die Tabelle zu formatieren und die Editoren zu setzen, weil ich finde, dass dies
* logisch hier reingehört. Damit sind alle Metainformationen zur Tabelle in einer Klasse. Dies geschieht im Konstruktor der Klasse
* Die Zeilenhöhe der Tabelle wird normalerweise über den Schriftgrad bestimmt. Deswegen ist ein Trick nötig um die Zeilenhöhe zu setzen.
*
* Diese Klasse ist in jedem Buch über JFace beschrieben.
*/
class MyTableLabelProvider extends LabelProvider implements ITableLabelProvider {
private String [] columnNames = {"BitFeld","Label"};
private int [] columnAligments = {SWT.CENTER, SWT.LEFT};
private int [] columnWidth = {400,200};
public MyTableLabelProvider() {
// Tabelle formatieren und Editoren vorbereiten
//Formatieren
Table table = getTableViewer().getTable();
table.setHeaderVisible(true);
table.setLinesVisible(true);
for(int i = 0; i < columnAligments.length; i++) {
TableColumn column = new TableColumn(table,columnAligments[i]);
column.setText(columnNames[i]);
column.setWidth(columnWidth[i]);
}
/*
* mit diesem Trick Zeilenhöhe festlegen
*/
table.addListener(SWT.MeasureItem, new Listener(){
public void handleEvent(Event event) {
event.height = event.gc.getFontMetrics().getHeight() *2;
}
});
// Editoren
getTableViewer().setColumnProperties(columnNames);
MultiCheckBoxCellEditor multiCheckBoxCellEditor = new MultiCheckBoxCellEditor(table);
TextCellEditor textCellEditor = new TextCellEditor(table);
getTableViewer().setCellEditors(new CellEditor[]{multiCheckBoxCellEditor,textCellEditor});
getTableViewer().setCellModifier(new MyCellModifier());
}
@Override
public Image getColumnImage(Object arg0, int arg1) {
return null;
}
@Override
public String getColumnText(Object datensatz, int spalte) {
if(spalte == 0)
return "" + ((DatenKlasse) datensatz).getFlagsToString();
else
return ((DatenKlasse) datensatz).getLabel();
}
}
//ContentProvider für TableViewer
/*
* Diese Klasse hat die Aufgabe die Zeilen für den TableViewer bereit zu stellen. Dies erledigt er mit der Methode
* getElements, die immer vom TableViewer aufgerufen wird, wenn sich die Tabelle neuzeichnet
*
* InputChanged verwende ich um mich beim Model als listener zu registrieren. Diese Methode feuert immer dann, wenn sich
* der Input des Viewers mit setInput ändert.
*
* Diese Klasse ist in jedem Buch über JFace beschrieben.
*
*/
class MyContentProvider implements IStructuredContentProvider, Observer {
private TableViewer tableViewer;
@Override
public Object[] getElements(Object arg0) {
return model.toArray();
}
@Override
public void dispose() {
// TODO Auto-generated method stub
}
@Override
public void inputChanged(Viewer viewer, Object oldSource, Object newSource) {
this.tableViewer = (TableViewer) viewer;
if(oldSource != null){
((DatenModell) oldSource).deleteObserver(this);
}
if(newSource != null){
((DatenModell) newSource).addObserver(this);
}
}
// Schutziger Trick zum einfachen neuzeichnen der Tabelle
@Override
public void update(Observable o, Object arg) {
tableViewer.refresh();
}
}
// Container für Zeilen, das Datenmodell eben ;-)
/*
* Dieses einfache Datenmodell nimmt alle Datenobjekte für die einzelnen Zeilen auf. Es wird benachrichtigt, wenn sich
* irgendein gespeicherertes Objekt ändern und feuert selbst mit einem Ereignis, wenn ein Objekt hinzugefügt, geändert, oder entfernt wird.
* Damit weiss der ContentProvider, dass sich Daten geändert haben und er die Tabelle neuzeichnen muss.
*
*/
class DatenModell extends Observable implements Observer{
private ArrayList<DatenKlasse> daten = new ArrayList<DatenKlasse>();
// Mit Spieldaten füllen
public DatenModell() {
add(new DatenKlasse(new BitSet(4), "test1"));
add(new DatenKlasse(new BitSet(4), "test2"));
add(new DatenKlasse(new BitSet(4), "test3"));
add(new DatenKlasse(new BitSet(4), "test4"));
add(new DatenKlasse(new BitSet(4),"test5"));
}
public void add(DatenKlasse datenKlasse) {
datenKlasse.addObserver(this);
daten.add(datenKlasse);
fireEvent();
}
public void remove(DatenKlasse datenKlasse) {
datenKlasse.deleteObserver(this);
daten.remove(datenKlasse);
fireEvent();
}
public Object[] toArray() {
return daten.toArray();
}
public String toString() {
return daten.toString();
}
/*
* Wird durch die einzelnen DatenObjekte ausgelöst, wenn sich darin etwas ändert.
* (non-Javadoc)
* @see java.util.Observer#update(java.util.Observable, java.lang.Object)
*/
@Override
public void update(Observable arg0, Object arg1) {
fireEvent();
}
private void fireEvent() {
setChanged();
notifyObservers();
}
}
// Spielklasse mit Daten für eine einzelne Zeile in der Tabelle (zweispaltig mit bitfeld und String)
/*
* Dies ist die eigentliche Datenklasse, die pro Zeile angezeigt wird. Im richtigen Leben repräsentiert
* die Klasse eine Person oder ein Auto oder ein anderes Geschäftsobjekt. Den Observer habe ich deswegen eingebaut, damit sich die Tabelle
* auch dann neuzeichnet, wenn jemand den Datensatz an der Tabelle vorbei ändert.
*
* In diesem Fall wird das Modell (siehe oben) benachrichtigt, welches wiederum dem ContentProvider eine Nachricht schickt.
* Der ContentProvider veranlaßt dann das Neuzeichnen der Tabelle mit refresh.
*
*/
class DatenKlasse extends Observable{
private BitSet flags = new BitSet();
private String label = "";
public DatenKlasse() {
this(new BitSet(4),"");
}
public DatenKlasse(BitSet flags, String label) {
super();
this.flags = flags;
this.label = label;
}
public BitSet getFlags() {
return flags;
}
public void setFlags(BitSet flags) {
this.flags = flags;
fireEvent();
}
public String getLabel() {
return label;
}
public void setLabel(String label) {
this.label = label;
fireEvent();
}
public String getFlagsToString(){
// Geht sicher besser, aber ich bin noch müde
StringBuilder builder = new StringBuilder();
boolean flag = false;
for (int i = 0 ; i < 4; i++){
if (flags.get(i)){
if(flag)
builder.append(",");
builder.append("Bit " + i);
flag = true;
}
}
if(builder.length() == 0)
builder.append("Keine Bits gesetzt.");
return builder.toString();
}
private void fireEvent() {
setChanged();
notifyObservers();
}
}
}