Summary WPF
XAML
- Elemente werden zu Objekten übersetzt, deren Attribute zu Properties
- Pixel sind device-independent und definiert als
1px = 1/96"
SnapsToDevicePixels
, damit sich Elemente an Gerätepixel anpassen
Property Syntax
Attribute Syntax
<Button Height="50" Width="200" Content="Watch Now" />
Property Syntax
<Button Width="120" Height="50"> <Button.Background> <SolidColorBrush Color="Black" /> </Button.Background> </Button>
GUI-Entwurf
Alignment
- Es gibt
HorizontalAligment
undVerticalAlignment
Layouts
WrapPanel
: Wie StackPanel, aber mit Zeilen-/SpaltenumbruchDockPanel
: Werden mitAttached Properties
auf Elementen mitDockPanel.Dock
an eine Seite gedockt. Das letzte Element füllt den restlichen Platz ausGridPanel
: Tabellenanordnung. Zeilen und Spalten müssen explizit angegeben werden.auto
nutzt verfügbaren Platz,*
nutzt ganzer Platz.- Ein Grid mit einem 1x1-Grid kann auch als Layout mit Margins verwendet werden
Dialog-Fenster
private void OpenDialog_OnClick(object sender, RoutedEventArgs e) { var win = new DialogWindow(); if (win.ShowDialog() != true) { Debug.WriteLine("Cancelled :-("); return; } Debug.WriteLine("OK :-)"); }
- Im Dialog-Fenster
DialogResult
setzen, dann wird als Seiteneffekt (!) das Fenster geschlossen und der bool-Wert zurückgegeben
Testing
Teststack.White Example
[TestClass] public class MyFirstUITest { public string BaseDir => Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); public string SutPath => Path.Combine(BaseDir, $"{nameof(MenusAndCommands)}.exe"); [TestMethod] public void MyFirstUITestMethod() { var app = Application.Launch(SutPath); var window = app.GetWindow("Hauptfenster", InitializeOption.NoCache); var button = window.Get<Button>("SaveButton"); Assert.AreEqual("Speichern", button.Text); button.Click(); Assert.AreEqual("Gespeichert!", button.Text); app.Close(); } }
GUI-Design
Resources
- Definieren z.B. in application, window oder auf einem Element
<Application.Resources> <SolidColorBrush x:Key ="MyButtonBackground" Color="#EEEEEE" /> </Application.Resources>
- Über Binding abrufbar
<Button Background="{StaticResource MyButtonBackground}" Content="Save" />
- Statische Resources werden nur 1x gebunden, dynamische zur Laufzeit
Zugriff auf System-Ressourcen
<Button Background="{x:Static SystemColors.ControlBrush}" Content="Save" />
Resource Dictionaries
- Externe Resource Dictionaries erlauben Kaskadierung
<ResourceDictionary xmlns=... xmlns:x=...> <!- include your base dictionaries, here --> <ResourceDictionary.MergedDictionaries> <ResourceDictionary Source="Colors.xaml"/> </ResourceDictionary.MergedDictionaries> <!– now, access the externally defined resources --> <SolidColorBrush x:Key="ButtonBgBrush" Color="{StaticResource ThemeColor1}" /> </ResourceDictionary>
Externe Ressourcen
<Image Source="/BasePicLib;component/media/pix/open.png" />
Kurzform für:
<Image Source="pack://application:,,,/BasePicLib;component/media/pix/open.png" />
BasePicLib
ist der name der externen Assemblycomponent
ist immer fixmedia/pix/open.png
ist der Pfad in der externen Assembly
Styles
- Explizite Styles haben einen Key
<Style x:Key="MyButtonStyle"> <Setter Property="Button.Foreground" Value="#2672EC" /> <Setter Property="Button.Padding" Value="10 2 10 2" /> </Style>
- Nutzung:
<Button Style="{StaticResource MyButtonStyle}" Content="Save" />
- Typenspezifische Styles gelten für einen bestimmten Typ von Control
<Style x:Key="MyButtonStyle3" TargetType="Button"> <Setter Property="Background" Value="Transparent" /> <Setter Property="Margin" Value="2" /> </Style>
- Wird der
x:Key
weggelassen, gilt der Style implizit für alle Controls dieses Typen!
Styles "vererben"
<Style x:Key="DangerButtonStyle" TargetType="Button" BasedOn="{StaticResource MyButtonStyle}"> <Setter Property="Background" Value="Red" /> </Style>
Control Templates
- Jedes Control hat ein
Template
-Property, das bestimmt, wie es aufgebaut ist - Eigenes Template über Style definieren:
<Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="Button" > <Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}"> <StackPanel Orientation="Horizontal" Background="{TemplateBinding Background}"> <Grid Margin="4" > <Ellipse Fill="{TemplateBinding Foreground}" Height="16" Width="16" /> <Label Foreground="{TemplateBinding Background}" HorizontalContentAlignment="Center">!</Label> </Grid> <ContentPresenter Content="{TemplateBinding Content}" Margin="0 0 10 0" VerticalAlignment="Center" /> </StackPanel> </Border> </ControlTemplate> </Setter.Value> </Setter>
TemplateBinding
bindet den Wert an eine Eigenschaft im Control, z.B.Foreground
ContentPresenter
stellt den Inhalt des Controls dar
Trigger
- Styles anhand unterschiedlicher Zustände anpassen
<Style.Triggers> <Trigger Property="IsMouseOver" Value="True"> <Setter Property="Foreground" Value="White" /> </Trigger> </Style.Triggers>
- Problem: Bei einigen Elementen (z.B. Button) werden diese Trigger vom VisualStateManager übersteuert
- Lösung: Eigenes Control Template für diese Controls verwenden
2D-Transformationen
LayoutTransform
wird vor der Layout-Phase berechnet,RenderTransform
nachher
<ScaleTransform ScaleX="1.5" ScaleY="1.5" /> <RotateTransform Angle="45" CenterX="30" CenterY="12" /> <SkewTransform AngleX="-35" AngleY="9" /> <TranslateTransform X="40" Y="-10" />
Data Binding
StringFormat
<TextBox Text="{Binding ScaleX, StringFormat={}{0:0.0}}" />
z.B. um für Multibinding einen String aus mehreren Properties zusammen zu setzen:
<MultiBinding StringFormat="{}{0} -- Now only {1:C}!"> <Binding Path="Description"/> <Binding Path="Price"/> </MultiBinding>
Binding auf ElementName
- Binding auf ein XAML-Element
<TextBlock Name="MyText" Margin="10" … /> <TextBlock Name="OtherText" Margin="{Binding ElementName=MyText, Path=Margin}" … />
ItemsControl
- Ist die Basisklasse von
ListBox
,ComboBox
undDataGrid
- Mit
ItemsSource
die Quelle per Binding angeben ItemTemplate
: EinDataTemplate
angeben, das ein Layout enthält, wie ein einzelner Listeneintrag aussehen soll
CollectionsViewSource
- Ermöglicht Gruppieren, Sortieren, Filtern
SortDescriptions
: SortierungsmerkmaleSource
: Quelle per Binding (meist eineObservableCollection<T>
)
Event Handling
- Merke: Es ist nicht immer klar, wo das Event herkommt
- Daher in den EventArgs die Quelle prüfen, von wem das Event ausgelöst wurde!
Internationalization (i18n)
- Mit .NET Embedded Resources
System.Globalization.CultureInfo
Objekte enthalten infos über Sprache, Formate, etc.- Im Projekt ein
Resources.resx
ablegen - Abruf mit
Properties.Resources.STRING_ID
- oder
Properties.Resources.ResourceManager.GetString("STRING_ID")
Resources
= Name des Resources.resx File
- oder
- Neue Sprache: z.B.
Resources.en-US.rex
erstellen
- WPF-Spezifisch
- In
csproj
die DefaultUICulture
festlegen - In
AssemblyInfo.cs
Zeile auskommentieren und auf Default-Sprache setzen - Zugriff im XML:
xml <Window xmlns:resx="clr-namesapce:I18n.Properties" ...> <TextBlock Test="{x: Static resx:Resouces.STRING_ID}" /> </Window>
- In
MVVM
Commands
- Direkt implementieren:
public class SomeCommand : ICommand { public bool CanExecute(object parameter) { // ... irgendwie true oder false zurückgeben } public void Execute(object parameter) { // ... irgendwas ausführen } public event EventHandler CanExecuteChanged; }
- Das ViewModel hält eine Instanz (Property) auf das Command, das von den Controls gebindet wird
- Bessere Variante: Generischen
RelayCommand<T>
implementieren, der zwei delegates (Predicate<T>
fürCanExecute()
) undAction<T>
fürExecute()
) entgegen nimmt und diese ausführt
public GadgetVm() { SaveCommand = new RelayCommand(() => this.Save(), () => this.CanSave)); }
- Binding im XML (hier mit Parameter)
<Button Content="Open" Command="{Binding OpenGadgetViewCommand}" CommandParameter="{Binding SelectedGadget}" />
Projektlayout
- Kleine Projekte: 1 Projekt, für jede MVVM-Rolle 1 Ordner + 1 Test-Projekt
- Mittlere Projekte: 1 Projket pro Layer (Model, Business-Logik, UI) + 1 Test-Projekt
- Grosse Projekte: Projekte der Schichten in weitere (testbare) Unterprojekte aufteilen