n00b pro

04. WPF layout

Dit hoofdstuk is onderdeel van de cursus C#. Andere cursussen in dezelfde reeks: HTML, CSS, Javascript, Ontwikkelomgeving.

Youtube: WPF layout

https://www.youtube.com/watch?v=B6VQzjlFd70

Vensters

Een nieuw WPF project heeft standaard een 800x450 Window (met daarin een Grid control):

<Window x:Class="WpfTest.MainWindow"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      xmlns:local="clr-namespace:WpfTest"
      mc:Ignorable="d"
      Title="MainWindow" Height="450" Width="800">
   <Grid>
   </Grid>
</Window>

Properties en methodes

Property Omschrijving
Height hoogte van het venster
MaxHeight maximumhoogte van het venster (indien herschaalbaar)
MaxWidth maximumbreedte van het venster (indien herschaalbaar)
MinHeight minimumhoogte van het venster (indien herschaalbaar)
MinWidth minimumbreedte van het venster (indien herschaalbaar)
ResizeMode hoe kan het venster herschalen, b.v. niet herschaalbaar: NoResize, CanResize...
Title titel in de titelbalk bovenaan
Width breedte van het venster
WindowStyle stijl van het venster, b.v. vereenvoudigd venster: ToolWindow
Property Omschrijving
Open() open een venster
Close() sluit een venster

Voorbeelden

Minimum en maximum afmetingen

Beperk de afmetingen met Width, Height, MinWidth, MinHeight, MaxWidth en MaxHeight:

<Window x:Class="WpfTest.MainWindow"
   ...
   Title="Test Window" Height="250" Width="400" MinHeight="250" MinWidth="400" MaxHeight="450" MaxWidth="800">
   <Grid>
   </Grid>
</Window>
venster is nu maar beperkt aanpasbaar in breedte/hoogte

Niet herschaalbaar maken

Niet herschaalbaar venster met ResizeMode="NoResize":

<Window x:Class="WpfTest.MainWindow"
   ...
   Title="Test Window" Height="250" Width="400" ResizeMode="NoResize">
   <Grid>
   </Grid>
</Window>

Vereenvoudigd

Maak een vereenvoudigd venster zonder icoon linksboven of minimize knoppen rechtsboven met WindowStyle="ToolWindow":

<Window x:Class="WpfTest.MainWindow"
   ...
   Title="Test Window" Height="250" Width="400" Height="250" Width="400" WindowStyle="ToolWindow">
   <Grid>
   </Grid>
</Window>
vereenvoudigd venster

Nieuw openen

Een nieuw venster openen kan met de Open() methode. Nemen we als voorbeeld een PopupWindow; voeg het toe aan je project via rechtermuisknop add, Window...:

Voeg dan b.v. een Button toe aan MainWindow, waaromee je de PopupWindow opent:

<Window x:Class="WpfTest.MainWindow" ...>
   <Grid>
      <Button Padding="10,5" Content="open popup" Click="BtnOpenPopup_Click" .../>
   </Grid>
</Window>

De event handler in code-behind:

private void BtnOpenPopup_Click(object sender, RoutedEventArgs e) 
{
   new MyPopupWindow().Show();
}

Voor de popup window is een niet herschaalbaar vereenvoudigd venster ideaal:

<Window x:Class="WpfTest.PopupWindow" Height="150" Width="300" WindowStyle="ToolWindow" ResizeMode="NoResize" ...>
    <Grid>
    </Grid>
</Window>

Je kan bij het openen gegevens als b.v. gebruikersnaam meegeven van MainWindow naar PopupWindow door de constructor van die laatste uit te breiden met een parameter.

In PopupWindow.xaml.cs:

public MyPopupWindow(string name) // breid uit met "name" parameter
{
   InitializeComponent();
   lblWelcome.Content = $"hello {name}";
}

In MainWindow.xaml.cs:

private void BtnOpenPopup_Click(object sender, RoutedEventArgs e) {
    new MyPopupWindow("Rogier").Show(); // geef waarde voor "name" mee
}

Sluiten

Maak b.v. een button om het venster te sluiten

<Button Content="sluiten" Click="BtnClose_Click" .../>

De event handler in code-behind:

private void BtnClose_Click(object sender, RoutedEventArgs e) 
{
   this.Close();
}

Positionering van controls

Margin en Padding

Slepen we in designer een Button control op de grid, en voegen we wat Padding toe:

<Grid>
   <Button Content="Button" Margin="35,48,10,20" Padding="10,5,10,5" HorizontalAlignment="Left" VerticalAlignment="Top" />    
</Grid>
volgorde van margins en paddings in WPF: links, boven, rechts, onder

VerticalAlignment en HorizontalAlignment

De attributen VerticalAlignment en HorizontalAlignment bepalen hoe de control gepositioneerd wordt t.o.v. de parent. Als afstanden worden de marges genomen. Stel je ze b.v. in op Right resp. Bottom, dan worden de rechter- en onderwaarden van Margin genomen:

<Grid>
   <Button Content="Button" Margin="35,48,10,20" Padding="10,5" HorizontalAlignment="Right" VerticalAlignment="Bottom" />    
</Grid>
rechstonder gepositioneerd

De standaardwaarden voor VerticalAlignment en HorizontalAlignment zijn Stretch, dus als je het weglaat wordt de control uitgerokken, b.v. VerticalAlignment weglaten rekt verticaal uit:

<Grid>
   <Button Content="Button" Margin="35,48,10,20" Padding="10,5" HorizontalAlignment="Left" />    
</Grid>
zonder VerticalAlignment: uitgerokken
→ speficieer dus altijd waarden voor VerticalAlignment en HorizontalAlignment voor elke control!

VerticalContentAlignment en HorizontalContentAlignment

De attributen VerticalContentAlignment en HorizontalContentAlignment bepalen de alignering van de content van de control. De XAML voor twee tekstvakken, verticaal gecentreerd en horizontaal links resp. rechts:

<TextBox Text="tekst links..." HorizontalContentAlignment="Left" VerticalContentAlignment="Center" ... />
<TextBox Text="tekst rechts..." HorizontalContentAlignment="Right" VerticalContentAlignment="Center"... />
verticaal gecentreerd, horizontaal links resp. rechts

WPF panels

overzicht

Control Omschrijving Voorbeeld
Canvas positionering met coördinaten, al dan niet overlappend
DockPanel control waarbinnen panels aan de vier zijden gedocked ("gekleefd") kunnen worden
Grid rijen en kolommen waarbinnen controls geplaatst worden
goede controle over breedtes/hoogtes rijen en kolommen
goede controle over positionering items binnen de cellen
StackPanel eenvoudige stapeling per rij of kolom zonder wrapping
alle items worden tegen elkaar gestapeld, dus "spreiden" is niet mogelijk
WrapPanel als StackPanel, maar met wrapping

Canvas

Canvas wordt hoofdzakelijk gebruikt om (vooral grafische) elementen een vaste plaats te geven. De positie bepaal je met de properties Canvas.Left, Canvas.Top enz...; voor de stapelvolgorde gebruik je Canvas.ZIndex. Een voorbeeld:

<Canvas Name="cvCanvas">
   <Ellipse Fill="Gainsboro" Canvas.Left="25" Canvas.Top="25" Width="200" Height="200" Panel.ZIndex="1" />
   <Rectangle Fill="LightBlue" Canvas.Left="87" Canvas.Top="40" Width="50" Height="50"  Panel.ZIndex="3" />
   <Rectangle Fill="LightCoral" Canvas.Left="50" Canvas.Top="50" Width="50" Height="50" Panel.ZIndex="4" />
   <Rectangle Fill="LightCyan" Canvas.Left="74" Canvas.Top="75" Width="100" Height="100" Panel.ZIndex="2" />
</Canvas>
canvas voorbeeld

DockPanel

Docking is een term gebruikt voor waar en hoe child elements “plakken” in een venster dat van grootte kan veranderen. Een DockPanel wordt typisch gebruikt voor de globale layout van een Windows venster. Een voorbeeld:

<DockPanel LastChildFill="True"> <!-- LastChildFill: laatste element vult resterende middenruimte -->
   <TextBox DockPanel.Dock="Top" Text="Top" />
   <TextBox DockPanel.Dock="Bottom" Text="Bottom" />
   <TextBox DockPanel.Dock="Left" Text="Left1" />
   <TextBox DockPanel.Dock="Left" Text="Left2" />
   <TextBox DockPanel.Dock="Right" Text="Right" />
   <TextBox Text="Center" AcceptsReturn="True" TextWrapping="Wrap" />
</DockPanel>
dockpanel voorbeeld

Grid

Bij Grid kan je een grid van rijen en kolommen definiëren met Grid.ColumnDefintions en Grid.RowDefinitions. Daarna plaats je elementen in de juiste rij/kolom met Grid.Row en Grid.Column.

<Grid Margin="10">
   <Grid.ColumnDefinitions>
      <ColumnDefinition Width="80" />
      <ColumnDefinition Width="*" />
   </Grid.ColumnDefinitions>
   <Grid.RowDefinitions>
      <RowDefinition Height="Auto" />
      <RowDefinition Height="Auto" />
      <RowDefinition Height="*" />
   </Grid.RowDefinitions>
   <Label>Name:</Label>
   <TextBox Grid.Column="1" Margin="0,5,0,10" />
   <Label Grid.Row="1">E-mail:</Label>
   <TextBox Grid.Row="1" Grid.Column="1" Margin="0,5,0,10" />
   <Label Grid.Row="2">Comment:</Label>
   <TextBox Grid.Row="2" Grid.Column="1" Margin="0,5,0,10" AcceptsReturn="True" />
</Grid>

De Width waarden van kolommen worden als volgt verwerkt (hetzelfde principe geldt voor Height waarden van rijen):

  1. teken eerst kolommen met vaste breedtes
  2. teken dan kolommen met breedte Auto, waarbij ze niet meer ruimte innemen dan hun content nodig heeft
  3. verdeel de resterende waarden proportioneel; als er b.v. twee kolommen 2* en 3* zijn, krijgt de eerste kolom 2/5 en de tweede 3/5
Voorbeeld Betekenis Anders gezegd...
Width="80" exact 80 px breed vaste waarde
Width="Auto" neem niet meer ruimte dan nodig fit content
Width="3*" neemt een gewicht 3 resterende ruimte
Width="*" (standaardwaarde) is hetzelfde als Width="1*" resterende ruimte
grid voorbeeld

StackPanel

Bij StackPanel stapel je controls in één rij of kolom:

<StackPanel Orientation="Horizontal">
   <Label FontSize="48" Width="100" Height="70" HorizontalContentAlignment="Center" 
          VerticalContentAlignment="Center" Background="LightGray" Margin="10,10,10,10">1</Label>
   <Label FontSize="48" Width="100" Height="70" HorizontalContentAlignment="Center" 
          VerticalContentAlignment="Center" Background="LightGray" Margin="10,10,10,10">2</Label>
   <Label FontSize="48" Width="100" Height="70" HorizontalContentAlignment="Center" 
          VerticalContentAlignment="Center" Background="LightGray" Margin="10,10,10,10">3</Label>
   <Label FontSize="48" Width="100" Height="70" HorizontalContentAlignment="Center" 
          VerticalContentAlignment="Center" Background="LightGray" Margin="10,10,10,10">4</Label>
   <Label FontSize="48" Width="100" Height="70" HorizontalContentAlignment="Center" 
          VerticalContentAlignment="Center" Background="LightGray" Margin="10,10,10,10">5</Label>
   <Label FontSize="48" Width="100" Height="70" HorizontalContentAlignment="Center" 
          VerticalContentAlignment="Center" Background="LightGray" Margin="10,10,10,10">6</Label>
   <Label FontSize="48" Width="100" Height="70" HorizontalContentAlignment="Center" 
          VerticalContentAlignment="Center" Background="LightGray" Margin="10,10,10,10">...</Label>
</StackPanel>
stackpanel voorbeeld

Om verticaal te stapelen gebruik je Orientation="Vertical":

<StackPanel Orientation="Vertical">
   ...         
</StackPanel>
      
StackPanel verticaal voorbeeld

WrapPanel

Bij WrapPanel stapel je elementen in meerdere rijen of kolommen:

<WrapPanel>
   <Label FontSize="48" Width="100" Height="70" HorizontalContentAlignment="Center" 
          VerticalContentAlignment="Center" Background="LightGray" Margin="10,10,10,10">1</Label>
   <Label FontSize="48" Width="100" Height="70" HorizontalContentAlignment="Center" 
          VerticalContentAlignment="Center" Background="LightGray" Margin="10,10,10,10">2</Label>
   <Label FontSize="48" Width="100" Height="70" HorizontalContentAlignment="Center" 
          VerticalContentAlignment="Center" Background="LightGray" Margin="10,10,10,10">3</Label>
   <Label FontSize="48" Width="100" Height="70" HorizontalContentAlignment="Center" 
          VerticalContentAlignment="Center" Background="LightGray" Margin="10,10,10,10">4</Label>
   <Label FontSize="48" Width="100" Height="70" HorizontalContentAlignment="Center" 
          VerticalContentAlignment="Center" Background="LightGray" Margin="10,10,10,10">5</Label>
   <Label FontSize="48" Width="100" Height="70" HorizontalContentAlignment="Center" 
          VerticalContentAlignment="Center" Background="LightGray" Margin="10,10,10,10">6</Label>
   <Label FontSize="48" Width="100" Height="70" HorizontalContentAlignment="Center" 
          VerticalContentAlignment="Center" Background="LightGray" Margin="10,10,10,10">...</Label>
</WrapPanel>
stackpanel voorbeeld

Je kan ook stapelen van rechts naar links met FlowDirection="RightToLeft", maar dat zijn de enige aligneermogelijkheden. Items centraal plaatsen of ver uit elkaar zetten kan bijvoorbeeld niet, zoals met CSS FlexBox wel kan. Voor complexere layouts gebruik je best altijd Grid.

Om verticaal te stapelen gebruik je Orientation="Vertical":

<WrapPanel Orientation="Vertical">
   ...         
</WrapPanel>
      
WrapPanel verticaal voorbeeld

UI componenten

overzicht

Control Omschrijving Voorbeeld
Menu een klassiek Windows menu
ScrollViewer voeg scrollbars toe aan panels en andere controls
StatusBar een statusbar onderaan het venster
Tabcontrol een control met tabs

Menu

Een menu bouw je met Menu en MenuItem controls, typisch in combinatie met een DockPanel:

<DockPanel LastChildFill="True">
   <Menu DockPanel.Dock="Top">
       <MenuItem Header="_File">
           <MenuItem Header="_Open..." />
           <MenuItem Header="_Save" />
           <MenuItem Header="Save _As..." />
           <Separator />
           <MenuItem Header="E_xit" Click="ExitItem_Click" />
       </MenuItem>
       <MenuItem Header="_Help">
           <MenuItem Header="_About" />
       </MenuItem>
   </Menu>
   <Grid></Grid>
</DockPanel>

→ de underscore in de XAML duidt aan welke letter als shortcut gebruikt wordt

Voorbeeld van een event handler voor het exit menu item:

private void ExitItem_Click(object sender, RoutedEventArgs e) 
{
    // 0 wilt zeggen dat er niets is fout gelopen
    Environment.Exit(0);
}
Menu voorbeeld

ScrollViewer

Sommige controls als ListBox hebben van zichzelf scrollbars, sommige controls als TextBox of panels niet. Ze scrollbars geven doe je — misschien wat vreemd — niet met een property, maar met een ScrollViewer control:

  1. wrap de control in een <ScrollViewer> ... </ScrollViewer>
  2. verhuis layout properties als Height, HorizontalAlignment, Margin, VerticalAlignment, Width enz... van de control naar de ScrollViewer

Een TextBox zonder scrollbars:

<TextBox 
   x:Name="txt1" 
   AcceptsReturn="True"
   Height="150" 
   HorizontalAlignment="Left" 
   Margin="35,39,0,0" 
   Padding="20,10,20,30" 
   Text="Bacon ipsum dolor amet prosciutto tail fatback, pancetta sausage meatball swine rump salami pig alcatra ..." 
   TextWrapping="Wrap" 
   VerticalAlignment="Top" 
   Width="200"
/>
      
TextBox zonder ScrollViewer

Dezelfde TextBox met scrollbars:

<ScrollViewer 
   Height="150" 
   HorizontalAlignment="Left" 
   Margin="35,39,0,0"
   VerticalAlignment="Top" 
   Width="200">
   <TextBox 
      x:Name="txt1" 
      AcceptsReturn="True"
      Padding="20,10,20,30" 
      Text="Bacon ipsum dolor amet prosciutto tail fatback, pancetta sausage meatball swine rump salami pig alcatra ..." 
      TextWrapping="Wrap"
   />
</ScrollViewer>
TextBox met ScrollViewer

StatusBar

Een StatusBar gebruik je typisch in combinatie met een DockPanel, onderaan gedocked:

<DockPanel LastChildFill="True">
   ...
   <StatusBar DockPanel.Dock="Bottom">
      <StatusBarItem Padding="10,5">
         <Label Content="all ready" FontSize="10" />
      </StatusBarItem>
   </StatusBar>    
   <Grid></Grid>
</DockPanel>
StatusBar voorbeeld

TabControl

Een TabControl voorbeeld:

<TabControl SelectedIndex="2" >
   <TabItem Header="Tab 1" Margin="0" Padding="10,5">
       <TextBox Padding="10">tab 1 content here...</TextBox>                
   </TabItem>
   <TabItem  Header="Tab 2" Margin="0" Padding="10,5">
       <TextBox Padding="10">tab 2 content here...</TextBox>
   </TabItem>
   <TabItem Header="Tab 3" Margin="0" Padding="10,5">
       <TextBox Padding="10">tab 3 content here...</TextBox>
   </TabItem>
   <TabItem Header="Tab 4" Margin="0" Padding="10,5">
       <TextBox Padding="10">tab 4 content here...</TextBox>
   </TabItem>
</TabControl>
TabControl voorbeeld