Old_Logo

WPF

Controls

Übersicht

Controls dienen als Elemente in XAML-Dateien. XAML-Dateien sind die Dateien, die WPF verwendet. Hier ist eine Liste der wichtigsten und gebrächlichsten WPF-Controls. Die Properties, die normalerweise gebindet werden (mehr dazu bei Binding) sind unterstrichen.
Control spezifische Properties Beschreibung
Label Content erzeugt eine Beschriftung für z.B.: TextBox
TextBox Text, TextAlignment, TextWrapping, TextTrimming, AcceptsReturn erzeugt eine Box, dessen Text man bearbeiten kann
TextBlock Text, TextAlignment, TextWrapping, TextTrimming erzeugt eine Box, dessen Text nicht bearbeitet werden kann
Slider Minimum, Maximum, Value, TickFrequency, TickPlacement erzeugt einen Schieberegler
RadioButton Content, IsChecked, GroupName erzeugt erzeugt einen runden Button mit Text, wo man einen auswählen kann
CheckBox Content, IsChecked erzeugt ein Kästchen mit Text, welches man anhäckelchen kann
Button Content, Command erzeugt einen anklickbare Box, die eine Aktion durchführt
DatePicker Text erzeugt eine TextBox mit auswählbarem Datum
DataGrid ItemsSource, SelectedItem erzeugt eine Art Tabelle bzw. Liste, die bearbeitet und sortiert werden kann
ListView ItemsSource, SelectedItem, DisplayMemberPath erzeugt eine Liste
ListBox erzeugt eine Liste
ComboBox erzeugt eine DropBox, wo man Items auswählen kann
Canvas erzeugt einen Bereich, wo Controls frei platziert werden können
StackPanel Orientation erzeugt einen Bereich, wo standardmäßig untereinander Controls hineinkommen
WrapPanel Orientation erzeugt einen Bereich, wo standardmäßig nebeneinander Controls hineinkommen
DockPanel erzeugt einen Bereich, wo Controls relativ zu den vier Rändern platziert werden können
Grid erzeugt ein Raster, welches frei konfiguriert werden kann

Label

Ein Label dient als Beschriftung für eine TextBox oder einen TextBlock, einen DatePicker oder irgendein anderes Control.
<Label Content="Passwort: " Width="100"/>

TextBox

In eine TextBox kann der User etwas hineinschreiben, was dann verarbeitet werden kann.
<TextBox Text="" Width="200"/>

TextBlock

Ein TextBlock ist ein Feld mit Text, den den User nicht bearbeiten, sondern nur lesen kann.
<TextBox Text="" Width="200"/>

Slider

Ein Slider ist ein Schieberegler, den der User hin und her schieben kann, um bestimmte Einstellungen zu verändern. Meistens benötigt man einen Converter für einen Slider, damit die Position der Sliderauswahl auch in einen Wert umgewandelt werden kann.
<Slider Value="50" Minimum="0" Maximum="100" TickFrequency="1" TickPlacement="Top/Bottom"/>

RadioButton

Ein RadioButton kommt selten alleine. Mehrere RadioButtons ermöglich eine Auswahl von mehreren Angeboten, sodass der User zum Beispiel sein Lieblingsessen auswählen kann.
<RadioButton Content="Italienisch" IsChecked="False" GroupName="FavouriteFood"/>
<RadioButton Content="Österreichisch" IsChecked="True" GroupName="FavouriteFood"/>
<RadioButton Content="Chinesisch" IsChecked="False" GroupName="FavouriteFood"/>
<RadioButton Content="Griechisch" IsChecked="False" GroupName="FavouriteFood"/>

CheckBox

Ein CheckBox kommt selten alleine. Mehrere CheckBoxes ermöglich eine Mehrfachauswahl von mehreren möglichen Wahr/Falsch-Aussagen, sodass der User zum Beispiel Besitztümer ankreuzen kann.
<CheckBox Content="Computer" IsChecked="True"/>
<CheckBox Content="Laptop" IsChecked="False"/>
<CheckBox Content="Handy" IsChecked="True"/>
<CheckBox Content="SmartWatch" IsChecked="True"/>

Button

Ein Button ist ein klickbares Element, das mithilfe von ControlBinding eine bestimmte Aktion durchführen kann.
<Button Content="Add" Command="{Binding AddCmd}"/>

DatePicker

Mithilfe eines DatePicker kann man eine TextBox mit einem Datumsauswählfeld erstellen. Wenn man daraufklickt, öffnet sich ein kleines Fenster mit einem Kalender.
<DatePicker SelectedDate="18/03/2022"/>

Tabellen

DataGrid
ListView
ListBox
ComboBox

Panel

Mithilfe von Panels kann man andere XAML-Controls den Wünschen entsprechend anordnen.
StackPanel
Das StackPanel ordnet jedes Kindelement vertikal an.
<StackPanel Orientation="Vertical">

</StackPanel>
Die Orientation bestimmt, ob die Elemente vertikal oder horizontal angeordnet werden sollen.
WrapPanel
Das WrapPanel ordnet jedes Kindelement horizontal an.
<WrapPanel Orientation="Horizontal">

</WrapPanel>
Die Orientation bestimmt, ob die Elemente vertikal oder horizontal angeordnet werden sollen.
DockPanel
Das DockPanel erlaubt das kleben an den vier Seiten.
<DockPanel DockPanel.Dock="Left">

</DockPanel>
Das DockPanel.Dock="" Property bestimmt, wo die Elemente kleben sollen.
Grid
Das Grid erzeigt eine Tabelle, die sich der Größe des Fensters anpasst.
<Grid Margin="15">
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="2*" />
        <ColumnDefinition Width="1*" />
        <ColumnDefinition Width="2*" />
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
        <RowDefinition Height="3*" />
        <RowDefinition Height="2*" />
    </Grid.RowDefinitions>
</Grid>
ColumnDefinition bestimmt, wie die Spalten aufgeteilt sein sollen. In diesem Fall ist die zweite Spalte halb so breit, wie die erste und dritte Spalte.
RowDefinition bestimmt, wie die Reihen augeteilt sein sollen. In diesem Fall ist die erste Reihe 1,5x so hoch, wie die zweite Reihe.
Sowohl Reihen als auch Spalten ändern sich somit relativ zur Gesamtbreite des Grid.
Canvas
Der Canvas ist eine Art Zeichenfläche, wo man Elemente beliebig hin- und herziehen kann.
<Canvas>
    <Label Canvas.Left="15" Canvas.Top="20">MyLabel</Label>
</Canvas>
Mit Canvas.Left / .Right / .Top / .Bottom kann man jedes Element frei ausrichten.

Properties

Die meisten Controls haben eigene spezifische Properties, zum Beispiel hat ein Slider ein Value-Property, welches eine TextBox nicht hat. Es gibt allerdings auch einige Properties, die jedes Control in WPF hat.
Property Beschreibung
Name definiert den Namen des Controls
Width bestimmt die Breite des Elements / Controls
Height bestimmt die Höhe des Elements / Controls
Margin bestimmt den Abstand außerhalb des Borders (zwischen Border und Umgebung)
Padding bestimmt den Abstand innerhalb des Borders (zwischen Text und Border)
Background bestimmt die Farbe, den Gradianten oder das Bild des Hintergrunds bzw., welches im Hintergrund angezeigt werden soll
Foreground bestimmt die Farbe des Vordergrundes

MVVM - Modell

Das Model-View-ViewModel- Modell hat in WPF sehr viel sinn, weil man dadurch DataBinding, ControlBinding und Converter programmieren kann. Die View ist hierbei das, was der User sehen, anklicken und bearbeiten kann. Das Model sind Klassen, die zum Beispiel Werte (Properties) speichern. Zum Beispiel eine Person. Das ViewModel ist der Teil, der die View mit dem Model verbindet.

Wenn man in WPF etwas bindet, sprechen Programmierer automatisch von diesem Modell. Man kann sich das so vorstellen, dass das ViewModel direkt hinter der View klebt und alle Eingaben, Klicks usw. dem Model weiterleitet oder selber managed.

DataBinding

In der Tabelle oben, sind all diejenigen Properties unterstrichen, die gebindet werden können bzw. normalerweise gebindet werden (man kann auch andere Properties von Controls binden...). Als Beispiel nehme ich einfach Text als das Property, das gebindet werden soll.

Name ist hier eine Variable aus der VM-Klasse.
<TextBox Text="{Binding Name}"/>
Damit man diesen Name auch verwenden kann, benötigt man einen Verweis auf die VM-Klasse.
<Window.DataContext>
    <local:PersonVM/>
</Window.DataContext>
In C# benötigt man etwas mehr Code. Diese Klasse ermöglicht zum Beispiel jeder erbenden Klasse das Nutzen der OnPropertyChanged(); - Methode. Diese Methode benachrichtigt die View, falls sich in der ViewModel-Klasse ein Wert verändert hat, sodass sich diese updated.
public class PropertyChangedClass : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    public void OnPropertyChanged([CallerMemberName] string propertyname = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyname));
    }
}
Die Klasse Person sieht so aus (mit drei Properties und ToString()- und ToCSV()-Methoden):
public class Person
{
    public string Name { get; set; }
    public string Password { get; set; }
    public bool IsCool { get; set; }

    public override string ToString()
    {
        string iscool = (IsCool) ? "ist cool" : "ist nicht cool";
        return String.Format($"{Name} hat {Password} als Passwort und {iscool}!");
    }

    public string ToCSV()
    {
        return String.Format($"{Name};{Password};{IsCool}");
    }
}
Die VM-Klasse sieht dann so aus. Hier sieht man, dass beim Setten eines Properties die OnPropertyChanged()-Methode aufgerufen wird, sodass alle Listener benachrichtigt werden, dass sich dieses Property geändert hat.

Da stellt man sich vielleicht nun die Frage: Woher weiß die Methode denn, welches Property sich verändert hat, wenn gar kein Parameter übergeben wird? Einfach Antwort: Darum kümmert sich das [CallerMemberName] aus der PropertyChangedClass.
public class PersonVM : PropertyChangedClass
{
    private Person _person;

    public PersonVM()
    {
        _person = new Person();
    }
    public string Name
    {
        get { return _person.Name; }
        set { _person.Name = value; OnPropertyChanged(); }
    }
    public string Password
    {
        get { return _person.Password; }
        set { _person.Password = value; OnPropertyChanged(); }
    }
    public int IsCool
    {
        get { return _person.IsCool; }
        set { _person.IsCool = value; OnPropertyChanged(); }
    }
}

ObservableCollection

Wenn man eine Liste mit Elementen, wie zum Beispiel DataGrid haben will, die sich mit TextBoxen und anderen Controls gemeinsam updated, benötigt man eine ObservableCollection.
public class PersonsVM : PropertyChangedClass
{
    public PersonsVM()
    {
        AllPersons = new ObservableCollection<PersonVM>();
        AllPersons.Add(new PersonVM() { Name = "Clemens", Password = "asdfjklö" });
        AllPersons.Add(new PersonVM() { Name = "Felix", Password = "ölkjfdsa" });
        AllPersons.Add(new PersonVM() { Name = "Anton", Password = "asdfölkj"});
        AllPersons.Add(new PersonVM() { Name = "Lorenz", Password = "1234" });

        SelectedPerson = AllPersons.First();
    }

    ObservableCollection<PersonVM> _allpersons;

    public ObservableCollection<PersonVM> AllPersons
    {
        get { return _allpersons; }
        set { _allpersons = value; }
    }

    private PersonVM _selectedperson;

    public PersonVM SelectedPerson
    {
        get { return _selectedperson; }
        set { _selectedperson = value; OnPropertyChanged(); }
    }
}
Dies wird dann folgendermaßen mit der XAML-Datei verknüpft:
<Window.DataContext>
    <local:PersonsVM/>
</Window.DataContext>
<DataGrid ItemsSource="{Binding AllPersons}" SelectedItem="{Binding SelectedPerson}" Width="300"/>

ControlBinding

Will man die Aktion eines Buttons kontrollieren, benötigt man ControlBinding.
public class PersonsVM : PropertyChangedClass
{
    public AddCmd AddCmd { get; set; }
    public LoginCmd LoginCmd { get; set; }

    public PersonsVM()
    {
        AllPersons = new ObservableCollection<PersonVM>();
        AllPersons.Add(new PersonVM() { Name = "Clemens", Password = "asdfjklö" });
        AllPersons.Add(new PersonVM() { Name = "Felix", Password = "ölkjfdsa" });
        AllPersons.Add(new PersonVM() { Name = "Anton", Password = "asdfölkj"});
        AllPersons.Add(new PersonVM() { Name = "Lorenz", Password = "1234" });

        SelectedPerson = AllPersons.First();

        AddCmd = new AddCmd(this);
        LoginCmd = new LoginCmd(this);
    }

    ObservableCollection<PersonVM> _allpersons;

    public ObservableCollection<PersonVM> AllPersons
    {
        get { return _allpersons; }
        set { _allpersons = value; }
    }

    private PersonVM _selectedperson;

    public PersonVM SelectedPerson
    {
        get { return _selectedperson; }
        set { _selectedperson = value; OnPropertyChanged(); }
    }
}
public class LoginCmd : ICommand
{
    private PersonsVM _personsVM;

    public LoginCmd(PersonsVM PersonsVM)
    {
        _personsVM = PersonsVM;
    }

    public bool CanExecute(object parameter)
    {
        return (_personsVM.SelectedPerson.Name != null && 
            _personsVM.SelectedPerson.Password != null &&
            _personsVM.SelectedPerson.Name != "" && 
            _personsVM.SelectedPerson.Password != "");
    }

    public void Execute(object parameter)
    {
        System.Windows.MessageBox.Show(_personsVM.SelectedPerson.Name + 
            " is cool", "Coolnesslevel");
    }

    public event EventHandler CanExecuteChanged
    {
        add => CommandManager.RequerySuggested += value;
        remove => CommandManager.RequerySuggested -= value;
    }
}
public class AddCmd : ICommand
{
    private PersonsVM _personsVM;

    public AddCmd(PersonsVM PersonsVM)
    {
        _personsVM = PersonsVM;
    }

    public bool CanExecute(object parameter)
    {
        return (_personsVM.SelectedPerson.Name != null && 
            _personsVM.SelectedPerson.Password != null &&
            _personsVM.SelectedPerson.Name != "" && 
            _personsVM.SelectedPerson.Password != "");
    }

    public void Execute(object parameter)
    {
        _personsVM.AllPersons.Add(new PersonVM()
        {
            Name = _personsVM.SelectedPerson.Name,
            Password = _personsVM.SelectedPerson.Password
        });
    }

    public event EventHandler CanExecuteChanged
    {
        add => CommandManager.RequerySuggested += value;
        remove => CommandManager.RequerySuggested -= value;
    }
}

Converter

Wie vorhin schon erwähnt benötigt man einen Converter meistens für Slider, muss aber nicht sein. Will man zum Beispiel einen Integer (1-5, wie Schulnoten) in den dazupassenden String ("Sehr gut" - "Nicht genügend") convertieren, kann man das so lösen:
public class IntToMarkConverter : IValueConverter
{
    public object Convert(object value, Type targetType, 
        object parameter, CultureInfo culture)
    {
        switch (System.Convert.ToInt32(value))
        {
            case 1: return "Sehr gut";
            case 2: return "Gut";
            case 3: return "Befriedigend";
            case 4: return "Genügend";
            default: return "Nicht genügend";
        }
    }

    public object ConvertBack(object value, Type targetType, 
        object parameter, CultureInfo culture)
    {
        // macht keinen Sinn, lol
        throw new NotImplementedException();
    }
}
<Window.Resources>
    <local:IntToMarkConverter x:Key="IntMark"/>
</Window.Resources>
<TextBox Text="{Binding Note}"/>
<TextBlock Text="{Binding Note, Converter={StaticResource IntMark}}"/>