App-Entwicklung für bada – ein Beispiel

Seite 2: Simulator

Inhaltsverzeichnis

Der einfachste (und in Summe auch schnellste) Weg zum Testen einer bada-Applikation ist der im 2.0.x-SDK enthaltene Emulator. Dabei handelt es sich um eine virtuelle Maschine samt Hardwareemulation – der in den 1.x-Builds mitgelieferte Simulator (bada APIs für Win32) wird nicht mehr unterstützt.

Da die bada IDE auf Eclipse aufsetzt, bekommt der Anwender es auch hier mit dem Konzept der Debugger-Konfigurationen zu tun. Dabei handelt es sich um Einstellungen, die das Verhalten des Debuggers und des Compilers steuern. Das Erstellen der ersten Debugger-Konfiguration ist einfach. Durch Klicken auf den grünen Käfer öffnet der Entwickler den "Debug As"-Dialog. Dort wählt er bada Emulator Application und klickt auf Ok. Wenn ein Fehler vom Typ "Binary not found" erscheint, sind die Rekompilierung durch Klick auf das Hammer-Icon anzustoßen und der Prozess danach zu wiederholen.

Der Emulator schläft - er will wie ein echtes Telefon durch Druck auf "Power" aufgeweckt sein (Abb. 1).

Je nach Systemkonfiguration dauert der Start des Emulators bis zu eine Minute. Er verhält sich wie ein reales Handy, geht also nach einiger Inaktivität, wie in Abbildung 1 gezeigt, "schlafen".

Nach dem ersten Start des Emulators ist es oft notwendig, einen kleinen Einstellungs-Wizard zu erstellen. Danach erscheint die Applikation am Bildschirm. Die Ausgabe von AppLog sieht man im Output-Tab in der Perspektive "bada C++". Nach dem Beenden des Debuggings durch einen Klick auf die Auflegen-Taste des Emulators beendet dieser die Anwendungsausführung, läuft aber weiter. Das spart beim nächsten Start Zeit.

Zu beachten ist, dass insbesondere Haltepunkte alles andere als zuverlässig funktionieren. Mehrfaches Single Stepping ist ein sicheres Rezept für einen Absturz der zugrunde liegenden Debugger-Engine. Zum Fortsetzen des Testens sind dann zumindest die IDE, manchmal sogar der Rechner neu zu starten.

bada 2.0 kennt keine "primitiven" Listen. Die Logik zum Verwalten der Items muss immer der Entwickler stellen, die Liste kommuniziert mit ihrer Datenquelle über das Interface IListViewItemProvider. Insbesondere bei nicht allzu komplexen Formularen ist es sinnvoll, dieses Interface gleich in der Formularklasse zu implementieren. Dafür muss der Entwickler den Header Form1.h um folgende Passagen erweitern:

class Form1 :
public Osp::Ui::Controls::Form,
public Osp::Ui::IActionEventListener,
public Osp::Ui::Controls::IListViewItemProvider,
public Osp::Ui::Controls::IListViewItemEventListener
{

Aus Gründen der Bequemlichkeit implementiert der Autor hier sowohl den Item Provider als auch das Interface zum Einlesen von Listen-Events. Als Datenspeicher sei ein statisches Array aus maximal acht Elementen verwendet – es ist als Member der Klasse angelegt:

public:
Osp::Base::String item[8];
int itemCount;

Damit lässt sich mit der Implementierung der Listenverwaltungslogik beginnen. Als Erstes realisiert der Entwickler die Methode GetItemCount, die die Listen-Engine darüber informiert, wie viele Elemente im Speicher liegen:

int Form1::GetItemCount(void)
{
return itemCount;
}

Die einzelnen Elemente der Liste sind keine Text-Strings, sondern Steuerelemente. Es ist also durchaus möglich, ein eigenes Item zu realisieren. Erfreulicherweise liefert Samsung einige Templates mit, die die meisten Anwendungsfälle abdecken. CreateItem sieht deshalb so aus:

Osp::Ui::Controls::ListItemBase* Form1::CreateItem(int index, 
int itemWidth)
{
Osp::Ui::Controls::SimpleItem * pItem = new SimpleItem();
pItem->Construct(Osp::Graphics::Dimension(itemWidth, 100),
LIST_ANNEX_STYLE_RADIO);
pItem->SetElement(item[index], null);
return pItem;
}

Nach dem Aufruf erstellt CreateItem eine neue Instanz von SimpleItem. Wie die meisten bada-Systemobjekte verlangt diese Klasse eine zweistufige Initialisierung. Das bedeutet, dass die Anforderung des Speichers und die Parametrisierung in zwei separaten Aufrufen erfolgen.

Der Konstruktoraufruf firmiert unter dem Namen Construct(). Nun übergibt man als Parameter die Größe des Elements sowie den gewünschten Stil und weist danach noch den Text zu. Als Rückgabewert bekommt der Listenmanager den Pointer auf das fertige Objekt, das er nur noch auf den Bildschirm zeichnet.

Das Abtragen der Listenelemente ist kompliziert. Der Grund dafür ist, dass DeleteItem immer dann aufgerufen wird, wenn ein Listenelement-Objekt abgetragen werden soll – das hat nicht unbedingt Einfluss auf den Datenspeicher:

bool Form1::DeleteItem(int index, Osp::Ui::Controls::ListItemBase* 
pItem, int itemWidth)
{
if(myKillFlag==true)
{
myKillFlag=false;
itemCount--;
for(int i=index;(i+1)<9;i++)
{
item[i]=item[i+1];
}
}
return false;
}

Diese Methode aktualisiert den Datenspeicher nur, wenn das Kill-Flag gesetzt ist. Die Rückgabe von false weist das System dazu an, das Listenelement selbst abzutragen. Wenn der Entwickler das zu eliminierende Objekt selbst "erlegt", sollte er stattdessen true zurückgeben.

Nun fehlt noch die Verdrahtung der Liste mit dem Element-Array. Das erledigt man am effektivsten in OnInitializing:

result Form1::OnInitializing(void) { result r =
E_SUCCESS;

GetFooter()->AddActionEventListener(*this);

itemCount=3;
item[0]="Item A";
item[1]="Item B";
item[2]="Item C";

myKillFlag=false;

myList = static_cast<ListView *>(GetControl(L"IDC_LISTVIEW1"));
myList->SetItemProvider(*this);
myList->AddListViewItemEventListener(*this);


return r;
}

Die im Konstruktor erstellten Items erscheinen in der Liste (Abb. 2).

Dann wird das Listen-Array mit Daten befüllt, die Listeninstanz bekommt einen Item-Provider und einen Event-Listener zugewiesen. Der Aufruf von GetControl fördert das Steuerelement aus der Liste aller Widgets hervor – die ID des Steuerelements ist in der Ressourcendatei festgelegt. Damit ist das Programm einsatzbereit. Im Emulator sieht es wie in Abbildung 2 aus.

Obwohl das Programm nun funktionsfähig ist, hat es einen ärgerlichen Makel. Es ist möglich, mehrere Icons mit einem Haken zu markieren. Das Erzwingen der "Einzel-Selektion" ist Aufgabe des Entwicklers – eine gangbare Lösung sieht so aus:

void Form1::OnListViewItemStateChanged
(Osp::Ui::Controls::ListView &listView,
int index, int elementId,
Osp::Ui::Controls::ListItemStatus status)
{
//Uncheck all others
if(status==LIST_ITEM_STATUS_CHECKED)
{
AppLog("Checked!");
for(int i=0;i<myList->GetItemCount();i++)
{
if(i!=index)
{
myList->SetItemChecked(i,false);

}
}
}

OnListViewItemStateChanged prüft als Erstes, ob der Aufruf durch das Markieren eines Elements erfolgt ist. Ist dem so, deselektiert die for-Schleife alle anderen Items der Liste – auf diese Art ist immer nur ein Element gleichzeitig markiert. Die restlichen Handler sind für das Beispielprogramm uninteressant. Da die Namen der Methoden aussagekräftig gewählt sind, erschließt sich ihr Einsatzzweck aber aus der Logik.

Im nächsten Schritt soll das Löschen der Items in OnActionPerformed angelegt werden. Dazu sei folgendes Codeschnipsel verwendet:

case ID_FOOTER_REMOVE:
{
bool isFormulaFound=false;
for(int i=0;i<myList->GetItemCount();i++)
{
if(myList->IsItemChecked(i)==true)
{
isFormulaFound=true;
myKillFlag=true;
myList->RefreshList(i,LIST_REFRESH_TYPE_ITEM_REMOVE);
break;
}
}
if(isFormulaFound==false)
{
MessageBox msgbox;
msgbox.Construct("Bitte Item selektieren!", "Bitte wähle
ein Item aus!" ,MSGBOX_STYLE_OK, 10000);
int modalResult = 0;
msgbox.ShowAndWait(modalResult);
}
}
break;

Im ersten Schritt ist nach dem markierten Item zu suchen. Da es in bada keine Funktion zum Ermitteln aller selektierten Elemente einer Liste gibt, muss man die ganze Liste durchlaufen. Sobald ein markiertes Element gefunden ist, wird es entfernt. Von Zeit zu Zeit vergessen Anwender das Markieren eines Elements. In diesem Fall zeigt das Programm eine MessageBox an.