Showing posts with label C#. Show all posts
Showing posts with label C#. Show all posts

Saturday, February 1, 2014

DataBinding “ohne” INotifyPropertyChanged, aber mit Fody

Einleitung

Datenbindung in WPF ohne INotifyPropertyChanged, ja geht denn das? Jein, natürlich nicht. Aber ich möchte euch ein NuGet Package vorstellen, welches euch die Arbeit erheblich erleichtern kann, wenn ihr gerade eine WPF Applikation mit Datenanbindung an die GUI, vermutlich mittels MVVM, beginnen wollt. Legen wir also gleich einmal los.

Den kompletten Beispiel-Code findet ihr hier

DataBinding ohne INotifyPropertyChanged

Zuerst fügen wir eine neue Klasse Employee zu unserem Projekt hinzu, dieser geben wir 2 Properties – FirstName und LastName.

Code:
    class Employee
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
Kümmern wir uns um die GUI, diese sollte so aussehen:
image

An die beiden TextBoxen wollen wir unsere Klasse Employee mittels DataBinding anbinden.
Der XAML Code sieht so aus:
  <Grid>
        <TextBox Text="{Binding FirstName}" 
            HorizontalAlignment="Left" Height="23" Margin="62,43,0,0"  
VerticalAlignment
="Top" Width="120"/>
        <TextBox Text="{Binding LastName}" 
            HorizontalAlignment="Left" Height="23" Margin="62,104,0,0" 
VerticalAlignment
="Top" Width="120"/>
        <Label Content="FirstName" HorizontalAlignment="Left" Margin="62,10,0,0" 
VerticalAlignment
="Top"/>
        <Label Content="LastName" HorizontalAlignment="Left" Margin="62,73,0,0" 
VerticalAlignment
="Top"/>
        <Button Content="OK" HorizontalAlignment="Left" Margin="62,156,0,0" 
           VerticalAlignment="Top" Width="120" Click="Button_Click"/>
    </Grid>

Nun wollen wir noch eine neue (leere) Instanz der Klasse Employee an unser Fenster binden und im Click Event wollen wir die Werte anpassen und in einer MessageBox ausgeben. Im Idealfall sehen wir den neuen Vornamen und den neuen Nachnamen in der MessageBox UND in den TextBoxen (das wäre ja der Sinn der Datenbindung).


Code:
    public partial class MainWindow : Window
{
Employee employee;

public MainWindow()
{
InitializeComponent();

employee = new Employee();
this.DataContext = employee;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
employee.FirstName = "Berndt";
employee.LastName = "Hamboeck";

MessageBox.Show(string.Format("{0} {1}",
                employee.FirstName, employee.LastName), "Employee Info",
MessageBoxButton.OK, MessageBoxImage.Information);
}
}

Wenn wir unser Projekt laufen lassen, so können wir sehr schön erkennen, dass die Werte zwar in der Meldung ausgegeben werden, den Weg aber nicht zu den TextBoxen finden.
image
Ganz klar, werden einige von euch nun denken, kann ja nicht funktionieren, da wir ja noch INotifyPropertyChanged implementieren müssen.
Stimmt, aber genau das wollen wir uns ersparen und werden ein NuGet Package, mit dem Namen PropertyChanged.Fody zu unserem Projekt hinzufügen.

DataBinding mit PropertyChanged.Fody

Öffnen wir also das NuGet Fenster in Visual Studio 2013 und fügen dieses Package hinzu.
image
In dem neuen Fenster sucht Ihr am Besten nach Fody und wählt dann das PropertyChanged.Fody Paket zur Installation aus:
image
Alternativ könnt Ihr das Package auch über die Nuget Packet Manager Console installieren, diese findet Ihr im Menüpunkt Tools.
image
Dort gebt Ihr folgenden Befehl in der Konsole ein:
Install-Package PropertyChanged.Fody

Wie immer Ihr euch entscheidet, das Resultat sollte sein, dass Ihr die Referenz in eurem Projekt wieder findet:
image
Nun müssen wir noch die Annotation [ImplementPropertyChanged] zu unserer Klasse hinzufügen und uns den NameSpace holen.
image

Das Endresultat sieht in der Employee-Klasse dann so aus:



Code:
using PropertyChanged;

namespace FodySample
{
[ImplementPropertyChanged]
class Employee
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
}
Jetzt lassen wir die Applikation noch einmal laufen, und wir sehen, dass die veränderten Werte nun auch in den TextBoxen zu sehen sind.
image
Wenn wir uns den kompilierten Code ansehen, dann können wir erkennen, dass Fody uns einfach nur die Arbeit abnimmt und zwar während der Kompilierung unseres Projektes. Der generierte Code sieht so aus:
Code:
namespace FodySample
{
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Threading;

internal class Employee : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;

public virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler propertyChanged = this.PropertyChanged;
if (propertyChanged != null)
{
propertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}

public string FirstName
{
[CompilerGenerated]
get
{
return this.<FirstName>k__BackingField;
}
[CompilerGenerated]
set
{
if (!string.Equals(this.<FirstName>k__BackingField, value, StringComparison.Ordinal))
{
this.<FirstName>k__BackingField = value;
this.OnPropertyChanged("FirstName");
}
}
}

public string LastName
{
[CompilerGenerated]
get
{
return this.<LastName>k__BackingField;
}
[CompilerGenerated]
set
{
if (!string.Equals(this.<LastName>k__BackingField, value, StringComparison.Ordinal))
{
this.<LastName>k__BackingField = value;
this.OnPropertyChanged("LastName");
}
}
}
}
}


Zusammenfassung

WPF und Datenanbindung und MVVM ist eine tolle Art Applikationen zu entwickeln. Das funktioniert out-of-the-box schon sehr gut, aber das Fody Package kann uns doch einiges an Arbeit abnehmen. Somit bleibt mehr Zeit für die eigentlich wichtigen Dinge in einem Software-Projekt: Das Projekt muss fertig werden und die Wünsche der Anwender möglichst gut abbilden, ohne dass wir Entwickler mit der Technologie zu kämpfen haben.

Saturday, May 11, 2013

Windows Store Split App Template an Echtdaten anbinden

Eines der Templates um eine Windows Store App zu erstellen ist das Split App (XAML) Template.

Dieses ist sofort lauffähig und liefert euch sofort Daten. Diese sind in der Applikation allerdings “harcoded”, das ist für die meisten Zwecke nicht wirklich brauchbar. Wir wollen nun gemeinsam etwas “Dynamik” in das Template bringen und Echtdaten, die frei zur Verfügung stehen anbinden.

Dazu erstelle ich zuerst einmal eine neue Windows Store Splitt App (XAML) und nenne diese SplitAppRealData, und zur Sicherheit lasse ich diese gleich einmal laufen, um die Demodaten "zu begutachten.

SNAGHTML904531b0image

Es gibt einige JSON Quellen, die Daten frei zur Verfügung stellen, eine davon ist die Flickr API. Diese werden wir in diesem Beispiel verwenden.

http://api.flickr.com/services/feeds/photos_public.gne?tags=surf,pipeline&tagmode=all&format=json&jsoncallback=?

Der erste Schritt ist dass wir C# Klassen zu dem JSON-Resultat benötigen ,diese lassen wir uns mit Hilfe der Webseite http://json2csharp.com/ erzeugen.

Dazu postet Ihr einfach das Resultat aus dem Browser auf die Webseite. Das Resultat sind dann folgende Klassen (die ich etwas angepasst habe, damit diese den allgmeinen C#-Coding Standards entsprechen):

    [DataContract()]
public class Media
{
[DataMember(Name = "m")]
public string PreviewUrl { get; set; }
}

[DataContract()]
public class Item
{
[DataMember(Name = "title")]
public String Title { get; set; }

[DataMember(Name = "link", IsRequired = true)]
public String Link { get; set; }

[DataMember(Name = "media")]
public Media Media { get; set; }

[DataMember(Name = "date_taken")]
public string Datetaken { get; set; }

[DataMember(Name = "description")]
public string Description { get; set; }

[DataMember(Name = "published")]
public string Published { get; set; }

[DataMember(Name = "author")]
public string Author { get; set; }

[DataMember(Name = "author_id")]
public string AuthorId { get; set; }

[DataMember(Name = "tags")]
public string Tags { get; set; }
}

[DataContract()]
public class RootObject
{
[DataMember(Name = "title", IsRequired = true)]
public String Title { get; set; }

[DataMember(Name = "link", IsRequired = true)]
public String Link { get; set; }

[DataMember(Name = "items", IsRequired = false)]
public Item[] Items { get; set; }
}

Jetzt ist es an der Zeit die Daten von Flickr zu holen und in dem Template anuzeigen. Dazu muß man die LoadState Methode der ItemsPage.xaml.cs Datei anpassen. Ich habe den “Default-Call” zu SampleDataSource.GetGroups auskommentiert. Stattdessen rufen wir eine Methode LoadItems auf. Diese wiederum ruft eine neue Methode SampleDataSource.GetJsonGroups, die danach implementiert wird.

        protected override void LoadState(Object navigationParameter, Dictionary<String, Object> pageState)
{
// TODO: Create an appropriate data model for your problem domain to replace the sample data
//var sampleDataGroups = SampleDataSource.GetGroups((String)navigationParameter);

LoadItems();
}

private async Task LoadItems()
{
string url =
"http://api.flickr.com/services/feeds/photos_public.gne?tags=surf,pipeline&tagmode=all&format=json&jsoncallback=?";
var sampleDataGroups = await SampleDataSource.GetJsonGroups(url);

this.DefaultViewModel["Items"] = sampleDataGroups;
}
 

Die Methode  GetJsonGroups erledigt die eigentliche Arbeit:



  • Den WebRequest usführen
  • Das Resultat in einen MemoryStream schreiben
  • Deserialisieren des JSON-Strings in die C# Klassen
  • Zurückgeben der Enumeration
        public static async  Task<IEnumerable<Item>> GetJsonGroups(string url)
{
try
{
var client = new HttpClient();
var result = await client.GetStringAsync(new Uri(url, UriKind.Absolute));

MemoryStream stream = new MemoryStream();
//Usually 0 and result.length, BUT
//Flickr adds a ( at the beginning and a ) at the end
stream.Write(Encoding.UTF8.GetBytes(result), 1, result.Length - 2);
stream.Position = 0;
DataContractJsonSerializer ser = new DataContractJsonSerializer(typeof(RootObject));

var rootObject = (RootObject)ser.ReadObject(stream);

return rootObject.Items.ToList();
}
catch (Exception ex)
{
Debug.WriteLine("!EXCEPTION in GetJsonGroups!");
throw;
}
}

Und das war es dann auch schon, wenn man die App laufen lässt, erhält man schon ein durchaus erfreuliches Resultat:


imageimage


Um das Resultat zu verbessern, muss noch das Standard250x250ItemTemplate in StandardStyles.xaml minimal angepasst werden.

    <DataTemplate x:Key="Standard250x250ItemTemplate">
<Grid HorizontalAlignment="Left" Width="250" Height="250">
<Border Background="{StaticResource ListViewItemPlaceholderBackgroundThemeBrush}">
<Image Source="{Binding Media.PreviewUrl}" Stretch="UniformToFill" AutomationProperties.Name="{Binding Title}"/>
</Border>
<StackPanel VerticalAlignment="Bottom" Background="{StaticResource ListViewItemOverlayBackgroundThemeBrush}">
<TextBlock Text="{Binding Title}" Foreground="{StaticResource ListViewItemOverlayForegroundThemeBrush}" Style="{StaticResource TitleTextStyle}" Height="60" Margin="15,0,15,0"/>
<TextBlock Text="{Binding Author}" Foreground="{StaticResource ListViewItemOverlaySecondaryForegroundThemeBrush}" Style="{StaticResource CaptionTextStyle}" TextWrapping="NoWrap" Margin="15,0,15,10"/>
</StackPanel>
</Grid>
</DataTemplate>

Wie man sieht ist es relativ einfach das Standard-Template so aufzubohren, dass es sehr schnell Resultate liefert.

CSharpCodeFormatter