App-Entwicklung für Windows Phone 8–Part 1 (Anlegen, Bilder laden, Anzeige)

Hi! Smile

Nachdem wir uns und unser kleines Projekt beim letzten Blogeintrag vorgestellt hatten, wollen wir nun auch direkt (wenn auch mit leichter Verspätung Winking smile) losstarten!

Bevor wir jedoch ins Visual Studio wechseln, um die App mit euch zu schreiben, hier erst einmal ein kurzer Überblick über den Inhalt dieses Blogeintrags:

Was haben wir vor?

  • Anlegen eines neuen Projektes
  • Entwerfen der Hauptseite
  • Einlesen der Bilder vom lokalen Speicher des Telefons
  • Darstellen der Fotos in einer Liste

 

Anlegen eines neuen Projekts

Am Anfang einer jeden App steht das Anlegen des Projekts im Visual Studio. Der Vorgang dabei ist kinderleicht und sieht so aus:

Ein Klick auf Datei->Neu->Projekt führt uns in den dazugehörigen Dialog, dort wählen wir das Template mit Namen Windows Phone App aus. Nun fehlt jedoch noch ein passender Name für das Projekt (für den Blogeintrag entschieden wir uns für „GeoFotoalbum“) . Falls nötig, kann auch noch der Speicherort des Projekts angepasst werden, diesen belassen wir jedoch in diesem Fall auf dem Standardwert und bestätigen mit OK.

Wie schon im letzten Blogeintrag erwähnt, wählen wir hier natürlich Windows Phone OS 8.0 als Plattform, da wir einige der neuen (Windows Phone 8 exklusiven) Features verwenden werden.

clip_image001 Tipp: Solltet Ihr ausschließlich Features aus Windows Phone 7.X verwenden, so empfiehlt sich momentan noch die Wahl der anderen Zielplattform (Windows Phone OS 7.1), einfach um auch die vielen Millionen Windows Phone 7-Geräte auf dem Markt zu erreichen (Eure App ist natürlich trotzdem voll zu Windows Phone 8 aufwärtskompatibel!).

Nun sollte Eure Projektmappe in etwa so aussehen:

image

Entwerfen der Hauptseite

Die Hauptseite (MainPage) wurde bereits für uns erstellt. Um nun jedoch zu dem Layout zu kommen, welches wir gerne auf der ersten Seite unserer App sehen würden, ist noch einiges zu tun:

Wir öffnen zunächst die zugehörige Datei (MainPage.xaml) in der Code-Ansicht und löschen erst einmal alle unnötigen Controls und Kommentare von der Seite. Nun sollte der XAML-Code der MainPage etwa so aussehen:

 <phone:PhoneApplicationPage ...>    <Grid x:Name="LayoutRoot" Background="Transparent">    </Grid></phone:PhoneApplicationPage>

Nun werden wir uns die Controls suchen, welche wir verwenden wollen. Im Visual Studio finden wir auf der linken Seite den Werkzeugkasten, welcher alle wichtigen Controls enthält.

Als erstes fügen wir der Seite ein Pivot-Control hinzu, indem wir es von der Toolbox in den Designer ziehen. Das Pivot-Control sollte nun im XAML-Code innerhalb des Grids mit dem Namen „LayoutRoot“ erscheinen.

Wir ändern folgende Eigenschaften des Pivot-Controls um es wie gewünscht auf unserer Seite zu platzieren (dabei sorgen die „Alignment“-Elemente dafür, dass das Control den kompletten verfügbaren Platz einnimmt).

 <phone:Pivot Title="Fotos" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">      ...</phone:Pivot>

Nun ändern wir die zwei PivotItems, welche unsere verschiedenen Ansichten der Fotos beinhalten werden. In diesem Blogeintrag werden wir uns jedoch nur auf das erste der beiden (die Listenansicht) konzentrieren, die Darstellung mittels Karte folgt in Teil 2.

Unser XAML sollte nun wie folgt aussehen:

 <Grid x:Name="LayoutRoot" Background="Transparent">  <phone:Pivot Title="Fotos" HorizontalAlignment="Stretch"                 VerticalAlignment="Stretch">    <phone:PivotItem CacheMode="{x:Null}" Header="Liste">      <Grid/>    </phone:PivotItem>    <phone:PivotItem CacheMode="{x:Null}" Header="Karte">      <Grid>        <!--TODO: Implementiere Karte im Teil 3-->      </Grid>    </phone:PivotItem>  </phone:Pivot></Grid>

Einlesen der Bilder

Als Nächstes werden wir ein bisschen C#-Code schreiben, um Bilder vom lokalen Speicher zu lesen und sie dann auf unserer MainPage anzeigen zu können.

Als erstes erstellen wir eine neue Klasse. Dazu klicken wir im Visual Studio auf unser Projekt „GeoFotoalbum“ mit der rechten Maustaste und fügen mit Hinzufügen->Neues Element->Klasse eine neue Klasse mit Namen „Foto.cs“ hinzu. Diese Klasse repräsentiert später genau einen Fotoeintrag in unserer Liste.

Für die Verwendung etwas weiter unten, müssen wir noch die Sichtbarkeit der Klasse ändern, indem wir das Schlüsselwort public vor die Definition schreiben.

Nun fügen wir die privaten Member-Variablen picture, name, vorschaubild, bild, laengengrad und breitengrad hinzu.

 private Picture picture;private string name;private BitmapImage vorschaubild;private BitmapImage bild;private double laengengrad;private double breitengrad;

clip_image001[1] Tipp: Mit großer Sicherheit wird Visual Studio die Klasse BitmapImage nicht auf Anhieb erkennen. Das ist auch völlig klar, denn es wurde dem Compiler ja nirgendwo mitgeteilt, was BitmapImage in diesem Kontext bedeutet. Dies lässt sich ändern, indem wir genau das nachholen. Dafür gibt es nun mehrere Wege, von welchen die einfachste Variante ist, es dem Visual Studio zu überlassen, den richtigen NameSpace einzubinden (dies könnt Ihr über das Kontextmenü, oder dieses Symbol image , welches erscheint, wenn Ihr mit dem Cursor auf BitmapImage verweilt, tun). Natürlich kann man den NameSpace auch manuell angeben, dafür reicht in diesem Fall ein using System.Windows.Media.Imaging; in den Using-Definitionen am Beginn der Datei. Selbiges gilt auch für using Microsoft.Xna.Framework.Media;!

Als nächstes fügen wir die Eigenschaften (Properties) Name, Vorschaubild, Laengengrad und Breitengrad hinzu, um unsere privaten Member auch nach außen geordnet zugänglich zu machen.

 public string Name { get { return name; } }public BitmapImage Vorschaubild { get { return vorschaubild; } }public double Laengengrad { get { return laengengrad; } }public double Breitengrad { get { return breitengrad; } }

Im Konstruktor der Klasse initialisieren wir unsere privaten Member-Variablen, das tatsächliche Einlesen der Metadaten aus den Bildern sparen wir uns jedoch noch für Teil 2 auf.

 public Foto(Picture picture){  this.picture = picture;  this.name = picture.Name;  this.vorschaubild = new BitmapImage();  vorschaubild.SetSource(picture.GetThumbnail());  // TODO (Teil 2): Einlesen von Längen und Breitengrad}

Als letztes implementieren wir die Methode GetBild(), welche uns später das Originalbild zurückgeben wird.

 public BitmapImage GetBild(){  if (bild == null) // Wenn das Bild noch nie eingelesen wurde  {    bild = new BitmapImage();    bild.SetSource(picture.GetThumbnail());  }  return bild;}

Die gesamte Klasse sollte wie folgt aussehen:

 using Microsoft.Xna.Framework.Media;using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;using System.Windows.Media.Imaging;namespace GeoFotoalbum{  public class Foto  {    private Picture picture;    private string name;    private BitmapImage vorschaubild;    private BitmapImage bild;    private double laengengrad;    private double breitengrad;    public string Name { get { return name; } }    public BitmapImage Vorschaubild { get { return vorschaubild; } }    public double Laengengrad { get { return laengengrad; } }    public double Breitengrad { get { return breitengrad; } }    public Foto(Picture picture)    {      this.picture = picture;      this.name = picture.Name;      this.vorschaubild = new BitmapImage();      vorschaubild.SetSource(picture.GetThumbnail());      // TODO (Teil 2): Einlesen von Längen und Breitengrad    }    public BitmapImage GetBild()    {      if (bild == null) // Wenn das Bild noch nie eingelesen wurde      {        bild = new BitmapImage();        bild.SetSource(picture.GetThumbnail());      }      return bild;    }  }}

Darstellen der Fotos in einer Liste

Jetzt fügen wir unserer MainPage ein LongListSelector-Control hinzu, welches später die gelesenen Bilder anzeigen wird.

 <phone:PivotItem CacheMode="{x:Null}" Header="Liste">  <Grid>    <phone:LongListSelector Name="longListSelectorFotos">    </phone:LongListSelector>  </Grid></phone:PivotItem>

Nun erstellen wir das Template der einzelnen Foto-Einträge:
Es besteht aus einem Grid mit 2 Spalten einem Image und einem TextBlock.
Wobei das Image das Vorschaubild und der TextBlock den Namen des jeweiligen Fotos binden werden.

 <phone:LongListSelector Name="longListSelectorFotos">  <phone:LongListSelector.ItemTemplate>    <DataTemplate>      <Grid Margin="2 5 2 5">        <Grid.ColumnDefinitions>          <ColumnDefinition Width="Auto" />          <ColumnDefinition />        </Grid.ColumnDefinitions>        <Image Grid.Column="0" Source="{Binding Vorschaubild}"                 HorizontalAlignment="Center" VerticalAlignment="Center" />        <TextBlock Grid.Column="1" Text="{Binding Name}" Margin="10 0 0 0"                     HorizontalAlignment="Left" VerticalAlignment="Center" />      </Grid>    </DataTemplate>  </phone:LongListSelector.ItemTemplate></phone:LongListSelector>

clip_image002 Exkurs: Binden? Über Data-Binding könnten und wurden bereits ganze Kapitel bzw. Bücher geschrieben, deshalb halten wir uns – um den Rahmen nicht vollkommen zu sprengen – hier nur ganz kurz. Im Kern handelt es sich bei Bindings um einen Weg die UI mit der Business-Logic eurer App zu verbinden. Kurz gesagt sorgt dieses Konstrukt dafür, dass eure UI der Business-Logic „zuhört“ und es dadurch ermöglicht wird, dass sich ein TextBlock seinen Text direkt aus einer Variable zieht, obwohl er lediglich deren Namen weiß, diese jedoch niemals explizit zugewiesen wurde. Dies kann dann auf die Spitze getrieben werden, indem spezielle Interfaces implementiert werden, um eine Variable im Code-Teil der App mit einem Feld im UI-Teil der App beidseitig zu verbinden (Beispiel: Ihr habt eine TextBox, die beidseitig auf einen String im Code-Teil bindet; nun ist es möglich, dass sowohl eine Änderung der Variable, als auch eine Änderung des Werts in der TextBox durch den User zum gegenseitigen Update des jeweiligen Pendants führt – und das alles ohne eine einzige Zeile Code!).

Einlesen der Bilder vom lokalen Speicher des Telefons

Zu guter Letzt lesen wir alle Bilder vom lokalen Speicher unseres Telefons bzw. unseres Emulators ein, um sie in der Liste (und später auch auf der Karte) anzeigen zu können.

Dafür fügen wir dem Code-Behind-File der MainPage (MainPage.xaml.cs) eine neue private Member-Variable mit dem Namen fotos hinzu.

 private ObservableCollection<Foto> fotos;

clip_image002[1] Exkurs: Was sind Code-Behind-Files? Code-Behind-Files sind die Dateien, die sich direkt hinter eurer XAML-Datei befinden. Solltet Ihr einen Button im XAML anlegen, dort ein Click-Event angeben und dieses implementieren wollen, so leitet das Event in das Code-Behind-File der XAML-Page über und ruft dort die passende (von euch, bzw. dem Visual Studio erstellte) Methode auf. Das Code-Behind-File kann somit als das C#-Pendant eures XAML-Codes betrachtet werden – oder, um es einfacher zu sagen: Das Code-Behind-File „klebt“ an eurer XAML-Page und beinhaltet allen direkt zugehörigen C#-Code.

Als nächstes implementieren wir das Auslesen der Fotos aus dem lokalen Speicher des Telefons.

 public void LoadImages(){  MediaLibrary medianbibliothek = new MediaLibrary();  fotos = new ObservableCollection<Foto>(); ;  foreach (Picture picture in medianbibliothek.Pictures)  {    Foto foto = new Foto(picture);    fotos.Add(foto);  }  longListSelectorFotos.ItemsSource = null;  longListSelectorFotos.ItemsSource = fotos;}

Der Kern der Angelegenheit ist das Instanziieren des MediaLibrary-Objekts und das darauffolgende iterieren durch die Liste der Bilder mittels der Foreach-Schleife. Wir überführen die Fotos hier in unsere eigene ObservableCollection, um sie später in der Liste anzeigen zu können.

clip_image002[2] Exkurs: Wieso ObservableCollection? Die ObservableCollection ist im Endeffekt auch nur eine Liste, jedoch hat sie eine spezielle Eigenschaft, welche sie für unseren Einsatzzweck sehr wertvoll macht: sie implementiert die Interfaces INotifyCollectionChanged und INotifyPropertyChanged. Da wir, wie man im obigen C#-Code sehen kann, die ObservableCollection direkt als ItemsSource des LongListSelectors angeben und diese die oben genannten Interfaces implementiert, haben wir den Vorteil, dass der LongListSelector von selbst merkt, wenn sich die Liste ändert (da diese das passende Event aus den Interfaces aufruft) und sich selbst neu zeichnet, wodurch die neuen Items direkt – ohne Zutun unsererseits – in der UI dargestellt werden.

Jetzt müssen wir die neue Methode nur noch aufrufen, dies geschieht am bestem im Konstruktor der MainPage (welcher sich im Code-Behind-File befindet).

     public MainPage()    {      InitializeComponent();      LoadImages();    }

Wenn wir die Anwendung jetzt ausführen würden, wäre die Liste der Bilder jedoch noch leer. Dies liegt daran, dass wir der Anwendung noch nicht die für das Auslesen benötigten Rechte gegeben haben.

Wir öffnen dafür das File GeoFotoalbum/Properties/WMAppManifest.xml im Visual Studio und kreuzen im Reiter Funktionen die Checkbox ID_CAP_MEDIALIB_PHOTO an.

Mit dem grünen Pfeil in der oberen Hälfte des Visual Studio führen wir unsere neue App innerhalb des Emulators aus.

Wenn alles geklappt hat, sehen wir nun folgendes:

image

clip_image001[2] Tipp: Falls die Liste leer ist, liegt der Fehler möglicherwiese gar nicht an eurer App, sondern am Emulator. Abhilfe schafft dabei, wenn man mit der Kamera App des Emulators ein Foto schießt und es danach nochmal erneut versucht.

Das sieht doch für unseren ersten Schritt schon einmal ganz gut aus! Smile Im nächsten Teil werden wir dann die Kartenansicht und das Auslesen der Metadaten behandeln, doch bis dahin soll es erst einmal genug gewesen sein – wir sprengen jetzt schon fast den Rahmen. Winking smile

 

Blogeinträge und Ressourcen

Am Ende dieses Blogeintrags verweisen wir noch einmal auf eine Liste der bisher veröffentlichten Blogeinträge:

und auf den Link zum Download der aktuellen Projektversion (beinhaltet den kompletten Stand der App bis zum Ende dieses Eintrags):

GeoFotoalbum1.zip

Zu guter Letzt

Abschließend möchten wir uns für Eure Aufmerksamkeit und das Durchhalten bis zum Schluss bedanken (wir versuchen uns das nächste Mal etwas kürzer zu halten Winking smile ) und würden uns sehr freuen, euch auch beim nächsten Teil wieder begrüßen zu dürfen! In diesem Sinne: Danke noch einmal und bis zum nächsten Mal