Skip to content

Posts der Kategorie ‘Programmierung’

4
Feb

C++ Builder + iOS: Seitliche Einstellungsleiste + „modale“ Dialoge

Heute gibts mal wieder einen Blogeintrag aus dem Bereich C++ Builder + iOS. Und zwar gibt es ein kleines Tutorial wie man eine seitliche Einstellungsleiste erstellen kann und wie man fast genausoleicht „modale“ Dialoge in iOS nachbilden kann. Modale Dialoge wie z. B. beim Hinzufügen eines neuen Kontakts in der Telefon-App.

Nachdem wir eine Mobile C++-Builder Anwendung erstellt haben, beginnen wir mit dem Design. Als erstes legen wir ein Layout auf die Form. Dieses Layout setzen wir auf „alClient“, so dass es die gesamte Form einnimmt. In dieses Layout wiederum setzen wir eine ListBox wiederum mit „alClient“ und fügen ein paar Einträge mit dem Eintragseditro hinzu.

Danach legen wir eine zusätzliche ListBox auf die Form. Dieser weisen wir den Align „alLeft“ zu und auch hier fügen wir über den Eintragseditor ein paar Einträge hinzu. Bei mir heißen diese „Einstellung 1“, „Einstellung 2“ und „Einstellung 3“. Danach setzen wir die „Width“-Property dieser ListBox auf 0, so das diese nicht mehr sichtbar ist.

Zum Schluss legen wir noch ein Layout auf die Form, welches ich „layModal“ genannt habe. Dieses Layout bekommt als Align „alBottom“ und enthält ein Rectangle, welches wiederum einen Button und eind Label enthält. Das Rectangle setzen wir auf „alClient“, damit es den gesamten Bereich des Layouts einnimmt. Außerdem entfernen wir alle Haken bei „Corners“ und „Sides“ und setzen dann unter „Fill“->“Color“ die Farbe auf „White“.
Wozu brauchen wir das Rectangle überhaupt? Je nachdem welche Komponenten auf dem modalen Layout liegen, werden die darunter liegenden Komponenten nicht verdeckt. Durch ein weißes Rectangle umgeht ihr dies.
Zum Schluss setzen wir den „Height“-Wert des modalen Layouts auf 0, so dass auch dieses nicht mehr sichtbar ist.

In das erste Layout, in dem die „Haupt“-ListBox ist, fügen wir noch eine Toolbar ein, welche einen Button enthält. Den Button setzen wir mittels der Eigenschaft „StyleLookup“ auf „addtoolbutton“. Die Toolbar erhält auch noch einen zweiten Button, der als Align „alLeft“ hat und als StyleLookup „detailstoolbutton“.

Außerdem brauchen wir noch 2 Float-Animations. Eine muss in der 2en (Einstellungs)ListBox liegen, die andere im layModal. Die FloatAnimation für die Einstellungen erhält für die Eigenschaft „PropertyName“ den Wert „Width“, die für den modalen Dialog „Height“.

Damit sind wir mit der GUI fertig, diese sollte dann in etwa so aussehen:

Nun brauchen wir noch folgenden Code:

Der Button für die Einstellungsleiste:

if (this->ListBox2->Width == 200)
{
	this->FloatAnimationEinstellungen->StartValue = 200;
	this->FloatAnimationEinstellungen->StopValue = 0;
}
else
{
	this->FloatAnimationEinstellungen->StartValue = 0;
	this->FloatAnimationEinstellungen->StopValue = 200;
}
	
this->FloatAnimationEinstellungen->Start();

Der Button für den modalen Dialog:

this->FloatAnimationModal->StartValue = 0;
this->FloatAnimationModal->StopValue = this->Height;
this->FloatAnimationModal->Start();

Der Button im modalen Dialog:

this->FloatAnimationModal->StartValue = this->Height;
this->FloatAnimationModal->StopValue = 0;
this->FloatAnimationModal->Start();

Damit sind wir durch. Hier noch das (zugegeben nicht schönste) Ergebnis:

Falls ihr in Zukunft auch solche Tutorials als Videso haben möchtet hinterlasst mir einfach einen Kommentar 🙂

19
Jan

[Xcode]: Hallo Welt für iOS

Heute möchte ich euch eine kleine Einführung in die iOS-Programmierung geben. In Form eines Hallo Welt zeige ich ein wenig wie man mit Xcode 5 umgeht.
Was man nun alles braucht um für physikalisches iOS Gerät zu programmieren werde ich hier nicht aufzählen. Dazu gibt es schon genügend Auskunft im Internet. Hier möchte ich einfach nur kurz und einfach ein „Hallo Welt“-Programm machen.

Projekt erstellen
Vorrausgesetzt ihr habt Xcode bereits über den Appstore heruntergeladen und die benötigten SDKs installiert, startet ihr nur noch Xcode und klickt auf „Create a new Xcode project“. Im darauf folgenden Dialog wählt ihr links unter „iOS“ „Single View Application“:

Mit einem Klick auf „Next“ geht es weiter. In das Feld „Product Name“ geben wir den Namen ein den unsere App haben soll (in unserem Fall also „Hallo Welt“). „Organization Name“ ist der Name der Organisation, welcher im App Store später angezeigt wird. „Company-Identifier“ sollte ein eindeutiger Name sein. Im Normalfall ist es eine URL in umgekehrter Reihenfolge. Bei mir also „com.chrisblog“. Zum testen ist es aber eigentlich vollkommen egal was ihr dort hinein schreibt. „Class-Prefix“ lassen wir mal frei und bei „Devices“ wählen wir „iPhone“.

Anschließend sucht ihr noch einen geeigneten Speicherplatz für euer Projekt und es kann losgehen.

Die ersten Schritte
Als erstes wählt ihr auf der linken Seite die Datei „Main.storyboard“ aus und öffnet diese mit einem einfachen Klick. Rechts solltet ihr die Utilities sehen. Ist dies nicht der Fall klickt in der rechten oberen Ecke auf „Hid or Show the Utilities“.

Sobald die Utilities sichtbar sind, müsst ihr im unteren Bereich auf den Würfel klicken, damit die Komponenten sichtbar sind. Nun gebt ihr ganz unten „Label“ ein und zieht ein Label auf den linken ViewController. Danach das gleiche mit einem Button. Mit einem Doppelklick auf den Button ändern wir den Text in „Hallo Welt!“ Das fertige „Interface“ sollte in etwa so aussehen:

Da Xcode auch schon die passenden Implementierungsdateien für uns erzeugt haben, aktivieren wir den Assistang-Editor der im rechten oberen Bereich mit dem Button der wie ein Smoking aussieht aktivieren kann. Im normalfall wird euch dann rechts neben dem Storyboard die Datei „ViewController.m“ angezeigt. Im oberen Bereich des Editors müssen wir auf die Datei „ViewController.h“ umschalten.
Nun zieht ihr mit gedrückter „ctrl“-Taste eine Verbindung vom Label in die Datei unter die Zeile „@interface ViewController : UIViewController“, ebenso macht ihr das mit dem Button.

Die beiden aufploppenden Dialoge werden wie folgt ausgefüllt:

Die Datei „ViewController.h“ sollte nun wie folgt aussehen:

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController
@property (weak, nonatomic) IBOutlet UILabel *theLabel;
- (IBAction)DoIt:(id)sender;

@end

Nun kann der Assistant-Editor wieder deaktiviert werden. Öffnet die Datei „ViewController.m“ indem ihr einmal darauf klickt.
Xcode hat durch die „Action“ netterweise gleich eine Funktion angelegt. In dieser können wir nun unseren Code schreiben:

- (IBAction)DoIt:(id)sender {
    [self.theLabel setText:@"Hallo Welt!"];
}

Die Funktion hat den Rückgabewert „IBAction“, heißt „DoIt“ und hat als Parameter einen sender. Alles was wir nun tun müssen ist dem Label den Text zuweisen.
Dazu rufen greifen wir auf die aktuelle Instanz „self“ zu und greifen dort wiederum auf „theLabel“ zu. Eine Funktion in Xcode wird so aufgerufen:

[objekt funktion:parameter];

Man öffnet eine eckige Klammer, gibt das Objekt an von welchen man eine Funktion ausführen möchten. Nach einem Leerzeichen folgt der Funktionsname, nach einem Doppelpunkt der erste Parameter danach mit Komma getrennt können weitere Parameter kommen.
Wir rufen nun also die Funktion „setLabel“ des Objekts „theLabel“ auf und weisen den Text „Hallo Welt!“ zu. Mit „cmd + R“ könnt ihr das Projekt nun kompilieren.

Sobald das Projekt kompiliert ist startet der Simulator und dieser dann die App. Sobald die App gestartet ist braucht ihr nur noch auf den Button klicken und schon steht in eurem Label „Hallo Welt!“.

18
Jan

[Vorstellung]: MedienVerwaltung(Firemonkey) – iPhone-Client

Heute stelle ich euch den iPhone-Client zu meiner MedienVerwaltung vor. Alles wichtige auf der Client-Seite habe ich ja bereits schon im vorherigen Beitrag erwähnt.

Da ich noch nicht so viel mit iOS-Programmiert habe und dies eine erste Beispiel-App werden soll, ist das Design auch hier noch nicht Perfekt.
Mit was ist die App programmiert?
Auch hier wieder wie beim Desktop-Client:
Als IDE wird der C++ Builder von Embarcadero verwendet, als Framework Firemonkey.

Holen der Daten
Sehen wir uns als erstes mal den Startbildschirm an:

Wie bei iOS so oft, haben wir also als erstes nur eine Listendarstellung. Beim ersten Start ist die Liste noch leer. Nach einem Tipp auf das Aktualisieren-Symbol rechts oben, verbindet sich das iPhone mit dem Internet und lädt die Daten aus meiner MySQL-Datenbank herunter. Diese werden dann auf dem iPhone wiederum in einer SQLite-Datenbank gecached. Wie zu erwarten werden beim ersten herunterladen sämtliche Datensätze geholt.
Bei erneutem Aktualisieren werden nur neue Datensätze (mit einer höheren ID) oder Datensätze mit dem Update-Kennzeichen heruntergeladen.
Nach erneutem start der App werden die Daten gleich angezeigt. Da diese lokal auf dem Gerät gecached sind, dauert dies auch nicht lange. Wie man auf dem Bild sehen kann ist natürlich auch eine Suche integriert.

Detailansicht
Tippt man nun auf einen Eintrag kommt man in die Detailansicht:

Wie schon auf dem Desktop-Client gibt es auch hier Bilder. Wenn die Detailansicht geladen wird, schaut die App erstmal ob es das Bild lokal bereits gibt. Ist dies der Fall, wird es geladen und angezeigt. Andernfalls wird eine Verbindung mit dem Webserver hergestellt. Ist die Datei auf diesem vorhanden, wird die Datei per FTP auf das Gerät geladen und auch wieder gecached. Von dort aus wird das Bild dann wieder geladen.

17
Jan

[Vorstellung]: MedienVerwaltung (Firemonkey) – Desktop-Client

Heute möchte ich euch ein kleines Projekt vorstellen, an dem ich momentan arbeite. Noch bin ich mir nicht sicher ob ich es auch (wenn es mal soweit ist) zum Download bereitstelle. Momentan bin ich der Meinung eher nicht. Wieso zeige ich es euch dann? Vielleicht kann sich ja der eine oder andere ein paar Anregungen holen.

In diesem Beitrag stelle ich euch den aktuellen Status des Desktop-Clients vor.

Mit was Programmiere ich? Für mein Projekt „MedienVerwaltung“ verwende ich den C++ Builder von Embarcadero. Als Framework kommt das Crossplatform-Framework Firemonkey zum Einsatz.
Das Programm ist momentan noch in der „Alpha-Phase“. Es funktioniert also erst ein Teil des späteren Gesamtpakets.

Momentan arbeite ich erstmal an den Filmen. Dies ist für mich erstmal das wichtigste.

Hauptanzeige
Wie man auf den Bildern sehen kann, wird im Grid noch so ziemlich alles angezeigt, was auch in der DB steht. Einfach um zu sehen ob alles richtig gespeichert, geupdatet, usw. wird.

Dazu gleich ein paar Infos wie die Dateien gespeichert werden. Ich verwende eine SQLite-Datenbank. Mithilfe von FireDAC greife ich auf die SQLite-Datenbank zu. Für Status, Genre und Medium gibt es jeweils eine eigene Tabelle in der beliebige Werte gespeichert werden können. Da ich keine Datensätze lösche, gibt es einfach nur ein Gelöscht-Datum. Sobald dort ein Datum steht, wird der Datensatz nicht mehr angezeigt, außer im oberen Bereich wird das „Gelöschte anzeigen“-Häckchen gesetzt (und nein, dass ist nicht die Endgültige Position, mir geht es in erster Linie um die Funktionalität, danach werde ich mich etwas näher mit dem Design beschäftigen).

Wieso lösche ich keine Datensätze? Da ich für das Programm auch eine App habe (die schon so einigermaßen funktioniert), muss ich das ganze ja synchronisieren. Momenten schicke ich also neue/geänderte Datensätze per JSON an ein PHP-Skript, welches die Daten in eine MySQL-Datenbank schreibt. Um Probleme mit den IDs zu vermeiden werden deshalb keine Datensätze gelöscht. Ich hole mir immer die letzte ID aus der MySQL-Datenbank und alle Datensätze am Client die eine höhere ID haben (oder ein Update-Kennzeichen) werden an das PHP-Skript geschickt.

Dafür sind auch die Spalten „U-Mobil“ = UpdateMobil und „U-Desktop“ = UpdateDesktop. Wie gesagt werden diese Spalten zur Zeit nur wegen dem Testen angezeigt. Ändere ich also etwas am Desktop-Client wird der Wert bei UpdateMobil auf „1“ gesetzt und wenn ich auf Synchronisieren klicke weiß mein Programm „Okay, diesen Datensatz muss ich in die Cloud schicken“.

Erfassen von neuen Filmen
Da die restlichen Spalten selbsterklärend sein sollten sehen wir uns nun den Erfassen-Dialog etwas näher an:

Wie man sehen kann, können auf dem Dialog (der zugegebenermaßen auch noch ein wenig verschönert werden kann) alle benötigten Daten erfasst werden. Dazu gehören Bezeichnung, Status, Medium, Genre, Medium, Preis, Kaufdatum. Rechts wird ein Bild des jeweiligen Filmes angezeigt.
Mit einem Klick auf übernehmen werden die Daten in der DB abgelegt und ein neuer Film kann erfasst werden. Mit Ok werden die Daten ebenfalls übernommen und der Dialog geschlossen.

Wie man rechts außerdem sehen kann gibt es ein Rechteck mit einem Pfeil darin. Zieht man dort eine Datei (.jpg) hin, wird diese in das Verzeichnis in das Programm liegt in einer Ordnerstruktur abgelegt. Diese sieht in dem Beispiel etwa so aus:
„Verzeichnis des Exe-Programms\Filme\Bilder\BluRay\2012.jpg“. Außerdem werden alle Sonderzeichen entfernt, welche im Dateinamen nicht erlaubt sind und Umlaute ersetzt (z. B. „ä“ durch „ae“) um Probleme beim FTP-Upload und -Download zu vermeiden.
Außerdem wird gleichzeitig ein Thumbnail erzeugt, welcher dann auf den Webserver geladen wird.

Neue Genres anlegen
Wie auf dem letzten Bild zu sehen ist, gibt es einen Button „Genre hinzufügen“. Sobald man diesen anklickt, erscheint folgender Dialog:

Wie mann sehen kann, können hier neue Genres hinzugefügt und alte entfernt werden. Durch einen Klick auf „Übernehmen“ wird auch die Combobox im Erfassen-Dialog gleich aktualisiert, damit man schon eingegebene Daten nicht nochmal eingeben muss. Auch die Genres kann man Synchronisieren, damit diese in der Cloud zur Verfügung stehen.

30
Dez

C++ Builder + iOS: Versenden von E-Mails

Vielleicht geht es einigen anderen genauso wie mir. Nun kann man endlich mit dem C++ Builder für iOS Programmieren, was richtig cool ist. Manche Sachen sind in den Beispielen schon ganz gut erklärt. Andere wieder nicht. Diese muss man sich dann selbst zusammensuchen. So z. B. beim versenden von E-Mails. Darum möchte ich mein Wissen auch gerne mit euch Teilen.

Dazu erstellen wir im C++ Builder als erstes eine neue Mobile Firemonkey-Anwendung.

Ich arbeite für dieses Beispiel nur mit dem vorerstellten Dialog. Als erstes müssen wir in die Headerdatei folgende Includes einfügen:

#ifndef WIN32
#include >iOSapi.UIKit.hpp<
#include >iOSapi.CocoaTypes.hpp<
#endif

Der Einfachheit halber habe ich jetzt einfach ein #ifndef WIN32 darum gemacht. Somit wird dieser Code immer ausgeführt, sobald die Platform nicht Windows 32 Bit ist. Wollt ihr das Programm auch als 64 Bit compilieren, müsst ihr natürlich den entsprechenden ifdef für iOS machen.

Nachdem wir die Includes haben, brauchen wir 2 Funktionsdefinitionen:

	#ifndef WIN32
	_di_UIApplication SharedApplication(void);
	_di_NSURL StrToNSUrl(String str);
	#endif

Shared Application wird für die Mail-App benötigt (um Mails versenden zu können, muss man nämlich auf die Mail-App von iOS zugreifen). Als Rückgabewert haben die zwei beiden Funktionen einen Datentypen mit _di_ vorangestellt (welche für Delphi-Interface stehen).
StrToNSUrl wird für das umwandeln des Strings benötigt (NS sind iOS Datentypen).

Die ausprogrammierten Funktionen sehen dann wie folgt aus:

#ifndef WIN32
_di_UIApplication TFrameKontaktDetail::SharedApplication(void)
{
	return TUIApplication::Wrap(TUIApplication::OCClass->sharedApplication());
}
//---------------------------------------------------------------------------

_di_NSURL TFrameKontaktDetail::StrToNSUrl(String str)
{
	return TNSURL::Wrap(TNSURL::OCClass->URLWithString(NSSTR(str)));
}
//---------------------------------------------------------------------------
#endif

Um eine Mail zu versenden fehlen nur noch 2 weitere Zeilen:

#ifndef WIN32
String strURL = "mailto:tester@googlemail.com?subject=Test&body=Gesendet%20von%20iOS";
this->SharedApplication()->openURL(StrToNSUrl(strURL));
#endif

Die erste Zeile sagt: Öffne eine neue E-Mail an den Empfänger „tester@googlemail.com“ mit dem Betreff „Test“ und dem Text „Gesendet von iOS“.
Falls Ihr im Betreff oder als Text etwas mit Leerzeichen o. ä. übergeben wollt müsst ihr wie man sehen kann, die entsprechenden Escape-Sequenzen für HTML verwenden (%20 z. B. für Leerzeichen).
Sollte es nicht funktionieren, liegt es wahrscheinlich daran, dass kein E-Mail-Account eingerichtet ist. Dies ist nämlich Voraussetzung dafür.

Das wars auch schon 🙂

14
Nov

C++11 – Ein paar neue Features vorgestellt

Heute möchte ich ein paar Features vorstellen die mit dem neuen Standard gekommen sind.

Initialisieren

Sehr praktisch ist z. B. das Initialisieren in der Headerdatei. Somit kann man direkt beim deklarieren einen Initialwert zuweisen:

class MyTest
{
   private:
      std::string strName    = "Mustermann";
      std::string strVorname = "Max";
}

auto

Als nächstes das auto-Keyword. Damit muss man den Typ nicht mehr selbst definieren:

auto i = 5;
auto str = "Test";
auto db = 10.5;

So ergibt die erste Zeile z. B. als Typ einen Integer, die zweite einen String und die dritte Zeile einen Double. Richtig Interessant wird das auto-Keyword aber erst in Kombination von Lambda-Funktionen bzw. Vectoren. Fangen wir mit den Lambda-Funktionen an.

Lambda

Was ist eine Lambda-Funktion? Eine Lambda-Funktion ist eine anonyme Funktion, die Innerhalb einer anderen Funktion deklariert werden kann. So kann man leicht Hilfsfunktionen basteln, die man nur in einer bestimmten Funktion braucht. Um diese aber einfach zu verwenden müsste man sich einen Funktionszeiger deklarieren. Das würde dann in etwa so aussehen:

typedef void(FunctionPointer)(std::string);
FunctionPointer *p;
// Hier wird eine Lambdafunktion erzeugt und dem Funktionszeiger "p" zugewiesen
p = [](std::string strName) -> void { std::cout < < "Hello " << strName.c_str(); };
p("Christian");

Ich habe mir also einen Funktionszeiger deklariert. Diesem weise ich eine Lambda-Funktion zu, welche nichts anderes macht als ein „Hello Name“ auszugeben. Wie muss man die Lambda-Funktion verstehen? Von außen können Variablen übernommen werden:

[=] Die äußeren Werte werden kopiert, heißt: Bei einer Variable int x, die in meiner Klasse steht, kann ich mit einer Kopie des Wertes in der Funktion arbeiten
[&] Auf die äußeren Werte kann per Referenz zugegriffen werden

Außerdem sind Kombinationen möglich:

[&, =db] Auf alle Werte wird per Referenz zugegriffen, außer auf „db“, hier wird mit einer Kopie gearbeitet.

Nach den eckigen Klammern wird angegeben, welche Parameter die Funktion nimmt (hier einen String in dem ein Name übergeben werden soll). Mit „-> void“ legt man fest welchen Rückgabewert die Lambda-Funktion hat. Im Fall von „void“ kann der Rückgabewert auch weggelassen werden. Danach kommt in geschweiften Klammern der Programmcode, der ausgeführt werden soll.

Wie gesagt, wird der Funktionspointer aber mit dem auto-Schlüsselwort nicht mehr benötigt, was es für uns um einiges einfacher macht:

auto p = [](std::string strName) { std::cout < < "Hello " << strName.c_str(); };
p("Christian");

Vector mit auto

Ziemlich cool wird es auch mit den Vectoren. Wer mit diesen arbeitet wird mit Sicherheit solche Konstrukte kennen:

std::vector<std::string> myVec;
myVec.push_back("Test1");
myVec.push_back("Test2");
myVec.push_back("Test3");
myVec.push_back("Test4");
myVec.push_back("Test5");

for(std::vector</std::string><std::string>::const_iterator cit = myVec.begin(); cit != myVec.end(); ++cit)
   std::cout < < (*cit).c_str() << std::endl;

Die for-Schleife kann man jetzt ganz einfach und abkürzen:

   for(std::string str : myVec)
      std::cout < < str.c_str() << std::endl;

Und nochmal vereinfachen kann man es mit dem auto-Schlüsselwort:

   for(auto str :myVec)
      std::cout < < str.c_str() << std::endl;

Dies war zwar nur ein kleiner Ausschnitt der neuen Features, allerdings die, die ich persönlich mitunter am besten Finde.

10
Okt

Update per View? Ja, das geht!

Letztens war ich doch etwas sprachlos. Vor ca. einer Woche haben wir herausgefunden, dass per View auch Updates/Inserts möglich sind. Eigentlich waren wir bisher immer der Meinung View = nur lesen. Wieso sollte man denn auch auf etwas anderes kommen? Man nimmt Views eigentlich immer nur zum lesen her. Auch der „deutsche Name“ („Sichten“) wahrt den Anschein. Sicht = etwas zum sehen und nicht zum verändern (meiner Meinung nach zumindest).

Okay, so ganz ohne Einschränkung funktioniert das dann natürlich auch nicht. Zum löschen wird z. B. ein Trigger benötigt oder ohne Trigger ein etwas anderes SQL-Statement als gewohnt. Updates und Inserts funktionieren dafür allerdings einwandfrei. Allerdings müssen die Spaltennamen auf die man einen Insert/Update macht eindeutig sein. Solang man also einen Insert/Update auf im View(also Sichtbare) enthaltene Spalten macht, dürfte das kein Problem sein. Das wurde dann auch gleich ausprobiert und siehe da tatsächlich. Macht man nun auf eine Spalte einen Update auf die man eigentlich gar nicht will, hat man unter Umständen irgendwo etwas geändert wo man eigentlich gar nix machen wollte und man vllt. auch gar nicht weiß in welcher Tabelle das steht. Ziemlich gefährlich. Aber da haben wir wieder was dazu gelernt.

Zur Zeit programmiere ich ja wieder ein wenig an meiner MedienVerwaltung. In diesem Zuge schreibe ich nebenbei auch einen kleinen CodeGenerator für die Datenbankklassen (SQLite). Da ich aber sowohl zu Hause als auch manchmal Mittags in der Arbeit daran arbeite, Tausche ich die Projekte meistens über Dropbox aus. Bietet sich halt an. Nun legt der C++ Builder für jede Datei Historien-Dateien an, damit man auf einen früheren Stand zurückhüpfen kann (keine Ahnung ob man das Ausschalten kann). Dies werden aber dann ganz schnell ganz viele Dateien. Und je mehr Dateien um so länger dauert der Upload. Also habe ich mir Kurzerhand einen „HistoryCleaner“ geschrieben. Der läuft einfach sämtliche Ordner Rekursiv durch und löscht evtl. vorhandene „__history“-Ordner. Eigentlich ziemlich simpel und einfach.

Programm kurz darübersausen lassen und schon sind aus ~200 Dateien knapp 80 geworden 🙂

20
Sep

Rad Studio XE5 entwickeln für Android mal anders

Seit kurzer Zeit gibt es nun endlich das Rad Studio in der Version XE5. Bisher konnte man mit dem Compiler von Embarcadero nur Programme/Apps für Windows, Mac und iOS schreiben. Wobei letzteres wieder auf Delphi beschränkt ist. Mac funktioniert auch mit C++.
Im laufe des Winters soll Gerüchteweise auch der Compiler für C++ kommen.
Zurück zum Thema. iOS ist ja schon ein alter Hut und Mac OS noch älter. Also im Zusammenhang mit dem Compiler meine ich natürlich. Denn mit der neuesten Version kann man auch für Android Programmieren. Natürlich bisher wieder nur in Delphi. Da ich aber momentan Android-User bin und es mit dem Google-Betriebsystem viel einfacher ist (es wird kein kostenpflichtiger Developer-Account wie bei Apple benötigt), bin ich natürlich daran sehr interessiert und habe mich auch schon ein wenig damit beschäftigt.

Am Anfang ist Delphi jedenfalls etwas Gewöhnungsbedürftig. Das legt sich dann mit der Zeit allerdings auch wieder. Nach etwas Einarbeitungszeit kommt man auch mit Delphi ganz gut klar.

Was ist die Vorraussetzung damit man Apps auf das Android-Gerät bringen kann? Das Gerät braucht mindestens eine ARMv7 CPU, welche neon unterstützt. Sonst funktioniert das ganze nicht. Soll heißen diese Apps laufen z. B. auf keinem Motorola Razr i.

Was ist dafür der Vorteil? Die Controls werden selbst gezeichnet. Es werden also keine nativen Komponenten verwendet, was diese Apps sehr flexibel macht. Natürlich nehmen die Buttons usw. das aussehen von nativen Controls an. Was ist aber der große Vorteil durch dieses Firemonkey genannte Framework?
Nunja, eine Sache die ziemlich cool ist. Mit nur wenigen Unterscheidungen im Code kann man die App auch auf einem iPhone laufen lassen, ohne großen Aufwand zu haben. Auch 3D-Anwendungen lassen sich damit schreiben. Als Grundlage des Firemonkey-Framworks wird dafür im Mobilen Bereich OpenGLES verwendet.

Ich habe mich nun auch schon einige Stunden damit beschäftigt und ich muss zugeben, es gefällt mir sehr gut. Auch wenn mir noch ein paar Sachen fehlen. Aber das kommt bestimmt nach und nach. Dafür geht es z. B. super einfach ein Foto zu schießen und bestimmte Effekte darauf anzuwenden usw.

Einzig und allein mit meinem HTC One bin ich etwas skeptisch. Nicht wegen des Compilers usw. sondern eher wenn man das Gerät ständig an den Rechner anstöpselt. Und zwar wegen des Akkus. Steckt man das Gerät nämlich am PC an fängt es auch gleichzeitig an den Akku zu laden. Wenn ich nun öfter mal nur kurz probieren möchte eine App auf das One zu spielen wird der Akku auch gleich wieder geladen. Das One hat wie die meisten wissen natürlich keinen wechselbaren Akku. Das heißt der Akku wird zusätzlich strapaziert, ich habe aber keine Möglichkeit diesen selbst zu wechseln falls er kaputt geht…
Allerdings steht demnächst auch meine Vertragsverlängerung an. Nun bin ich schon am überlegen. Ich habe auch schon einen Blick auf das Samsung Galaxy Note 3 geworfen. Falls ich mir das Gerät nehme, habe ich natürlich den großen Vorteil, dass es einen wechselbaren Akku hat und ich mir zumindest darum keine Sorgen machen brauche. Damit hätte ich auch gleich ein Entwicklungsgerät.

Andererseits habe ich auch ein Auge auf das iPhone 5s geworfen. Somit hätte ich von den 2 Großen Betriebssystemen jeweils ein Gerät. Oder auch ein iPad wär eine Option. Aber das ist wieder ein anderes Thema.

29
Aug

C++ – Freedb DiscID ermitteln

Ich arbeite zu Hause zur Zeit an einer kleinen MedienVerwaltung. Logisch, dass dort auch CDs erfasst werden sollen. Allerdings wäre es natürlich cool, wenn das Programm die CD und die Titel eigenständig ermitteln kann. Also was liegt nahe? Natürlich freedb. In diesem Tutorial beschreibe ich deshalb wie Ihr am schnellsten die DiscID einer CD ermittelt und die dazugehörigen Titel abruft.

Ich verwende dafür wie üblich den C++ Builder und verwende für die Webanfragen Indy. Genau, ich greife nicht per telnet auf freedb zu, sondern per http.

Damit sind wir bei der ersten Hürde. Wie kann ich über http auf freedb zugreifen? Das hat mich einen Abend gekostet. Doch schlussendlich hat es funktioniert. Wer sich erstmal ein wenig mit dem Protokoll beschäftigen will und evtl. erst einen Test per telnet machen will, kann sich ja hier mal einlesen.

Wichtig beim Zugriff per http ist, dass der „Handshake“ jedesmal übermittelt wird. Wollen wir also die auf dem Server vorhandenen Musikkategorien auflisten, sieht der Link wie folgt aus:

http://freedb.freedb.org/~cddb/cddb.cgi?cmd=cddb+lscat&hello=chris+chris.blog.com+MV+v0.1&proto=1

Als erstes kommt der Server, freedb.freedb.org ist ein Random-Server. Gefolgt von ~cddb/cddb.cgi. Von dort aus kann man dann auf die „API“ zugreifen. Mit „cmd=“ wird das Commando eingeleitet. Darauf folgt das Kommando wie es genau beim telnet auch eingegeben werden würde, der einzige Unterschied dabei ist, dass anstatt Leerzeichen ein „+“ verwendet wird. Mit „&hello=“ wird der Handshake eingeleitet. Der erste Parameter ist Name, gefolgt von URL/Domain, Programm, Version. Eigentlich ist es ziemlich egal was dort eingegeben wird.
Zum Schluß wird mit „&proto=“ noch die Protokollversion angegeben, die verwendet werden soll. Dies könnt Ihr im bereits o. g. Link nachlesen.

Als Ergebnis des obigen Links erhaltet Ihr folgende Liste:

210 OK, category list follows (until terminating `.')
data
folk
jazz
misc
rock
country
blues
newage
reggae
classical
soundtrack
.

Okay, alles noch ganz einfach bis jetzt. Aber wie geht es weiter? Theoretisch könnte man sich die DiscID selbst berechnen. Aber wieso sollte man das tun wenn es von freedb eine Abfrage dafür gibt. Man macht sich das Leben doch gern etwas einfacher. Was brauchen wir also für die Abfrage der DiscID?
Als erstes brauchen wir die Anzahl der Lieder die auf der entsprechenden CD sind. Danach pro Lied die Offsets wo dieses startet. Zum Schluss noch die Länge der gesamten Spielzeit der CD in Sekunden. Okay, was ist ein Offset? Diese Frage habe ich mir auch erstmal gespielt.
Ganz einfach gesagt, um nicht lange das fachliche zu erklären: Sekunden des Songs * 75 = Offset des Songs. Also Minuten * 60 * 75 + Sekunden * 75 + Frames. Dann habt Ihr das Offset des Songs.

Wie aber kommt man am leichtesten an die benötigten Infos? Ich habe 2 Tage lang etliche Seiten durchsucht. Um den einfachsten Weg zu finden um an diese Daten zu kommen. Und das Zauberwort heißt MCI. Ich weiß zwar nicht ob dies die eleganteste Lösung ist, jedenfalls ist es die einfachste.

Also fangen wir mal an. Als erstes ermitteln wir die Anzahl der Audiotracks. Folgende zusätzliche includes und ein pragma link werden benötigt:

#include &lt;mmsystem .h&gt;
#include &lt;stdio .h&gt;

#pragma link "Winmm.lib"

Dann wie gesagt als erstes die Anzahl der Tracks ermitteln:

   int nAnzahlTracks;
   wchar_t strErgebnis[50];
   MCIERROR mciErgebnis;

   // Anzahl der Tracks ermitteln
   mciErgebnis = mciSendString(L"status cdaudio number of tracks", strErgebnis, sizeof(strErgebnis), 0);

   // Falls ein Fehler aufgetreten ist, Funktion verlassen
   if (mciErgebnis != S_OK)
      return;

   nAnzahlTracks = StrToInt(strErgebnis);

Mit „mciSendString“ wird über einen String eine Anfrage geschickt, die die Anzahl der Audio-Tracks auf der CD zurückliefern soll. Das Ergebnis wird in „strErgebnis“ gespeichert, einen eventuell auftretenden Fehler Speichern wir in der Variable mciErgebnis ab.
Ist der Wert in mciErgebnis „S_OK“, hat alles funktioniert und wir können den Ergebnisstring in einen Integer Casten, andernfalls wird die Funktion verlassen.
Die Prüfungen auf „!= S_OK“ lass ich in weiteren Codeteilen weg. Die Prüfung kann aber nach jedem mciSendString durchgeführt werden.

Nachdem wir jetzt die Anzahl der Tracks haben, brauchen wir als nächstes die Offsets. Dazu müssen wir die Offsets jedes einzelnen Tracks ermitteln:

   unsigend uStartMin, uStartSec, uStartFrame
   String strOffsets = "", strMCIBefehl;

   // Alle Titel durchlaufen
   for(int i = 1; i < = nAnzahlTracks; ++i)
   {
      // MCI-Befehl zum ermitteln der Startzeit des Tracks
      strMCIBefehl = "status cdaudio position track " + IntToStr(i);
      // MCI-Befehl sende
      mciSendString(strMCIBefehl.c_str(), strErgebnis, sizeof(strErgebnis), 0);
      // Startminute, -sekunde und -frame aus dem Ergebnis auslesen
      sscanf(((AnsiString)strErgebnis).c_str(), "%02d:%02d:%02d", &uStartMin, &uStartSec, &uStartFrame);
      // Offset berechnen
      uOffset = 60 * 75 * uStartMin + 75 * uStartSec + uStartFrame;
      // An den String anhängen
      strOffsets += "+" + IntToStr(static_cast<int>(uOffset));
   }

Jetzt haben wir die Offsets aller Titel. Jetzt fehlt uns nur noch die Gesamtspielzeit der CD. Diese ermittelt man wie folgt:

   strMCIBefehl = "status cdaudio position track " + IntToStr(nAnzahlTracks);
   mciSendString(strMCIBefehl.c_str(), strErgebnis, sizeof(strErgebnis), 0);
   sscanf(((AnsiString)strErgebnis).c_str(), "%02d:%02d:%02d", &uStartMin, &uStartSec, &uStartFrame);

   strMCIBefehl = "status cdaudio length track " + IntToStr(nAnzahlTracks);
   mciSendString(strMCIBefehl.c_str(), strErgebnis, sizeof(strErgebnis), 0);
   sscanf(((AnsiString)strErgebnis).c_str(), "%02d:%02d:%02d", &uMinLen, &uSecLen, &uFrameLen);

   unsigned uPlayingSeconds = ((uStartMin * 60 * FRAMES_PER_SECOND + uStartSec * FRAMES_PER_SECOND + uStartFrame + uMinLen * FRAMES_PER_SECOND * 60 + uSecLen * FRAMES_PER_SECOND + uFrameLen)) / FRAMES_PER_SECOND;

Da wir jetzt alles haben was wir brauchen können wir nun die URL zusammenbasteln mit der man die DiscID ermitteln kann:

   strGet = "http://freedb.freedb.org/~cddb/cddb.cgi?cmd=discid+" + IntToStr(nAnzahlTracks) + "+" + strOffsets + "+" + IntToStr(static_cast&lt;int&gt;(uPlayingSeconds)) + "&hello=chris+chris.blog.com+mv+v0.1&proto=1";

   ShowMessage(this->IdHTTP->Get(strGet));

Als Ergebnis solltet Ihr sowas ähnliches wie das hier bekommen:

200 Disc ID is 970a920d

Je nachdem welche CD Ihr im Laufwerk habt, kann dies natürlich variieren. Außerdem kann es sein, dass für eine CD mehrere Ergebnisse zurückkommen. Diesen Fall habe ich hier nicht berücksichtigt. Mit diesen Informationen solltet Ihr aber jedenfalls weiterarbeiten können 🙂
Wenn man sich die „Syntax“ ein wenig zu Gemüte führt, ist es eigentlich ganz einfach 🙂

22
Jun

C++ – Templates, eine Einführung

Keine Ahnung wies euch geht. Bis vor kurzem waren Templates für mich ein „schwarzes Tuch“. Naja, ich wusste, dass es sie gibt, ich wusste dass sie unter Umständen praktisch sein können, aber ich habe keine verwendet. In letzter Zeit habe ich mich doch ein wenig damit beschäftigt und siehe da, sehr, sehr Praktisch!
Letztens habe ich ein kleines Tool geschrieben: Ohne Templates jede Klasse ca. 200 Zeilen (es waren 3 oder 4 Klassen). Mit Templates alle Klassen ca. 70 Zeilen + das Template selbst.

Fangen wir mal mit einem einfachen Beispiel an:

#include <iostream>
#include <string>

template<typename T>
void Ausgabe(T myType)
{
   std::cout < < myType << std::endl;
}

int main(void)
{
   Ausgabe("Hello World!");
   Ausgabe(42);
   Ausgabe(42.5);

   return 0;
}

Was haben wir hier? Das Template „Ausgabe“ nimmt den übergebenen Typ entgegen und gibt ihn auf der Console aus. Das funktioniert so bei allen Datentypen, die den ostream-Operator unterstützen. Man kann also Problemlos einen string, int, double, float usw. übergeben. Alles wird ganz normal auf der Console ausgegeben.

Okay, wie sieht es aber mit einer einfachen Klasse aus? z. B.:

struct Person
{
   std::string strVorname;
   std::string strName;
   int         nAlter;
}

Erstmal wird ein Objekt der Klasse in main definiert, dann versuchen wir auch gleich die Ausgabe aufzurufen, die Main-Methode sieht dann wie folgt aus:

int main(void)
{
   Person p;

   p.strVorname = "Max";
   p.strName = "Mustermann";
   p.nAlter = 32;

   Ausgabe("Hello World!");
   Ausgabe(42);
   Ausgabe(42.5);
   Ausgabe(p);

   return 0;
}

Wird das ganze so funktionieren? Natürlich nicht. Erstmal muss der < <-Operator überladen werden. Dies muss Global geschehen:

std::ostream &operator< <(std::ostream &os, const Person &p)
{
   os << p.strVorname << " " << p.strName << " " << p.nAlter;
   return os;
}

Und schon kann „Ausgabe“ auch mit einer „Person“ umgehen. Wieso hat der Operator als Rückgabewert „std::ostream“? Weil sonst die Aneinanderkettung nicht mehr funktionieren würde. Die Fertige Klasse sieht dann wie folgt aus:

#include <iostream>
#include <string>

template<typename T>
void Ausgabe(T myType)
{
	std::cout < < myType << std::endl;
}

class Person
{
	public:
	std::string strVorname;
	std::string strName;
	int         nAlter;
};

std::ostream &operator << (std::ostream &os, const Person &p)
{
	os << p.strVorname << " " << p.strName << " " << p.nAlter;
	return os;
}

int main(void)
{
	Person p;

	p.strVorname = "Max";
	p.strName = "Mustermann";
	p.nAlter = 32;

	Ausgabe("Hello Wordl!");
	Ausgabe(42);
	Ausgabe(42.5);
	Ausgabe(p);

	getchar();
	return 0;
}

Wie funktioniert das ganze? Der Compiler erstellt pro Aufruf eine Funktion mit dem entsprechenden Datentypen. Wir hätten die Funktion Ausgabe jetzt also 4x.
1x für string
1x für int
1x für float
1x für Person