Grundlagen: Style, ControlTemplate, DataTemplate, ContentPresenter

    • WPF

      Grundlagen: Style, ControlTemplate, DataTemplate, ContentPresenter

      Anbei ein DetailView, vergleichbar dem in die vier Views (in Wpf) gegebenem und erläutertem Beispiel.

      Das Viewmodel ist minimalistisch: Eine ListCollectionView, befüllt mit FileSystemInfos steht zur Anbindung bereit (Zeile #25).
      (FileSystemInfo - weiss man ja: die Basisklasse der überaus nützlichen Klassen FileInfo und DirectoryInfo):

      VB.NET-Quellcode

      1. Imports System.Collections.ObjectModel
      2. Imports System.IO
      3. Public Class MainModel
      4. Public Shared ReadOnly Root As New MainModel
      5. Private Sub New()
      6. Dim di = New DirectoryInfo("..\..\")
      7. Dim dpo = New System.Windows.DependencyObject
      8. If System.ComponentModel.DesignerProperties.GetIsInDesignMode(dpo) Then
      9. __FileSystemInfs = New ObservableCollection(Of FileSystemInfo)(New FileSystemInfo() {di.EnumerateFiles.First, di, di})
      10. Else
      11. __FileSystemInfs = New ObservableCollection(Of FileSystemInfo)(di.EnumerateFileSystemInfos("*.*", SearchOption.AllDirectories))
      12. End If
      13. FileSystemInfs = DirectCast(CollectionViewSource.GetDefaultView(__FileSystemInfs), ListCollectionView)
      14. End Sub
      15. Private Sub FileSystemInfs_CurrentChanged(sender As Object, e As EventArgs) Handles FileSystemInfs.CurrentChanged
      16. Dim i = FileSystemInfs.CurrentPosition
      17. Debug.WriteLine(If(i < 0, "leer", __FileSystemInfs(i).FullName))
      18. End Sub
      19. Public Property __FileSystemInfs As ObservableCollection(Of FileSystemInfo)
      20. Public WithEvents FileSystemInfs As ListCollectionView
      21. End Class
      Zu beachten, wie in Sub New zur Designzeit Dummi-Daten geladen werden (zeile #12), damit man der Xaml-Designer eine Vorschau mit Daten hat (Prinzip WYSIWYG).

      Nun das Xaml:

      XML-Quellcode

      1. <Window x:Class="MainWindow"
      2. xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      3. xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      4. xmlns:io="clr-namespace:System.IO;assembly=mscorlib"
      5. Title="MainWindow" Height="350" Width="525"
      6. xmlns:my="clr-namespace:TemplatedDetailView"
      7. DataContext="{x:Static my:MainModel.Root}" WindowState="Maximized">
      8. <FrameworkElement.Resources>
      9. <Style TargetType="HeaderedContentControl">
      10. <Setter Property="Template">
      11. <Setter.Value>
      12. <ControlTemplate TargetType="HeaderedContentControl">
      13. <StackPanel Orientation="Horizontal">
      14. <ContentPresenter Content="{TemplateBinding Header}" Margin="0,0,5,0" />
      15. <ContentPresenter Content="{TemplateBinding Content}" />
      16. </StackPanel>
      17. </ControlTemplate>
      18. </Setter.Value>
      19. </Setter>
      20. </Style>
      21. <DataTemplate DataType="{x:Type io:FileInfo}">
      22. <Border BorderBrush="Aqua" BorderThickness="10" Padding="8" >
      23. <StackPanel Orientation="Vertical">
      24. <HeaderedContentControl Header="Name:" Content="{Binding Path=Name}"/>
      25. <HeaderedContentControl Header="DirectoryName:" Content="{Binding Path=DirectoryName}"/>
      26. <HeaderedContentControl Header="Length:" Content="{Binding Path=Length}"/>
      27. </StackPanel>
      28. </Border>
      29. </DataTemplate>
      30. <DataTemplate DataType="{x:Type io:DirectoryInfo}">
      31. <Border BorderBrush="#FFFAD90A" BorderThickness="10" Padding="8" VerticalAlignment="Center" >
      32. <StackPanel Orientation="Vertical">
      33. <HeaderedContentControl Header="Name:" Content="{Binding Path=Name}"/>
      34. <HeaderedContentControl Header="FullName:" Content="{Binding Path=FullName}"/>
      35. </StackPanel>
      36. </Border>
      37. </DataTemplate>
      38. </FrameworkElement.Resources>
      39. <Grid>
      40. <Grid.ColumnDefinitions>
      41. <ColumnDefinition Width="Auto" MinWidth="20" />
      42. <ColumnDefinition />
      43. </Grid.ColumnDefinitions>
      44. <ListBox Grid.Column="0" MinWidth="60" ItemsSource="{Binding Path=FileSystemInfs}" IsSynchronizedWithCurrentItem="True">
      45. <FrameworkElement.Resources>
      46. <DataTemplate DataType="{x:Type io:FileInfo}">
      47. <TextBlock Text="{Binding Name, Mode=OneWay}" Background="Aqua"/>
      48. </DataTemplate>
      49. <DataTemplate DataType="{x:Type io:DirectoryInfo}">
      50. <TextBlock Text="{Binding Name, Mode=OneWay}" Background="#FFFAD90A"/>
      51. </DataTemplate>
      52. </FrameworkElement.Resources>
      53. </ListBox>
      54. <ContentPresenter Grid.Column="1" Margin="10" Content="{Binding Path=FileSystemInfs/}"/>
      55. </Grid>
      56. </Window>
      Fast der ganze Xaml-Code liegt in (<FrameworkElement.Resources>), nämlich
      • ein Style für HeaderedContentControl
      • ein ControlTemplate für HeaderedContentControl
        (im Style drinne)
      • zwei DataTemplates: eines für FileInfo, das andere für DirectoryInfo
      • nochmal zwei DataTemplates für diese beiden "Datenklassen"
        Nämlich in den lokalen Resourcen der Listbox (#46-51, schau genau!)
      HeaderedContentControl
      HeaderedContentControl kennt nicht jeder - das ist ein Wpf-Control, was üblicherweise eine Betextung (.Header) mit einem Wert (.Content) verknüpft.
      Normalerweise wird dabei der Header über dem Content angezeigt, aber noch normaler ist, dass man diesem Control ein ControlTemplate verpasst, sodass komplett anders aussieht, nämlich so, wie ich will, dass es aussieht.
      In diesem Falle will ich, dass der Header links vom Content steht, deshalb habich ein horizontales StackPanel ins ControlTemplate gemacht.

      ControlTemplate
      Kurz zur Erklärung: Ein ControlTemplate definiert das Aussehen eines Controls komplett neu.
      Ein sehr mächtiges Wpf-Feature.
      Es ist aber auch meist enorm viel Arbeit, ein gutes ControlTemplate zu bauen - weil man sich um jedes Detail selbst kümmern muss.
      Etwa beim Button heisst das: Border, Background, Image, Text, etc pp. Und alles mehrfach, für die verschiedenen Zustände die ein Button haben kann: Normal, Hovered, Pressed, Focused, ... hab ich was vergessen?
      Egal - jdfs bei sonem HeaderedContentControl ist das Template wirklich einfach: je ein ContentPresenter für die .Header und .Content, und gut.

      ContentPresenter? Was ist das? Und wieso keinen Textblock?
      Weil Textblock kann nur Text anzeigen, bei der FileInfo.Length (As Long) - Property zickt der rum.

      ContentPresenter
      ContentPresenter kann alles - weiler nichts selbst macht.
      Sondern er sucht für jeden Datentyp ein geeignetes DataTemplate, und wendet das dann an.

      Style
      Ein Style ist ein Dingens, was bei einem Control die Properties setzen kann.
      Deswegen beinhaltet ein Style vor allem Setter (meist mehrere).
      Und jeder Setter benennt die addressierte Property, und gibt ihr einen Value.
      Der Value kann ein einfacher Wert sein, dann ist so ein Setter in einer Zeile notiert.
      Aber wie man sieht kann der Setter.Value auch komplex sein - gugge Zeilen #11 - 18
      Also ich habe da einen Style gebastelt für HeaderedContentControl, mit einem Setter für die HeaderedContentControl.Template-Property, und der Wert den ich sette ist ein komplettes kleines ControlTemplate (für HeaderedContentControl)

      DataTemplate
      Ein DataTemplate mappt auf einen Datatype, und gibt ein (komplexes) Control an, was diesen Datatype präsentieren soll.
      Das Datatemplate der Zeilen #21-29 mappt auf FileInfo, darzustellen in einer Border, mit StackPanel drin, mit ... ... ... drin.
      Das Datatemplate der Zeilen #30-37 mappt auf DirectoryInfo, darzustellen in einer Border, mit StackPanel drin, mit ... ... ... drin.
      Also fast das gleiche, nur die Border hat eine andere Farbe, und FileInfo.DirectoryName, .Length werden nicht angezeigt, weil ein DirectoryInfo hat das ja nicht.
      Stattdessen nur der .FullName
      Wie gesagt: Fast das gleiche, nur ganz was anderes.

      Jo, nach so viel stylen und Templaten ist der Code für die eiglichen Controls extrem minimalistisch:

      XML-Quellcode

      1. <ListBox Grid.Column="0" MinWidth="60" ItemsSource="{Binding Path=FileSystemInfs}" IsSynchronizedWithCurrentItem="True">
      2. <FrameworkElement.Resources>
      3. <!-- noch mehr DataTemplates... -->
      4. </FrameworkElement.Resources>
      5. </ListBox>
      6. <ContentPresenter Grid.Column="1" Margin="10" Content="{Binding Path=FileSystemInfs/}"/>

      Also eine Listbox und ein ContentPresenter.
      Die Listbox ist IsSynchronizedWithCurrentItem - jo und der ContentPresenter presentet das CurrentItem - was sonst? (schau genau, das Binding ist bischen anders als bei der ListBox!)
      Also ein DetailView - hab ich ja gesagt :D

      Das Werk sieht übrigens nicht unerbärmlich aus, vor allem, weil in der Listbox die File- und Directory-Infos wie KrautnRübn durcheinander herumfahren:

      Dateien

      Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „ErfinderDesRades“ ()