JFace Databinding Radiobuttons

In diesem Beitrag geht es um die Verwendung der JFace Datenbindung mit zwei Radiobuttons.

Radiobuttons Databinding

Wie in der obigen Abbildung zu sehen werden zwei Button als Radiobuttons eingefügt (m_yesButton, m_noButton), ein Label welches den Wert des Members rbstate und ein Button zum direkten Setzen des Modellwertes rbstate.

Das Snippet wurde unter Eclipse (Oxygen Release (4.7.0)) erstellt und kann dort als Java Anwendung gestartet werden. Klickt man einen Radiobutton an, wird das Modell auf diesen Wert gesetzt. Steht rbstate auf YES, kann man mit dem Button „Auf No“ rbstate auf „NO“ setzen. Dann wird über die Datenbindung die Auswahl der Radiobuttons automatisch aktualisiert.

In der Methode iniDataBinding wird die Datenbindung vorgenommen. Dabei werden zunächst Observablen für die Selektion der Radiobuttons erstellt (Z130, Z132). Dazu wird eine SelectObservableValue  Observable erstellt, die mit addOption die Observablen für die Radiobuttons erhält (Z 135-Z 138). Dann wird die Observable für die Variable rbstate erzeugt (Z 140) und schliesslich selbige mit der SelectObservableValue gebunden(Z 143).

Die Observable für rbstate wird dann noch mit der Widget Observablen für den Label gebunden(Z 147).

Im Modell liegt die Modellklasse als Bean vor! D.h. es liegen zu den Variablen Getter und Setter vor.

 

/*******************************************************************************
 * Copyright (c) 2017 Günter Stubbe.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Das Programm zeigt die Datenbindung zweier Radiobuttons, die in beide 
 * Richtungen funktioniert: Das Schalten der Buttons ändert das Modell, die 
 * Änderung im Modell bewirkt die Aktualisierung des GUI
 ******************************************************************************/

package org.eclipse.jface.examples.databinding.snippets;

import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;

import org.eclipse.core.databinding.DataBindingContext;
import org.eclipse.core.databinding.beans.BeanProperties;
import org.eclipse.core.databinding.observable.Realm;
import org.eclipse.core.databinding.observable.value.IObservableValue;
import org.eclipse.core.databinding.observable.value.SelectObservableValue;
import org.eclipse.jface.databinding.swt.DisplayRealm;
import org.eclipse.jface.databinding.swt.WidgetProperties;
import org.eclipse.jface.examples.databinding.snippets.SnippetRadioButton1.YesNoModel.RESPONSE;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;

public class SnippetRadioButton1 {
	protected Shell shell;

	private Button m_yesButton;
	private Button m_noButton;

	private Text m_labelActValue;

	public YesNoModel yesNoModel;
	// DataBindingContext bindingContext;

	/**
	 * Anwendung starten
	 *
	 */
	public static void main(String[] args) {
		try {
			SnippetRadioButton1 window = new SnippetRadioButton1();
			window.open();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * Hauptfenster anzeigen
	 */
	public void open() {
		final Display display = Display.getDefault();
		Realm.runWithDefault(DisplayRealm.getRealm(display), new Runnable() {
			public void run() {
				createContents();
				shell.open();
				shell.layout();
				while (!shell.isDisposed()) {
					if (!display.readAndDispatch())
						display.sleep();
				}
			}
		});
	}

	/* Oberflächenelemente erzeugen */
	protected void createContents() {
		shell = new Shell();
		shell.setSize(300, 200);
		shell.setText("SnippetRadioButton1.java");
		final GridLayout gridLayout = new GridLayout();
		gridLayout.makeColumnsEqualWidth = true;
		gridLayout.numColumns = 4;
		// shell.setLayout(new FillLayout());
		shell.setLayout(gridLayout);
		Composite radioButtonGroupContainer = new Composite(shell, SWT.NONE);
		radioButtonGroupContainer.setLayout(new GridLayout());
		Label question = new Label(radioButtonGroupContainer, SWT.NONE);
		question.setText("TestFrage");

		m_yesButton = new Button(radioButtonGroupContainer, SWT.RADIO);
		m_yesButton.setText("Yes");
		m_noButton = new Button(radioButtonGroupContainer, SWT.RADIO);
		m_noButton.setText("No");
		m_noButton.setSelection(true);

		// hier wird angezeigt, was gerade ausgewählt wurde
		m_labelActValue = new Text(shell, SWT.NONE);

		// Die Daten aus dem Model
		yesNoModel = new YesNoModel();

		// Ein Button zum manipulieren des Modells: Hier wird der rbState
		// auf NO gesetzt - stehen die Radiobuttons auf YES schalten sie
		// automatisch auf NO um.
		Button setMBtn = new Button(shell, SWT.PUSH);
		setMBtn.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(final SelectionEvent e) {
				// Modell auf NO setzen
				// Die folgende Zeile ist zwingend so erforderlich! Die
				// übernächste Zeile, die jetzt auskommentiert ist,
				// funktionierte nicht!!! Mit der auskommentierten Zuweisung
				// wird das PropertyChange Event nicht ausgelöst und das
				// GUI nicht automatisch aktualisiert.
				SnippetRadioButton1.this.yesNoModel.setRbstate(RESPONSE.NO);
				// SnippetRadioButton1.this.yesNoModel.rbstate = RESPONSE.NO;
				System.out.println(SnippetRadioButton1.this.yesNoModel.rbstate);
			}
		});
		setMBtn.setText("Auf NO");
		iniDataBinding();
	}

	void iniDataBinding() {
		DataBindingContext bindingContext = new DataBindingContext();

		IObservableValue yesBtnSelection = WidgetProperties.selection()
				.observe(m_yesButton);
		IObservableValue noBtnSelection = WidgetProperties.selection()
				.observe(m_noButton);

		SelectObservableValue featureRepoPolicyObservable = new SelectObservableValue(
				YesNoModel.RESPONSE.class);
		featureRepoPolicyObservable.addOption(RESPONSE.YES, yesBtnSelection);
		featureRepoPolicyObservable.addOption(RESPONSE.NO, noBtnSelection);

		IObservableValue obsModelLabel = BeanProperties.value("rbstate")
				.observe(yesNoModel);

		bindingContext.bindValue(featureRepoPolicyObservable, obsModelLabel);

		IObservableValue obsLabel = WidgetProperties.text(SWT.Modify)
				.observe(m_labelActValue);
		bindingContext.bindValue(obsLabel, obsModelLabel, null, null);
	}

	// Ab hier folgen die Klassen für die Modeldaten
	// Minimal JavaBeans support
	public static abstract class AbstractModelObject {
		private PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(
				this);

		public void addPropertyChangeListener(PropertyChangeListener listener) {
			propertyChangeSupport.addPropertyChangeListener(listener);
		}

		public void addPropertyChangeListener(String propertyName,
				PropertyChangeListener listener) {
			propertyChangeSupport.addPropertyChangeListener(propertyName,
					listener);
		}

		public void removePropertyChangeListener(
				PropertyChangeListener listener) {
			propertyChangeSupport.removePropertyChangeListener(listener);
		}

		public void removePropertyChangeListener(String propertyName,
				PropertyChangeListener listener) {
			propertyChangeSupport.removePropertyChangeListener(propertyName,
					listener);
		}

		protected void firePropertyChange(String propertyName, Object oldValue,
				Object newValue) {
			propertyChangeSupport.firePropertyChange(propertyName, oldValue,
					newValue);
		}
	}

	// Model
	public static class YesNoModel extends AbstractModelObject {

		private RESPONSE rbstate;
		private String cstext;

		public static enum RESPONSE {
			YES, NO
		}

		public YesNoModel() {
			rbstate = RESPONSE.YES;
			cstext = "";
		}

		public String getCstext() {
			return cstext;
		}

		public void setCstext(String cstext) {
			this.firePropertyChange("cstext", this.cstext,
					this.cstext = cstext);
		}

		public RESPONSE getRbstate() {
			return rbstate;
		}

		public void setRbstate(RESPONSE rbstate) {

			this.firePropertyChange("rbstate", this.rbstate,
					this.rbstate = rbstate);
		}
	}
}

Beim Versuch die Datenbindung in meinem gerade aktuell bearbeiteten Programm zu integrieren hatte ich zunächst kein Glück. Das Problem bestand in der nicht Bean – Konformen Form meiner Daten. In der Modellklasse hatte ich eine Variable vColor. Die Setter und Getter habe ich selbst manuell zugefügt: getVColor und setVColor. Damit konnte in der Datenbindung die Variable vColor nicht gefunden werden. Richtig wäre gewesen: getvColor und setvColor, also mit einem kleinen v, welches nicht in einen Großbuchstaben gewandelt wird, da der folgende Buchstabe bereits ein Großbuchstabe ist. Gefunden unter

Java Bean Getters/Setters

Dieser Fehler hat mich Stunden der Suche gekostet.

 

Subversion zu Git Migration

Bisher habe ich für einige Projekte Subversion und später auch Git für die Versionierung verwendet. Jetzt wollte ich die Subversion Repositories zu Git migrieren. Wenn Sie diese Beschreibung nutzen, stellen Sie bitte sicher, dass sie keine Daten löschen. Am besten legen Sie zunächst eine Sicherung aller Daten an.

Powershell: Erstellen der authors.txt

Da auf dem Bild oben nicht so richtig viel zu erkennen ist, hier nochmal das Kommando:

svn.exe log −−quiet | ? { $_ −notlike '−∗' } | % { "{0} = {0} <{0}>" −f
($_ −split ' \| ')[1] } | Select −Object −Unique > authors.txt

Das Resultat könnte so aussehen:

Den Inhalt anpassen

Hier habe ich die letzte Zeile zugefügt, damit später das Migrieren sicher klappt.

Das Verzeichnis mit den Dateien (noch mit Subversion) die verschoben werden sollen. Sie sehen, dass das Verzeichnis viele Dateien enthält, die temporär erzeugt werden, also überflüssig sind. Diese sollte man mit gitignore ausschliessen. gitignore ist in dieser Beschreibung allerdings kein Thema.

Quellordner mit Subversion, Git Bash

Damit die Daten migriert werden können, muss man sie aus dem Subversion Repository in ein Git Repository bringen. Das oben gezeigte Verzeichnis wird dafür gar nicht benötigt.

Subversion Repositories auf meinem Haupt – PC

Aus dem obigen Repository wird dann zunächst in ein temporäres Verzeichnis (vorher anlegen) geklont. Dabei wird aus Subversion Git:

Das Migrieren in ein temporäres Verzeichnis aus dem Subversion Repository

Das Ergebnis sieht so aus wie in der folgenden Abbildung. Dieses Verzeichnis („f:\tmp\c#“) habe ich dann einfach wieder in meine Projekte kopiert (Bei mir: e:\Tex\Projekte\c#) und dort zunächst mit dem „Subversion“ Verzeichnis verglichen, da evtl. einige Änderungen noch nicht in Subversion angekommen waren. Dann muss man die „neuen“ Dateien zu Git hinzufügen: git add . und dann git submit. Danach kann man, nach sorgfältiger Prüfung (bei mir hier eine Kompilierung der Tex Dateien) und wenn man sich sicher ist, dass man alle relevanten Dateien erfasst hat das alte (Subversion) Verzeichnis und dann das temporäre Verzeichnis löschen.

Im neue Arbeitsverzeichnis mit dem .git Unterverzeichnis ( Bei mir: e:\Tex\Projekte\c#) öffnet man dann eine Git Bash und fügt einen Remote zu. In diesem Fall benutze ich einen Raspi im lokalen Netz, auf dem ein lokaler Git Server betrieben wird:

Dahin klont man dann das soeben erstellte lokale Verzeichnis:

Das Klonen des Subversion Repositories in ein neues temporäres Verzeichnis mit git Repository

Zukünftige Änderungen werden dann mittels add – commit und push in das Remote Repository übertragen:

Hinweis:

Ich hatte das Problem, dass die ip-Adresse des lokalen Netzes sich geändert hatte. Deshalb musste zunächst der Zugriff für Subversion neu gesetzt werden:

Das gleiche Problem trat aber auch mit den git Repositories auf, die schon vor der ip-Adressenänderung angelegt worden sind. Auch hier muss dann die URL angepasst werden. Dazu wechselt man in das betroffene Verzeichnis, öffnet dort eine Git Bash und führt das folgenden Kommando (natürlich mit passenden Repository, vfl-rastede.git ist nur ein Beispiel) aus:

Ausklappmenü, CSS und IPhone

Eine von mir erstellte Webseite verwendet ein auf CSS basierendes responsives Menü, welches auf mobilen Geräten auf eine Zeilenweise Darstellung der Menüpunkte wechselt. Das klappte bei der Entwicklung gut – die Untermenüs wurden auf meinem Smartphone und in der Entwickleransicht unter Chrom wie gewünscht beim Anklicken ausgeklappt. Dazu wird das Hovern in CSS verwendet. Allerdings gab es dann Meldungen, dass die Seite auf einem IPhone nicht richtig funktioniert – alle Menüpunkte mit Untermenüs funktionierten dort nicht. Dies ist das zweite Mal, dass ich Probleme mit Webseiten auf Geräten der Firma mit dem angebissenen Obst habe. Das erste Mal betraf übrigens ein Teil einer Webseite mit WebGL – lief nur unter Windows, Android und Linux.

Wie dem auch sei, ich musste eine Lösung für das CSS – Hover – Problem finden und Google half. Es war ein wenig Javascript nötig um mit möglichst wenig Aufwand das Menü ans Laufen zu kriegen. Hier der Link auf  die gefundene Lösung.

In der PHP Datei wird im <head> Bereich die Datei doubletaptogo.min.js eingebunden:

<html>
<head>
<title>Der Titel</title>
<?php 
	$relativeRootPath = EvalRelativeRootPath(); // z.B. ./../ 
?>
  	<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
  	<meta name="viewport" content="width=device-width"> <!--  sonst funktionieren die Mediaqueries nicht  -->
  	<link rel="stylesheet" type="text/css" href="<?php echo $relativeRootPath?>www/styles/main.css" />
	<script src="<?php echo $relativeRootPath; ?>external/jquery-1.10.2.js"></script>
  	<script src="<?php echo $relativeRootPath; ?>external/jquery-ui-1.10.4.custom.js"></script>
	<script src="<?php echo $relativeRootPath; ?>www/js/modernizr.custom.17475.js"></script>
	<script src="<?php echo $relativeRootPath; ?>www/js/doubletaptogo.min.js"></script>

An geeigneter Stelle wird dann in der JQuery ready Funktion die Zeile mit doubleTapToGo() eingefügt. Die Funktion wird für die li ausgeführt, die zu dem Div mit der Id HMenuL1 gehören – wenn die li weitere ul’s haben:

  <script type="text/javascript">
   $(document).ready(function() {
		// Problem Navigation Responsive auf Iphone beheben
		$( '#HmenuL1 li:has(ul)' ).doubleTapToGo();

Da ich kein I-Gerät besitze, musste ich meinen Schwiegervater mal kurz nach seinem IPhone fragen. Das Menü funktionierte nun zumindest auf seinem Gerät.

Android Content Provider

Wozu sind die noch mal gut? Ah ja, wenn man auf eine SQLite Datenbank aus einer App zugreift, kann bei einem direkten Zugriff nur die App auf die Datenbank zugreifen. Wird hingegen ein ContentProvider benutzt, können auch andere Anwendungen auf die Datenbank zugreifen. Als Beispiel müssen die Kontakte herhalten – Diese werden von diversen Apps benutzt.

Damit der Contentprovider funktioniert sind einige Vorgaben zu beachten:

  • Die eigene Contentprovider Klasse muss von ContentProvider abgeleitet werden. In der abgeleiteten Klasse müssen die Methoden onCreate, query, insert, update, delete und getType überschrieben werden. Sie werden quasi auf die Datenbank Methoden umgeleitet
  • Statt in der eigenen App direkt auf die Datenbank zuzugreifen, wird jetzt auf den ContentProvider zugegriffen.
  • Der Contentprovider muss in der Manifest Datei mit dem Tag <Provider … bekanntgemacht werden.

Um überhaupt auf Daten zugreifen zu können, benötigt man eine URI zu eben diesen Daten.  Die dabei verwendeten Uri’s erinnern mich start an das Erzeugen einer REST Schnittstelle. Um alle Daten der Tabelle products abzurufen, gibt man z.B.
content://de.stubbe_cs.database.provider.MyContentProvider/products
ein. Um an einen speziellen Rekord zu gelangen gibt man content://de.stubbe_cs.database.provider.MyContentProvider/products/6
ein, wobei die 6 die ID des Rekords bezeichnet.

Zum Bearbeiten der Uri’s kann man sehr gut die Klasse UriMatcher verwenden.:

    private static final String AUTHORITY = "de.stubbe_cs.database.provider.MyContentProvider";
    private static final String PRODUCTS_TABLE = "products";

    // Typ der URI. 1: ganze Tabelle, 2: Record
    public static final int PRODUCTS = 1;
    public static final int PRODUCTS_ID = 2;

    private static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH);

    static {
        sURIMatcher.addURI(AUTHORITY, PRODUCTS_TABLE, PRODUCTS);
        sURIMatcher.addURI(AUTHORITY, PRODUCTS_TABLE + "/#", PRODUCTS_ID);
    }
    ...
    ...
    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        // Typ der uri
        int uriType = sURIMatcher.match(uri);

Es wird ein Objekt der Klasse UriMatcher erzeugt und es werden Muster für Uri’s hinterlegt. Das erste Muster soll aus dem String AUTHORITY und dem String PRODUCTS_TABLE zusammengesetzt sein. Der Typ dieses Musters wird auf PRODUCTS festgelegt. Wird jetzt z.B. die

Uricontent://de.stubbe_cs.database.provider.MyContentProvider/products

gefunden, gibt sUriMatcher.match(uri) eine 1 zurück, was dem Typ PRODUCTS entspricht. Mit

uri.getLastPathSegment()

erhält man den String PRODUCTS_TABLE (hier also products).

SQLite unter Android mit Speicherung auf der SD Karte

Ich habe mich mit dem SQLite – Beispiel aus dem Buch “Android Studio Development“ von Neil Smyth beschäftigt. Zunächst habe ich das Beispiel ans Laufen gebracht, was aber mithilfe des Buches sehr einfach ist. Dann habe ich versucht mir auf meinem Smartphone die Datenbank anzusehen. Das klappte aber leider nicht, da dazu die Berechtigung fehlt. Also das Beispiel auf das virtuelle Smartphone gebracht. Jetzt hat man die Möglichkeit über eine Konsole (bei mir unter Windows 7) mit

adb -e shell

auf das virtuelle Smartphone zuzugreifen. Dort hat man dann auch die nötigen Rechte. Im Verzeichnis

/data/data/de.stubbe_cs.database/databases

befindet sich die Datenbank des Beispielprojektes. Man kann sie sich ansehen, indem man in die Konsole sqlite3 eingibt. Das startet ein SQLite Tool. Dort kann man wiederum .open ./<dbname> und dann .tables eingeben. Als Ergebnis werden dann die Tabellennamen der Datenbank angezeigt. Mit .schema erhält man auch Infos über die Felder in den Tabellen.

Da es scheinbar nur schwer möglich ist, die Datenbank auf einem Smartphone zu inspizieren, habe ich mir überlegt, die Datenbank auf der SD Karte meines Smartphones auszulagern. Dazu müssen allerdings einige Änderungen vorgenommen werden.

public class MyDBHandler extends SQLiteOpenHelper {
    private static final int DATABASE_VERSION = 1;
    //private static final String DATABASE_NAME = "procuctDB.db";
    private static final String DATABASE_NAME = "/sdcard/procuctDB.db";  // Neu
    public static final String TABLE_PRODUCTS = "products";
    public static final String COLUMN_ID = "_id";
    public static final String COLUMN_PRODUCTNAME = "productname";
    public static final String COLUMN_QUANTITY = "quantity";
    public MyDBHandler(Context context, String name,
                       SQLiteDatabase.CursorFactory factory, int version){
        super(context, DATABASE_NAME, factory, DATABASE_VERSION);
        SQLiteDatabase.openOrCreateDatabase(DATABASE_NAME, null); // neu
    }
    @Override
    public void onCreate(SQLiteDatabase db) {
        // Tabelle erzeugen
        String CREATE_PRODUCTS_TABLE = "CREATE TABLE " +
                TABLE_PRODUCTS + "(" + COLUMN_ID + " INTEGER PRIMARY KEY," +
                COLUMN_PRODUCTNAME + " TEXT," + COLUMN_QUANTITY +
                " INTEGER" + ")";
        db.execSQL(CREATE_PRODUCTS_TABLE);
    }

Dabei wurde in den Datenbankname der Pfad auf die SD Karte mit eingefügt und in den Konstruktor wurde  die Methode

SQLiteDatabase.openOrCreateDatabase

eingefügt. Dann muss noch in der Manifest Datei die Berechtigung zum Schreiben auf externe Medien gesetzt werden:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="de.stubbe_cs.database">

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"

Das passiert hier in Zeile 5 mit dem usespermission Tag.

Nach dem Start der App wurde die Datenbank auf die SD Karte ausgelagert. Aber wie überprüft man das? Im Emulator gab es zunächst Probleme. Wo steckt man die SD Karte rein? Das erledigte sich aber, denn beim Start der Datenbankanwendnung hat der Emulator angeboten, eine virtuelle SD Karte anzulegen. Ich habe die interne Variante gewählt und die virtuelle SD Karte formatiert. Dann klappte es immer noch nicht, da in den Einstellungen der App es keine Berechtigung für den Zugriff auf die SD Karte gab. Nachdem diese Probleme gelöst waren, konnte die App im Emulator gestartet werden. Das die Datenbank jetzt auf der SD Karte ist, kann man wierderum über die Konsole überprüfen. In der Shell wechselt man in das Verzeichnis /sdcard und findet dort dann (mit ls) die Datenbank Dateien.

Auf dem Smartphone klappte das genauso. Um die Datenbank in Ruhe inspizieren zu können, habe ich sie per FTP auf meinen PC gezogen und mit dem SQLite Browser untersucht.

Interessanter Weise wird mein Smartphone via USB Treiber in das Dateisystem meines Windows 7 PCs eingebunden. Die Datenbankdateien unter /sdcard sind dort allerdings nicht zu sehen !