Tutorial #11: SystemDialog & WebControl

SystemDialogControl

Normalerweise werden Dialoge passend zum grafischen Design der App gestaltet. Sie werden also wie alle anderen Elemente der Applikation aus Graphenknoten zusammengesetzt. In manchen Situationen, z.B. bei einer Abfrage ob die App tatsächlich beendet werden soll, ist aber der Einsatz von echten System-Dialogen gewünscht. Die Murl Engine bietet mit dem ISystemDialogControl die Möglichkeit einen einfachen System-Dialog anzuzeigen und die entsprechende Benutzereingabe abzufragen.

Wenn auf der Plattform ein ISystemDialogControl zur Verfügung steht, kann mit CreateSystemDialog() ein ISystemDialog-Objekt erzeugt werden. Mit der Methode Open() wird der Dialog angezeigt und mit Close() kann er wieder geschlossen werden.

Für unser Beispiel verwenden wir den 9-Slice-Button aus dem vorigen Beispiel und erzeugen drei Button Knoten.

  <Instance graphResourceId="package_main:init_button_9s"/>
  
  <!--Draw Buttons-->
  <Instance buttonId="button01" text="Show Dialog" posX="-100" posY="0" graphResourceId="package_main:graph_button_9s"/>
  <Instance buttonId="button02" text="+" posX="-170" posY="-100" sizeX="70" sizeY="70" graphResourceId="package_main:graph_button_9s"/>
  <Instance buttonId="button03" text="NEW" posX="-30" posY="-100" sizeX="70" sizeY="70" graphResourceId="package_main:graph_button_9s"/>

In der Header Datei erstellen wir Membervariablen für die Buttons und einen Zeiger für das ISystemDialog-Objekt.

    Logic::ButtonNode mButton01;
    Logic::ButtonNode mButtonPlus;
    Logic::ButtonNode mButtonNew;

    ISystemDialog* mSystemDialog;

Im Konstruktor initialisieren wir den Zeiger mSystemDialog auf 0.

App::SystemDialogLogic::SystemDialogLogic(Logic::IFactory* factory)
: BaseProcessor(factory)
, mSystemDialog(0)
{
}

In der OnInit Methode werden die Logic::ButtonNode Membervariablen mit den Referenzen der Buttonknoten initialisiert. Zusätzlich wird ein ISystemDialog-Objekt erzeugt und der Zeiger auf das Objekt in der MemberVariable mSystemDialog gespeichert.

Bool App::SystemDialogLogic::OnInit(const Logic::IState* state)
{
    state->GetLoader()->UnloadPackage("startup");
    
    Graph::IRoot* root = state->GetGraphRoot();
    AddGraphNode(mButton01.GetReference(root, "button01/button"));
    AddGraphNode(mButtonPlus.GetReference(root, "button02/button"));
    AddGraphNode(mButtonNew.GetReference(root, "button03/button"));

    if (!AreGraphNodesValid())
    {
        return false;
    }

    Logic::IDeviceHandler* deviceHandler = state->GetDeviceHandler();
    if (deviceHandler->IsSystemDialogControlAvailable())
    {
        mSystemDialog = deviceHandler->CreateSystemDialog("Test Dialog Title", 
            "Test Dialog\nChoose Action\n3rd Line Message", "CANCEL", "OK");
    }

    state->SetUserDebugMessage("SystemDialog Init succeeded!");
    return true;
}

Beim Erzeugen des Dialogs geben wir den Titel und die Nachricht für den Dialog gleich mit an.

Das erzeugte ISystemDialog Objekt muss mit DestroySystemDialog() auch wieder zerstört (freigegeben) werden! Dies wird in der OnDeInit Methode erledigt.

Bool App::SystemDialogLogic::OnDeInit(const Logic::IState* state)
{
    Logic::IDeviceHandler* deviceHandler = state->GetDeviceHandler();
    deviceHandler->DestroySystemDialog(mSystemDialog);
    deviceHandler->DestroyEMail(mEMail);
    deviceHandler->DestroyUrlRequest(mUrlRequest);
    return true;
}

Der Dialog soll geöffnet werden, wenn auf Button01 geklickt oder wenn die Taste D gedrückt wurde. Der Dialog wird automatisch geschlossen sobald auf einen Dialog-Button geklickt wurde. Zusätzlich implementieren wir einen AutoCloseTimer, der nach einem Timeout von 5 Sekunden den Dialog automatisch schließt.

    //SystemDialog
    if (mSystemDialog != 0)
    {
        static Double autoCloseTime;
        if (!mSystemDialog->IsOpen())
        {
            if (mButton01->WasReleasedInside() || deviceHandler->WasRawKeyPressed(RAWKEY_D))
            {
                mSystemDialog->Open();
                autoCloseTime = 5;
            }
        }
        else
        {
            autoCloseTime -= dt;
            if (autoCloseTime < 0)
            {
                mSystemDialog->Close(-1);
            }
        }

Mit GetClickedButtonIndex() bzw. GetButtonLabel() kann herausgefunden werden, welcher Dialog-Button geklickt wurde.

        if (mSystemDialog->WasClosed())
        {
            SInt32 clickedButtonIndex = mSystemDialog->GetClickedButtonIndex();

            Debug::Trace("Clicked Button Index: "+Util::SInt32ToString(clickedButtonIndex));
            if (clickedButtonIndex < 0)
                Debug::Trace("Titlebar close button pressed or time out close!");
            else
                Debug::Trace("Clicked button label: "+mSystemDialog->GetButtonLabel(clickedButtonIndex));
        }

Den Button mButtonPlus verwenden wir um dem Dialog einen neuen Button hinzuzufügen.

Zu beachten
Achtung! Bei Android ist die maximale Anzahl der sichtbaren Dialog-Buttons auf drei beschränkt.

Der Button mButtonNew zerstört das aktuelle ISystemDialog-Objekt und erzeugt ein neues ISystemDialog-Objekt.

        if (!mSystemDialog->IsOpen())
        {
            if (mButtonPlus->WasReleasedInside())
            {
                mSystemDialog->AddButton("EXTRA-"+Util::UInt32ToString(mSystemDialog->GetNumberOfButtons()));
            }

            if (mButtonNew->WasReleasedInside())
            {
                deviceHandler->DestroySystemDialog(mSystemDialog);
                mSystemDialog = deviceHandler->CreateSystemDialog("Test Dialog Title", "New Dialog", "OK");
            }
        }
    }

Gibt es mehr als einen Button im Dialog, ist die Reihenfolge der Buttons von der aktuellen Plattform abhängig, auf der die Anwendung läuft. Der Button 0 wird immer an der Position angezeigt, an der normalerweise der negative Button (typischerweise "Abbrechen") liegen würde. Der Aufruf CreateSystemDialog("Test Dialog Title", "Test Dialog\nChoose Action\n3rd Line Message", "CANCEL", "OK"); liefert auf den unterschiedlichen Plattformen folgende Ergebnisse:

tut0111_systemdialog_ios.png
System Dialog iOS
tut0111_systemdialog_android_lt11.png
System Dialog Android API Level < 11
tut0111_systemdialog_android_ge11.png
System Dialog Android API Level ≥ 11
tut0111_systemdialog_win.png
System Dialog Windows
tut0111_systemdialog_osx.png
System Dialog OS X

WebControl

Das IWebControl-Objekt bietet grundlegende Web-Funktionen an:

  • Öffnen einer Webseite im Standard-Browser
  • Versenden einer Email
  • HTTP POST und HTTP GET Aufrufe

Wir verwenden wieder mehrere 9-Slice-Buttons und ButtonNode Membervariablen um die Code-Ausführung zu steuern.

Webseite im Browser öffnen

Um eine Webseite zu öffnen, genügt der Aufruf der Methode OpenUrlInSystemBrowser() mit der Webadresse als Parameter.

    //WebControl
    if (mButtonBrowser->WasReleasedInside())
    {
        if (deviceHandler->IsWebControlAvailable())
            deviceHandler->OpenUrlInSystemBrowser("http://murlengine.com");
    }

Die angegebene Web-Adresse wird im Standard-Browser geöffnet.

Email senden

Um eine E-Mail verschicken zu können, muss zuerst ein IEMail Objekt erzeugt werden. Für die Speicherung dieses Objekts definieren wir in der Header Datei einen Zeiger IEMail* mEMail; und initialisieren diesen im Konstruktor auf 0.

Die Methode CreateEMail() erzeugt ein IEMail Objekt und gibt einen Zeiger auf das Objekt zurück. Mit der Methode Send() wird dann der Email-Client geöffnet und das Email kann vom Benutzer abgesendet werden.

    // Create and send Email
    if (mButtonEmail->WasReleasedInside())
    {
        if (deviceHandler->IsWebControlAvailable())
        {
            mEMail = deviceHandler->CreateEMail("My Subject", "My Message", "office@murlengine.com");
            if (mEMail != 0)
            {
                mEMail->AddToRecipient("konrad@zuse.com");
                mEMail->AddCcRecipient("alan@turing.com");
                mEMail->AddCcRecipient("ivan@sutherland.com");
                mEMail->AddBccRecipient("harry@nyquist.com");
                mEMail->AddBccRecipient("john@neumann.com");
                Debug::Trace(t+": Start Send");
                mEMail->Send();
            }
        }
    }

Das erzeugte IEMail-Objekt muss mit DestroyEMail() auch wieder zerstört (freigegeben) werden! Wir geben das Objekt frei sobald der Sendevorgang abgeschlossen wurde. Der aktuelle Status kann mit folgenden Methoden direkt vom IEMail Objekt abgefragt werden:

    // Destroy Email Object
    if (mEMail != 0)
    {
        if (mEMail->WasSent() ||
            mEMail->WasSaved() ||
            mEMail->WasCancelled() ||
            mEMail->WasRejected())
        {
            deviceHandler->DestroyEMail(mEMail);
        }
    }

Um den Statusverlauf anzuzeigen, geben wir den aktuellen Status noch als Debug Meldung aus:

    // Print Email status
    if (mEMail != 0)
    {
        if (mEMail->IsInQueue())
            Debug::Trace(t+": IsInQueue");
        else if (mEMail->IsSending())
            Debug::Trace(t+": IsSending");
        else if (mEMail->WasSent())
            Debug::Trace(t+": WasSent");
        else if (mEMail->WasSaved())
            Debug::Trace(t+": WasSaved");
        else if (mEMail->WasCancelled())
            Debug::Trace(t+": WasCancelled");
        else if (mEMail->WasRejected())
            Debug::Trace(t+": WasRejected");
        else
            Debug::Trace(t+": Unknown Status");
    }

Der typische Output dafür sieht folgendermaßen aus:

1.209361: Start Send
1.226596: IsSending
1.243769: IsSending
1.260958: IsSending
1.279752: IsSending
1.299660: IsSending
1.323538: WasSent
Zu beachten
Achtung! Die Methoden WasSaved(), WasCancelled() und WasRejected() liefern nur bei iOS ein akkurates Ergebnis. Alle anderen Plattformen liefern immer für WasSent() den Wert true zurück, sobald die Email-Nachricht erfolgreich an den Email-Client übergeben wurde. Eine Überprüfung ob der Benutzer die Nachricht tatsächlich verschickt hat oder die Email z.B. verworfen wurde, ist auf nicht iOS-Plattformen leider nicht möglich.

HTTP POST & HTTP GET

Das IWebControl bietet mit CreateUrlRequest() die Möglichkeit ein IUrlRequest Objekt zu erzeugen, welches dann für einen HTTP Aufruf verwendet werden kann. Für die Speicherung des IUrlRequest Objekts definieren wir in der Header Datei den Zeiger IUrlRequest* mUrlRequest; und initialisieren diesen im Konstruktor auf 0.

In der OnInit Methode wird ein IUrlRequest Objekt erzeugt und der Zeiger auf das Objekt in der MemberVariable mUrlRequest gespeichert.

mUrlRequest = deviceHandler->CreateUrlRequest(); 

Das erzeugte IUrlRequest Objekt muss mit DestroyUrlRequest() auch wieder zerstört (freigegeben) werden! Dies wird in der OnDeInit Methode erledigt.

deviceHandler->DestroyUrlRequest(mUrlRequest);

Ein HTTP-Request mit HTTP GET kann mit der Methode SendGet() ausgeführt werden. Als Paramter wird die HTTP-Adresse übergeben.

    if (mButtonHttpGet->WasReleasedInside())
    {
        mUrlRequest->SendGet("http://murlengine.com");
        Debug::Trace("URL request HTTP GET Start: '%s'", mUrlRequest->GetUrlString().Begin());
    }

Die HTTP-Adresse kann natürlich auch HTTP GET Parameter beinhalten. z.B.

mUrlRequest->SendGet("http://murlengine.com/news/?murlpage=news&murllang=en");

Der Status des HTTP-Requests kann mit folgenden Methoden abgefragt werden:

    //URL Request
    if (mUrlRequest != 0)
    {
        if (mUrlRequest->IsPending())
        {
            Debug::Trace("URL request is pendind. Received %d bytes.",mUrlRequest->GetCurrentDataSize());
        }
        else if (mUrlRequest->WasRejected())
        {
            Debug::Trace("URL request was rejected.");
        }
        else if (mUrlRequest->WasFinished())
        {
            UInt32 receiveSize = mUrlRequest->GetCurrentDataSize();
            Debug::Trace("URL request has finished. Received %d bytes", receiveSize);
            UInt32 limitSize = receiveSize;
            const UInt32 LIMIT = 1024;
            if (limitSize > LIMIT)
                limitSize = LIMIT;
            String response = mUrlRequest->GetResponseData().GetString(limitSize);
            Debug::Trace("Response Content (first %d bytes):\n%s", limitSize, response.Begin());
        }

Die Größe der erhaltenen Daten kann mit GetCurrentDataSize() und die eigentlichen Daten können mit GetResponseData() abgefragt werden. Im Beispiel werden die ersten 1024 Bytes als Debug Meldung ausgegeben.

Der typische Output dafür sieht folgendermaßen aus:

URL request HTTP GET Start: 'http://murlengine.com'
URL request is pendind. Received 0 bytes.
URL request is pendind. Received 0 bytes.
URL request is pendind. Received 0 bytes.
URL request is pendind. Received 0 bytes.
URL request is pendind. Received 0 bytes.
URL request is pendind. Received 0 bytes.
URL request is pendind. Received 3935 bytes.
URL request has finished. Received 8298 bytes
Response Content (first 1024 bytes):
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
...

Um einen HTTP-Request mit HTTP POST abzuschicken, kann die Methode SendPost() verwendet werden. Als Parameter werden die HTTP-Adresse, die Post-Daten und der "Content Type" übergeben.

    if (mButtonHttpPost->WasReleasedInside())
    {
        //early version of URL Encoding with space is +
        Data postData("Name=Jonathan+Doe&Age=23&Formula=a+%2B+b+%3D%3D+13%25%21");
        //URL Encoding with space is %20
        //Data postData("Name=Jonathan%20Doe&Age=23&Formula=a%20%2B%20b%20%3D%3D%2013%25%21");
        mUrlRequest->SendPost("http://murlengine.com/xtest.php?id=42", postData, "application/x-www-form-urlencoded");
        Debug::Trace("URL request HTTP POST Start '%s'", mUrlRequest->GetUrlString().Begin());
    }

Im oberen Beispiel werden die folgenden Parameter-Wert-Paare übermittelt.

Name: Jonathan Doe
Age: 23
Formula: a + b == 13%!

Für die Übermittlung müssen die Daten zuerst URL-kodiert werden (URL Encoding oder auch Percent-encoding; siehe auch de.wikipedia.org/wiki/URL-Encoding). Der kodierte String sieht dann folgendermaßen aus:

Name=Jonathan+Doe&Age=23&Formula=a+%2B+b+%3D%3D+13%25%21

Die folgende einfache HTML/PHP-Seite zeigt die empfangenen Daten an:

<html lang="en">
<head><meta charset="utf-8" /><title>xtest</title></head>
<body>
<h2>Post</h2>
<?php var_dump($_POST);?>
<h2>Get</h2>
<?php var_dump($_GET);?>
</body>
</html>

Der typische Output sieht dann folgendermaßen aus:

URL request HTTP POST Start 'http://murlengine.com/xtest.php?id=42'
URL request is pendind. Received 0 bytes.
URL request is pendind. Received 0 bytes.
URL request is pendind. Received 0 bytes.
URL request is pendind. Received 0 bytes.
URL request is pendind. Received 0 bytes.
URL request is pendind. Received 0 bytes.
URL request is pendind. Received 0 bytes.
URL request is pendind. Received 0 bytes.
URL request has finished. Received 294 bytes
Response Content (first 294 bytes):
<html lang="en">
<head><meta charset="utf-8" /><title>xtest</title></head>
<body>
<h2>Post</h2>
array(3) {
["Name"]=>
string(12) "Jonathan Doe"
["Age"]=>
string(2) "23"
["Formula"]=>
string(13) "a + b == 13%!"
}
<h2>Get</h2>
array(1) {
["id"]=>
string(2) "42"
}
</body>
</html>
tut0111_systemdialog_webcontrol.png
System Dialog & Web Control


Copyright © 2011-2017 Spraylight GmbH.