Laden...

[Artikel] WPF (Windows Presentation Foundation) Einführung - aktuell Teil 3: Beispielanwendung

Erstellt von talla vor 17 Jahren Letzter Beitrag vor 17 Jahren 49.632 Views
Thema geschlossen
talla Themenstarter:in
6.862 Beiträge seit 2003
vor 17 Jahren
[Artikel] WPF (Windows Presentation Foundation) Einführung - aktuell Teil 3: Beispielanwendung

Dies ist der Auftakt einer kleinen Serie von Artikeln die in die Windows Presentation Foundation(WPF) einführen und ein erstes Verständniss für diese neue Schnittstelle bieten soll.

Übersicht

Einführung in die WPF(Teil1) - Einführung

Einführung in die WPF(Teil2) - XAML

Einführung in die WPF(Teil3) - Beispielanwendung*Content Model *Events

Diskussion
Da die Artikelserie geschlossen bleibt, gibt es einen extra Diskussionsthread

Diskussion zur Artikelserie Einführung in die WPF(Windows Presentation Foundation)

Baka wa shinanakya naoranai.

Mein XING Profil.

talla Themenstarter:in
6.862 Beiträge seit 2003
vor 17 Jahren
Einführung in die WPF(Teil1) - Einführung

Im ersten Artikel werde ich kurz erläutern was die WPF überhaupt ist, einen kurzen Vergleich zu den Windows Forms ziehen und eine kleine Beispielanwendung vorstellen. In diesen und auch den anderen Artikeln werde ich anfangs immer das Thema vorstellen, ein wenig theoretisch erklären, aber der Schwerpunkt soll dann bei einem Beispiel liegen an dem das Thema erkärt wird und vor allem auch anschaulicher werden soll.
In weiteren Artikeln werden dann einzelne Aspekte wie XAML, DataBinding, Styles und Templates, Events usw. an verschiedenen Beispielen erläutert.
Ziel ist es nach dieser Artikelserie die Prinzipien hinter WPF zu verstehen und WPF Anwendungen schreiben zu können.

Was ist WPF
WPF ist die Abkürzung für Windows Presentation Foundation und bezeichnet die neue Präsentationsschnittstelle für .Net 3.0.
Eingeführt wird sie im Rahmen von Windows Vista, ist aber auch für WinXP und Windows Server 2003 verfügbar.
Für alle Beispiele verwende ich den RC1. Aber noch im Laufe dieses Jahres wird die Final erwartet.

Eigenschaften der WPF
WPF birgt ein paar grundlegende Änderungen für die Entwicklung von Oberflächen für Windows. Die Oberflächen werden jetzt komplett mit über DirectX gerendert und entlasten somit die CPU wenn die Grafikkarte leistungsfähig genug ist. Durch das Rendern mit DirectX werden auch einige Limitierungen von GDI(+) umgangen(wirkliche Tranzparenz, zeichnen auch auf nicht zum Control gehörigen Flächen,...). Außerdem ist das Rendersystem nun vektorbasiert, sprich man kann die Oberflächen beliebig ohne Qualitätsänderungen skalieren. Zur Entwicklung der Oberflächen kann nun auf XAML zurückgegriffen werden. Das ist eine neue XML Sprache zur Beschreibung von Oberflächen, sie wird aber auch in der Windows Workflow Foundation eingesetzt. Auf XAML gehe ich aber in einem gesonderten Artikel ein. WPF Programme können auf 2 Arten deployed werden: Entweder ganz normal als Stand-Alone oder als Webbrowser Anwendung wo sie dann mit eingeschränkten Rechten läuft. Die Medienunterstützung wurde verbessert, es werden jetzt z.b. auch Videos unterstützt. Auch das Textrendering System wurde stark erweitert und kann jetzt ohne Probleme z.b. Ruby Markup anzeigen.

Vergleich Windows Forms/WPF
Die Windows Forms basieren auf GDI+. Das Zeichenparadigma wurde gegenüber GDI+ nun geändert. Ein Beispiel: Viele kennen bestimmt die Probleme die GDI+ mit der Transparenz hat wenn mehrere Darstellungsebenen im Spiel sind, die gibt es in WPF nicht mehr(Das Warum zu erläutern würde doch den Rahmen hier sprengen). Auch waren bei den Windows Forms viele Controls mehr oder weniger einfache Wrapper um die WinApi Pendants. Bei WPF wurden die Controls alle neu implementiert und zwar völlig in managed Code. Die Controls der Windows Forms sind außerdem relativ steif. Um an ihren Aussehen oder an der Funktionalität etwas zu ändern, musste man idr. eine eigene Klasse ableiten und konnte dann neu implementieren.´In der WPF ist dies nun nicht mehr nötig. Funktionalität und Aussehen sind voneinander unabhängig und können beliebig ausgetauscht werden. Dort wählt man das Control nach der gewünschten Funktionalität die man möchte und passt dann einfach das Aussehen im Nachhinein an. Ein weiterer wichtiger Punkt der das eben genannte erst möglich macht: Controls in WPF können beliebige andere Controls enthalten. Das heißt wenn ich einen Button mit Bild haben möchte deklariere ich mir als Buttonchild ein Image und es wird im Button angezeigt. Will ich noch Text dazu, deklarier ich halt als Child noch ein Textfeld und nun wird das Bild und der Text angezeigt. Das eröffnet fast unbegrenzte Möglichkeiten und bietet auch Spielraum für so absurde Dinge wie z.b. einer Listbox als Menüitem usw. Für gerade genanntes Beispiel mit dem Button hätte man mit den Windows Forms noch vom Button erben müssen und wäre zum selber zeichnen verdammt gewesen.

Kleine Beispielanwendung

Download

Nachdem ich nun einfach ein paar Merkmale genannt hab, die im Laufe der Artikelserie wohl um einges anschaulicher werden, möchte ich nun einfach eine kleine Beispielanwendung zeigen. Sie kann nicht viel. Es wird nur ein Fenster geöffnet in der ein Text als Überschrift steht, darunter ein Slider und als letztes ein Button. An sich nichts spektakuläres, aber mit Windows Forms wäre das noch ziemlich mühsam gewesen es so darzustellen wie in dem Beispiel. Die Überschrift hat einen Farbverlauf, der Button ebenfalls(jedenfalls im Ursprung, im Hover und Pressed Status ist der auf Default) und mit dem Slider kann man einfach die Breite des Buttons einstellen. Und achja, der Button glüht leicht 🙂 Und, es wurde keine einzige Zeile C# benötigt! Es ist alles in XAML deklariert. Ich werde dieses Beispiel nicht groß erläutern, es soll einfach nur ein wenig WPF Code zeigen. Um nur das wichtigste zu nennen: Der Farbverlauf der Überschrift wird über ein Style gesetzt, die Controls sind in einem Gridlayout angeordnet, das Glühen des Buttons ist nen einfacher Bitmap Effekt und die Breite des Buttons ist per Databinding mit dem Wert des Sliders verknüpft. Zum Ausführen wird die .Net 3.0 Runtime benötigt. Im weiteren Verlauf sind dann wohl aber auch das Windows SDK und um auch mit dem VS arbeiten zu können, das Orcas CTP nötig.  
  
[.Net 3.0 RC1 Runtime](http://www.microsoft.com/downloads/details.aspx?displaylang=de&FamilyID=19E21845-F5E3-4387-95FF-66788825C1AF)  
[passendes Windows SDK](http://www.microsoft.com/downloads/details.aspx?FamilyId=117ECFD3-98AD-4D67-87D2-E95A8407FA86&displaylang=en)  
[RC1 Orcas CTP](http://www.microsoft.com/downloads/details.aspx?FamilyId=935AABF9-D1D0-4FC9-B443-877D8EA6EAB8&displaylang=en)  
  
Damit wäre der erste Teil auch schon geschafft. Es ist noch nicht viel passiert außer einer allgemeinen EInführung die einfach mal zeigen sollte was WPF so ist. In den nächsten Artikeln wirds dann spannender wenn einzelne Aspekte beleuchtet werden und dann die ganze Macht der WPF zum Vorschein kommt.  
  
Bis dann  
Gruß Talla

Baka wa shinanakya naoranai.

Mein XING Profil.

talla Themenstarter:in
6.862 Beiträge seit 2003
vor 17 Jahren
Einführung in die WPF(Teil2) - XAML

Im nächsten Artikel dieser kleinen Serie gehts nun um XAML.

Ich werde kurz vorstellen was man sich unter XAML vorstellen muss, was es kann/nicht kann und ein Anwendungsbeispiel zeigen an dem ich verschiedene Aspekte von XAML erläutere.

Was ist XAML
XAML steht für XML Application Markup Language, ist also eine XML basierte Sprache. Sie dient primär um Oberflächen für die WPF zu beschreiben, geht aber teilweise drüber hinaus und erlaubt auch einfache Aktionen und Animationen zu beschreiben. Ich setzte für dieses Beispiel vorraus dass XML zumindest in den Grundzügen und Konzepten bekannt ist. XML ist eine sogenannte Metasprache, sprich mit XML alleine lässt sich eher wenig anstellen, aber es ist möglich in XML Sprachen zu beschreiben die dann wiederum die unterschiedlichsten Anwendungsfälle abdecken, auch wenn sie im Grunde genommen nur XML sind. Vielleicht schon bekannte Beispiele sind XHTML, XSL, SVG oder auch SOAP. In all diesen XML Sprachen wird durch Schemas festgelegt wie die Struktur auszusehen hat, wann welche Elemente erlaubt sind usw. So entsteht dadurch eine genau definierte Sprache statt nur eine Ansammlung von Tags. Genauso funktioniert auch XAML.

Was XAML kann/nicht kann
XAML ist eine Sprache um Oberflächen zu beschreiben, nicht mehr und nicht weniger. Von daher kann man damit das Aussehen der GUI festlegen, wie welche Daten angezeigt werden. Es ist aber nicht möglich großartig Useraktionen zu beschreiben. Die XAML Elemente werden auf entsprechende Klassen in der .Net 3.0 Klassenbibliothek gemappt, von daher ist es möglich alles, was ich in XAML beschreiben kann, auch gleich selber in managed Code zu schreiben. Umgekehrt ist es aber nicht möglich, alles was ich in managed Code schreiben kann, in XAML zu schreiben. Aber wenn möglich sollte man ruhig XAML nutzen, weil diese deklarative Programmierung viel Schreibarbeit erspart.
Mal als kleines Beispiel:

XAML:

<Slider x:Name="SizeSlider" Grid.Row="1" Maximum="280" Margin="3" Value="100" />

C#:

Slider mySlider = new Slider();
mySlider.Name = SizeSlider;
mySlider.Maximum = 280;
mySlider.Margin = 3;
mySlider.Value = 100;
Grid.SetRow(mySlider,1);

Wie man sieht, schreibt sich XAML um einiges einfacher da nicht immer auf das Sliderobjekt verwiesen werden muss. Auch bietet XAML mit dem Grid.Row="1" im obigen Beispiel eine Besonderheit um auf Properties eines Parentelements zuzugreifen(Sowas nennt sich dann attached Properties).

Weitere Merkmale vom XAML
Nun kann man mit XML wirklich schon viel machen, aber es reicht dann trotzdem nicht ganz aus. Deshalb gibts es einige Erweiterungen, sogenannte Markup Extensions. Das gesamte Databinding funktioniert über diese Extensions, aber auch wenn man z.b. den Datentyp eines Elements angeben möchte bedient man sich deren. XAML instanziert Klassen immer mit dem parameterlosen Defaultkonstruktor was natürlich nicht immer ausreicht. Will man Argumente übergeben, muss man eine Markup Extension implementieren.

Dann gibts da noch die XML Namespaces. Die funktionieren genauso wie Namespaces in managed Code und dienen dazu Klassen eindeutig zu identifizieren. So kann man z.b. nicht einfach eine selbsgeschriebene Klasse User in der Art <User>...</User> verwenden, sondern man muss erst einen XML Namespace deklarieren der dem managed Namespace entspricht und kann darüber dann auf die Klasse zugreifen.

Desweiteren gibt es zwei verschiedene Arten um auf Properties eines Elements zuzugreifen. Die Attributsyntax und die Propertyelement Syntax. Die Attributsyntax dürfte bekannt sein:

<Element Attribut1="..." Attribut2="..." />

Die entsprechende Propertyelement Syntax sieht dann so aus:

<Element>
  <Element.Attribut1>...</Element.Attribut1>
  <Element.Attribut2>...</Element.Attribut2>
</Element>

Je nach Anwendung brauch man beide verschiedenen Schreibweisen, wobei wenn möglich die Attributsyntax um einiges kürzer ist.

Es gibt noch viele kleine andere Merkmale von XAML die für eine Einführung aber zu weit gehen, deshalb komme ich jetzt zum Beispiel wo hoffentlich einiges klarer wird 🙂

Beispiel

Download

Wieder eine Anwendung ohne viel Sinn und schlechtem Design, aber sie genügt als Beispiel. Es werden einfach ein paar Namen dargestellt.

Der entsprechende XAML Code sieht so aus:

<Window x:Class="Xaml_Beispiel.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:name="clr-namespace:Namensverwaltung" 
    Title="Xaml_Beispiel" Width="300" Height="160"
    >
    <Grid>
      <Grid.Resources>
        
        <ObjectDataProvider
          x:Name="NameDataProvider"
          x:Key="Namensliste"
          ObjectType="{x:Type name:NamensListe}" />
        
        <DataTemplate x:Key="NamesListeTemplate" DataType="{x:Type name:NamensListe}">
          <Border BorderBrush="Black" BorderThickness="2" CornerRadius="3" HorizontalAlignment="Stretch">
            <TextBlock Text="{Binding}" HorizontalAlignment="Stretch">
              <TextBlock.Style>
                <Style TargetType="{x:Type TextBlock}">
                <Setter Property="Foreground" Value="DarkBlue" />
                <Setter Property="Background">
                  <Setter.Value>
                    <LinearGradientBrush StartPoint="0,1" EndPoint="1,1">
                      <GradientStop Offset="0.0" Color="BlanchedAlmond" />
                      <GradientStop Offset="1.0" Color="PowderBlue" />
                    </LinearGradientBrush>
                  </Setter.Value>
                </Setter>
                </Style>
              </TextBlock.Style>
            </TextBlock>
          </Border>
        </DataTemplate>
      </Grid.Resources>
      <ListBox HorizontalContentAlignment="Stretch" ItemsSource="{Binding Source={StaticResource Namensliste}}" ItemTemplate="{StaticResource NamesListeTemplate}" />
    </Grid>
</Window>

Sieht erstmal schlimmer aus als er wirklich ist. Der Code muss auch nicht vollkommen verstanden werden, es sollen einfach die verschiedenen Aspekte von XAML die ich gerade genannt hab, gezeigt werden.

<Window x:Class="Xaml_Beispiel.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:name="clr-namespace:Namensverwaltung" 
    Title="Xaml_Beispiel" Width="300" Height="160"
    >

Das ist unser Root Element. Wir erstellen ein Window Objekt(das ist eine Möglichkeit ein Fenster in der WPF darzustellen) und da es das erste Element ist, werden hier die Namespaces angegeben.

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

sind obligatorisch, und müssen eingebunden werden um überhaupt XAML als Sprache einen Sinn zu geben. Dort wird über die entsprechenden Schemas festgelegt wie unser XAML Code aufgebaut ist und wie er funktioniert.

xmlns:name="clr-namespace:Namensverwaltung"

Das ist jetzt die erste interessante Stelle da wir hier einen eigenen Namespace deklarieren. Der Namespace heißt hier ganz kreativ mal name und wird auf den Namespace Namensverwaltung in managed Code gemappt. Sprich über name:Klasse1 kann ich auf meine Klasse1 zugreifen die im Namespace Namensverwaltung existiert. So wird die Eindeutigkeit von Klassen gewährleistet die auch im managed Code gegeben ist. Warum binde ich diesen Namespace ein? Weil ich dort eine Collection mit Namen habe die angezeigt werden soll, genau die Namen die angezeigt werden.

Vollständigkeitshalber der Code der Collection

using System;
using System.Collections.Generic;
using System.Text;

using System.Collections.ObjectModel;

namespace Namensverwaltung {
    public class NamensListe : ObservableCollection<string> {
        public NamensListe() {
            Add("Karsten");
            Add("Lisa");
            Add("Evi");
            Add("Tim");
            Add("Lola");
            Add("Toni");
        }
    }
}

Was es mit dieser ObservableCollection auf sich hat werde ich im Artikel zu Databinding erläutern.

Zurück zu unseren Anfangscode. Mit

Title="Xaml_Beispiel" Width="300" Height="160"

wende ich schon die Attributsyntax an um innerhalb eines Elements die Properties zu setzten. In dem Beispiel einfach den Titel des Fensters und desssen Größe.

Nun wirds interessanter. Es kommt mit

<Grid>
      <Grid.Resources>
	  ...
	  </Grid.Resources>
	  ...
</Grid>

ein Stück Code der in der Porpertyelement Syntax geschrieben ist. Warum? Rein theoretisch könnte ich auch sowas wie

<Grid Resources="..." />

schreiben, aber da kann ich nur einen einzelnen String angeben, das reicht mir aber vorne und hinten nicht um alle Elemente anzugeben die ich benötigt, also muss ich auf die Propertyelement Syntax zurückgreifen.

Nun kommt etwas Code der schon aufs Databinding vorgreift.

 <ObjectDataProvider
    x:Name="NameDataProvider"
    x:Key="Namensliste"
    ObjectType="{x:Type name:NamensListe}" />

Ich deklariere mir praktisch eine Datenquelle die mir in diesem Fall ein Objekt zurück gibt das ich dann fürs Databinding verwenden kann. Wie das funktioniert interessiert hier nicht, aber es wird einiges von dem oben angesprochenen hier verwendet.

    x:Name="NameDataProvider"
    x:Key="Namensliste"

Das sind beides spezielle MarkupExtensions. Über den Key kann ich immer eindeutig auf dieses Element drauf zugreifen und Name vergibt einfach einen Namen für dieses Element.
Viele Objekte haben eh ein Name Property das ich auch ohne diese Markup Extension setzten könnte, aber über diese Extension ist es nun möglich auch jenen Elementen einen Namen zu geben die kein Property dafür vorgesehen haben.

ObjectType="{x:Type name:NamensListe}"

Hier kommt nun eine MarkupExtension zusammen mit einer benutzerdefinierten Klasse zum Einsatz. Ich möchte den Typ meiner Datenquelle setzen. Um Typen anzugeben, benötige ich immer die x:Type Extension gefolgt vom Typ.
Das ist in unserem Fall die NamensListe. Da sie in einem anderen Namespace liegt muss ich diesen explizit angeben. Das tu ich nun über den am Anfang deklarierten XML Namespace im Muster XMLNamespace:Klassenname - name:NamesListe.

Der Code der nun folgt greift auf die Styles und Templates vor, soll aber wieder nur zur Veranschaulichung von XAML dienen.

<TextBlock Text="{Binding}" HorizontalAlignment="Stretch">
              <TextBlock.Style>
                <Style TargetType="{x:Type TextBlock}">
                <Setter Property="Foreground" Value="DarkBlue" />
                <Setter Property="Background">
                  <Setter.Value>
                    <LinearGradientBrush StartPoint="0,1" EndPoint="1,1">
                      <GradientStop Offset="0.0" Color="BlanchedAlmond" />
                      <GradientStop Offset="1.0" Color="PowderBlue" />
                    </LinearGradientBrush>
                  </Setter.Value>
                </Setter>
                </Style>
              </TextBlock.Style>
            </TextBlock>

Ich definiere mit einen Textblock, setzte ein paar Eigenschaften wie das Alignment und den Text wobei {Binding} wieder eine Markup Extension ist, die dem Databinding dient.
Diese Werte hab ich als Attribut angegeben, nun setzte ich noch das Style Property welches wieder umfangreicher ist und deshabb in Propertyelement Syntax folgt.
Nun folgt mit

<Setter Property="Foreground" Value="DarkBlue" />
                <Setter Property="Background">
                  <Setter.Value>
                    <LinearGradientBrush StartPoint="0,1" EndPoint="1,1">
                      <GradientStop Offset="0.0" Color="BlanchedAlmond" />
                      <GradientStop Offset="1.0" Color="PowderBlue" />
                    </LinearGradientBrush>
                  </Setter.Value>
                </Setter>

wieder ein Stückchen besonderer Code, auch wenn es auf den ersten Blick nicht so aussieht. Als erstes kommt ein Setter der mir das Property Foreground der Textbox(habe ich vorher bei TargetType im übergeordneten Element angegeben) auf die Farbe DarkBlue setzt - das ganze wieder in Attributsyntax. Als nächtes setzte ich das Property Background und den Wert gebe ich diesmal nicht als String an, sondern in der Propertyelement Syntax über

<Setter.Value>...</Setter.Value>

Nun folgt ein LinearGradientBrush wo ich wieder ein paar Eigenschaften setzte.

Wem ist jetzt vielleicht was aufgefallen? Ich habe Foreground und Background, in Windows Forms sind beides einfach Color Eigenschaften. Hier gebe ich auf einmal eine Farbe an(Darkblue) und dann einen Brush?!? Was wenn ich sage dass Foreground in Wirklichkeit auch ein Brush ist? Interessiert sich XAML vielleicht nicht für die Typsicherheit? Immerhin ist nen Brush ja auch nur ne Fläche mit ner Farbe 🙂 Mitnichten. XAML weiß ganz genau was für Typen wann erwartet werden und erlaubt sind und spätestens zur Laufzeit würde es ne Exception werfen. Was uns hier das Leben erleichtert ist ein ValueConverter. Davon sind so einige in WPF definiert und sorgen für eine impliziete Typumwandlung. Die Farbe DarkBlue wird also automatisch in einen entsprechenden Brush umgewandelt. Nur durch diese ValueConverter funktioniert auch das Databinding so gut. Man kann mit den passenden Convertern so ziemlich alles an allen binden. Es ist natürlich möglich sich solche Converter selber zu schreiben. Ein Beispiel dazu wird im Databinding Artikel folgen.

Jetzt haben wir den Code schon fast geschafft und es bleibt nun diese Zeile noch

<ListBox HorizontalContentAlignment="Stretch" ItemsSource="{Binding Source={StaticResource Namensliste}}" ItemTemplate="{StaticResource NamesListeTemplate}" />

Hier kommt wiederum Databinding zum Einsatz über diese Markup Extensions. Was aber auffällig ist: Bei ItemsSource sind die Extensions verschachtelt. Das ist also ohne weiteres möglich und ja sogar nötig wie in diesem Fall. StaticResource ist dabei wieder eine spezielle Markup Extensions um auf die Resourcen die ich irgendwo definiert habe zuzugreifen, dazu folgt aber in einem gesonderten Artikel mehr.

Nun sind wir am Ende des Artikel und Sie müssten in der Lage sein XAML Programme vom Aufbau her zu verstehen und verschiedene Elemente identifizieren können. Die Funktionalität dahinter muss sich dagegen noch nicht erschließen, das kommt mit den nächsten Artikeln.

Bis dann
Gruß Talla

Baka wa shinanakya naoranai.

Mein XING Profil.

talla Themenstarter:in
6.862 Beiträge seit 2003
vor 17 Jahren
Einführung in die WPF(Teil3) - Beispielanwendung

Willkommen im dritten Teil dieser Einführungsserie.

Für die weiteren Artikel brauchen wir eine Basis auf die wir aufbauen können.
Deshalb werde ich eine kleinen Minianwendung vorstellen die noch so wenig wie möglich Features der WPF benutzt.
Außerdem werden das Content Model von WPF und Events angesprochen.
Im Laufe der Artikelreihe werden wir dann immer mehr neue Features einbauen, so dass es am Ende eine richtige WPF Anwendung wird.

Kurze Beschreibung
Die Beispielanwendung ist ein kleiner Bildbetrachter der die Bildnamen aus einem Verzeichnis das angegeben wird ausliest und sie in einer Liste anzeigt. Nun kann man ein Bild in der Liste auswählen und es wird angezeigt. Klingt unspektakulär, ist es im Moment auch noch 🙂 Aber es bietet viel Potential.

Download

Der Quellcode

<Window x:Class="XPic.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="XPic" MaxHeight="600" MaxWidth="800" Margin="0" Padding="0"
    >
    <Grid>
      <Grid.RowDefinitions>
        <RowDefinition Height="25"/>
        <RowDefinition Height="200"/>
        <RowDefinition />
      </Grid.RowDefinitions>
      <Grid.ColumnDefinitions>
        <ColumnDefinition Width="40"/>
        <ColumnDefinition Width="650"/>
        <ColumnDefinition Width="100"/>
      </Grid.ColumnDefinitions>
      <Label Grid.Column="0" Grid.Row="0" HorizontalAlignment="Right" Margin="2">Pfad:</Label>
      <TextBox x:Name="DirectoryTextBox" Grid.Column="1" Grid.Row="0" Margin="2" HorizontalContentAlignment="Left" HorizontalAlignment="Stretch" Text="Bitte Pfad eingeben" />
      <Button x:Name="PfadButton" Content="wählen" Width="Auto" Grid.Column="2" Grid.Row="0" Margin="2" Click="PfadButton_Click"/>
      <Grid Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="3" Background="DarkSeaGreen">
        <ListView x:Name="ImageListView" Background="Gainsboro"  Margin="10" SelectionChanged="ImageListView_SelectionChanged"/>
      </Grid>
      <Grid Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="3" Background="CornflowerBlue">
        <Image x:Name="ImagePlace" />
      </Grid>
    </Grid>
</Window>

Vom Aufbau her sollte das Programm keine Schwierigkeiten bereiten, es werden nur einfache Elemente benutzt und verschiedene Attribute gesetzt. Wir haben wieder ein Fenster in dem sich erstmal ein Grid als Layout Element befindet. Wir legen über die Row- und GridDefinitions fest wieviele Spalten und Zeilen unser Grid haben soll. Dann platzieren wir einige Elemente in diesem Grid. Das werde ich auch mal visuell darstellen um das Grid näher zu erläutern weils schon ein häufig genutztes Layout Element ist.

Es ist im Prinzip eine 3*3 Tabelle und in den einzelnen Feldern platzieren wir nun unsere Elemente

Hier sind nun die einzelnen Felder so wie wir sie verwenden farbig hervorgehoben.
Oben links in Blau ist unser Label. Die Platzierung erfolgt einfach durch Angabe der entsprechenden Indizes

Grid.Column="0" Grid.Row="0"

Das gleiche machen wir nun mit einer TextBox(Rot) und einem Button(Gelb) - jeweils durch Angabe der passenden Grid.Column. Was dieses Grid.Column zu bedeuten hat werde ich später erläutern. Unter diesen drei Elementen wollen wir in der 2. Zeile unser ListBox mit den Dateinamen anzeigen. Durch Angabe der ersten Spalte als Column in der unsere Liste platziert werden soll und ColumnSpan="3" erreichen wir dass sich unsere Liste nun über alle 3 Columns und somit über die volle Breite des Programms erstreckt. Das gleiche geben wir auch bei unserem Image an. Auffällig an der ganzen Sache ist, dass wir innerhalb unseres großen Grids, für die Liste un das Bild jeweils erst ein eigenes Grid als Layoutelement benutzen und dort dann unsere Steuerelemente platzieren. Ein weiteres Property was oft benutzt wird, ist das Margin Property. Damit wird der Abstand der Steuerelemente zum Rand des Containers bestimmt. Ich habe hier nur eine Zahl als Abstand für jede Seite angegeben. Dieser kann aber auch für jede Seite einzeln in der Form Margin="1,2,3,4" angegeben werden. Webdesigner dürften sich recht wohl fühlen mit XAML oder? 🙂
Ein Bild wie es bei unserem Beispielprogramm jetzt ungefähr aussieht sehen sie jetzt. Die weißen Felder skizzieren unsere Controls und angedeutet ist der Rand zum sie enthaltenen Container.

Somit sind wir bei obrigen Quellcode schon wieder durch. Schuldig bin ich aber noch die Erklärung z.B. dieser Grid.Column Properties. Diese Properties, oder besser diese Art der Angabe von Properties, nennen sind Attached Properties und sind ein Feature von XAML.

Attached Properties erlauben es Werte von Properties in Elementen zu setzen die gar nicht zum eigentlichen Element, sondern zu einem Elternelement, gehören.
Wir setzen in unserem Fall immer die Spalte und Reihe in der unser Control im Grid erscheinen soll, dies ist aber wie gesagt keine Eigenschaft unseres Controls(es kann ja nicht wissen das es in einem Grid ist und nicht in einem anderen Layoutcontainer), sondern des Grids. Klarer wird dies wenn wir und das kleine Beispiel aus der XAML Einführung nochmal anschaun - wie solch ein Code in C# übersetzt wird.

XAML:

<Slider x:Name="SizeSlider" Grid.Row="1" Maximum="280" Margin="3" Value="100" />

C#:

Slider mySlider = new Slider();
mySlider.Name = SizeSlider;
mySlider.Maximum = 280;
mySlider.Margin = 3;
mySlider.Value = 100;
Grid.SetRow(mySlider,1);

Das Setzen der Grid.Row wird in einen extra Funktionsaufruf umgewandelt, und hat nichts mehr mit irgend nem Property unseres Sliders zu tun. Trotzdem entspricht es viel eher dieser deklarativen Syntax direkt bei unserem Steuerelement die entsprechende Reihe und Spalte einzustellen, statt bei dem übergeordneten Grid - deshalb diese attached Properties. Solche Properties kann man natürlich auch selber definieren, aber das soll nicht Teil dieser Einführung sein.

Wie sind Controls aufgebaut in der WPF? / Content Model

Bevor wir jetzt weiter fortfahren möchte ich kurz erläutern wie Controls in der WPF eigentlich aufgebaut sind - und auch erklären wieso ein Button keine Texteigenschaft hat 😉

In Windows Forms ist man bei den Controls recht beschränkt. Man ist doch recht festgelegt was Inhalt und Aussehen der Controls betrifft. Möchte man dort was ändern muss man i.d.R. ableiten und selbst implementieren. Bei der WPF ist es zum Glück um einiges leichter, aber auch vieles mächtiger - Problem ist nur dass man erstmal das Prinzip dahinter erkennen muss. Es gibt nur wenige Arten von Controls in der WPF - Layoutcontrols, Headered/ContentControl und Headered/ItemsControls. Das reicht völlig aus 😉 LayoutControls sind Controls wie das GridControl oder FlowLayoutControl die einfach beschreiben wie unsere anderen Controls innerhalb dieser LayoutControls angeordnet werden. Contentcontrols dienen dazu einzelne Daten anzuzeigen und ItemsControls dazu Listen anzuzeigen - die entsprechenden Headerpendants zeigen noch eine Überschrift an.

In WindowsForms war ein Label darauf beschränkt Text anzuzeigen - in der WPF ist es nun ein ContentControl und beschränkt sich darauf Content anzuzeigen.
Dies kann einfacher Text sein, aber damit wäre das Label schier unterfordert 🙂 Content bedeutet in der WPF nämlich nichts anderes als irgend ein beliebiges anderes Control. Somit ist es uns in der WPF endlich möglich jegliche Controls in anderne Controls einzubetten und das auch noch beliebig komplex. Ein einfaches Beispiel wäre z.b. ein MenuItem das nicht nur Text enthält, sondern bspw. ein Stackpanel(ein weiteres nützliches LayoutControl) das wiederum ein Image, Label, TextBox und Button enthält. Der Button kann seinerseits wieder Bild und Text enthalten usw. Der Kreativität der Entwickler sind nun kaum noch Grenzen gesetzt.
Leicht vorstellbar dürfte dann auch sein das die ganze GUI als Baum beschrieben wird. In unserem Fall wäre es z.b. ein Window mit einem Menü als Child. Das Menu hat wiederum ein MenuItem als Child und das Menuitem hat wie oben beschrieben auch mehrere Childs.

Nun lassen sich nicht nur selber Controls so zusammensetzten, sondern auch die fertigen Controls wie z.b. die ListBox, oder TabControl sind so aus mehreren Controls zusammengesetzt. Jedes Control hat so einen eigenen Elementbaum wie eben beschrieben. In der MSDN Doku ist zu fast jede Control ein umfangreiches Beispiel gebracht für so ein ControlTemplate. Genau, Template hört sich nach auswechselbar an oder? Denn das sind sie auch. Diese ControlTemplates lassen sich fast beliebig anpassen und auswechseln, auch für die Standardcontrols.

Und da sieht man dann auch hoffentlich ein, dass nen Label keine Texteigenschaft mehr hat sondern nur noch ein Content Property - immerhin muss das Label gar kein Steuerelement mehr enthalten was Text anzeigen kann. Wofür sollte dann eine Text Eigenschaft dienen?

Das ganze ist natürlich ziemlich komplex und bringt ein paar neue Konzepte was z.b. Events angeht. Es ermöglicht aber eine extrem flexible Anpassung auch vorhanderener Controls. Klarer dürft das ganze in einem zuküntigen Artikel werden wo Templates und Styles bsprochen werden.

Events in der WPF

Die oben vorgestellte Anwendung wie sie momentan ist, könnte man fast komplett in XAML schreiben, aber ein wichtiges Mittel fehlt uns dazu noch - das Databinding und das besprechen wir im nächsten Artikel. Jetzt müssen wir uns noch mit ein wenig C# Code rumschlagen, aber der ist sehr simpel. Es ist die Implementation unser 2 Eventhandler die wir oben in XAML festgelegt haben.
Es handelt sich konkret dabei um

 <Button x:Name="PfadButton" ... Click="PfadButton_Click"/>

und

 <ListView x:Name="ImageListView" ... SelectionChanged="ImageListView_SelectionChanged"/>

Die Syntax dürfte Recht einleuchtend sein, einfach der Eventname, gefolgt vom Namen des Eventhandlers. Wir müssen nun einfach den Eventhandler implementieren, das abbonieren der Events hingegen wird vom automatisch generierten Code übernommen, den wir im Idealfall(nämlich dann wenn alles funktioniert) nie zu Gesicht bekommen. Aber bevor wir zum Code kommen noch ein paar allgemeine Sache zu Events.

Windows Forms kennt einen Routing Mechanismus für Events, nämlich praktisch keinen 🙂 Es gibt nur direkte Events. Ein Button Click wird von einem Button ausgelöst und man kann beim Button einen Eventhandler anfügen der auf dieses Event reagieren soll.

In WPF gibts nun dieser 3: Direkt, Bubbling und Tunneling. Um es vorneweg zu nehmen, unsere beiden obigen Events sind Bubbling. Aus den Namen lässt sich auch schon ungefähr die Funktionsweise erahnen.

Direkte Events sind wie gehabt - Click wird vom Button ausgelöst und der Button könnte drauf reagieren. Tunneling Events werden nun vom Root Element angestoßen und wandern den Elementbaum runter bin zum Element das das Event ausgelöst hat! Klingt erstmal seltsam, ist aber so. Als Beispiel sei beim Button in WPF mal das PreviewKeyDown Event genannt. Alle Tunneling Events haben als Prefix "Preview" um sie von Bubbling Events zu unterscheiden. Diese wandern vom auslösenden Element den Elementbaum hoch zum Root.
Für die meisten Events gibt es deshalb solche PreviewABCXXX und ABCXXX Eventpaare - einmal als Tunneling Event und einmal als Bubbling Event.
die Tunneling Events treten dabei immer vor dem Bubbling Event auf.

Als Beispiel nehmen wir mal an wir haben ein Window mit einem Grid und darin befindet sich unser Button. Nun wir eine Taste gedrückt. Dann wirft unser Window das PreviewKeyDown Event, dann wird das Grid das entsprechende Event, dann erst der eigentliche Button wo es aufgetreten ist. Dann wirft der Button nach dem PreviewKeyDown das KeyDown Event das hochwandert zum Grid und von diesem geworfen wird und dann wird das Window das entsprechende Event.

Events können jederzeit auf dieser Route als "handled" makiert werden und werden dann nicht weiterverarbeitet. Auch zu beachten ist dass wenn ein Preview Ereigniss gehandled wird, das entsprechende Bubbling event nicht mehr geworfen wird!

Das war jetzt glaube ich arg viel der Theorie und die möchte ich jetzt auch nicht weiter vertiefen. Es wird ein Artikel folgen der Commands behandelt und dort werde ich auf dieses Thema nochmal eingehen - für alle anderen sei wie immer die MSDN Library Doku dazu angeraten.

Der C# Code

public void PfadButton_Click(object sender, RoutedEventArgs e) {
	ImageListView.Items.Clear();
	FileInfo[] files = null;
	if (Directory.Exists(DirectoryTextBox.Text)) {
		DirectoryInfo di = new DirectoryInfo(DirectoryTextBox.Text);
		files = di.GetFiles("*.jpg");
	}
	if (files != null) {
		for (int i = 0; i < files.Length; i++) {
			ImageListView.Items.Add(files[i].FullName);
		}
	}
}

Der Code ist recht simpel, sucht einfach aus dem in der TextBox angegebenen Verzeichnis alle Jpg's raus und fügt den Dateipfad in die ListBox ein. Auffällig bei der Signatur des Eventhandlers dürften die Eventargs sein. Die sind vom Typ RoutedEventArgs - dies ist der Basistyp für alle Events in der WPF. Es werden Eigenschaften wie z.b. Source und OriginalSource mitgegeben die es so noch nicht in WindowsForms gab. Um bei unserem Beispiel mit dem PreviewKeyDown Event zu bleiben, wäre Quelle jetzt unser Window(das Rootelement) und OriginalSource unser Button.

public void ImageListView_SelectionChanged(object sender, SelectionChangedEventArgs e) {
	if (e.AddedItems.Count > 0) {
		BitmapImage bmp = new BitmapImage(new Uri(e.AddedItems[0].ToString()));
		ImagePlace.Source = bmp;
	}
}

Statt eines RoutedEventArgs wird bei SelectionChanged direkt eine abgeleitete Klasse angegeben, immerhin brauchen wir ja auch die Informationen was sich nun geänert hat. Wir erstellen aus dem Dateinamen ein neues Bild und weisen es unserem ImageControl zu. An sich nichts daramtisches, trotzdem dürfte die Zeile mit dem Bitmap Konstruktor Fragen aufwerfen. Ich arbeite mit lokalen Pfaden und verwende eine URI? Ja - in WPF macht man das so 🙂 Und nicht nur beim Bitmap, eigentlich überall wo Pfade erwartet werden können URIs angegeben werden(was im Endeffekt ja auch nichts anderes als Pfade sind). Es gibt z.b. auch noch die Möglichkeit zwischen verschiedenen NavigationWindows in der WPF zu navigieren(ein Thema welches ich nicht ansprechen werde) und dort wird der Name der Page zu der navigiert werden soll, auch als URI angegeben.

Damit wäre auch dieser Artikel geschafft. Ich habe eine kleine Anwendung gezeigt die uns als Basis für die weiteren Artikel dienen soll und die wir um einige WPF Features erweitern wollen. Außerdem wurden in diesem Artikel erklärt was es mit dem Content Model so auf sich hat und wie Events in der WPF funktionieren.
Ich hoffe er hat euch gefallen und über Rückmeldung freue ich mich natürlich gerne.

Baka wa shinanakya naoranai.

Mein XING Profil.

Thema geschlossen