DelegateConverter

    • WPF

    Es gibt 2 Antworten in diesem Thema. Der letzte Beitrag () ist von ErfinderDesRades.

      DelegateConverter

      In Wpf kann man einem Binding einen Konverter mitgeben.
      Das wird hier genutzt, um Messwerte in einem Datagrid mit Farbwerten zu hinterlegen - also der Konverter muss aus den Messwerten einen SolidColorBrush "berechnen".


      Die Konversion erfolgt natürlich anhand von Schwellwerten - dafür habe ich ja an annerer Stelle das ThresholdDictionary erfunden :D
      Hier, wie es instanziert, mit 8 Farb-Brushes und 7 Schwellwerten "beschickt" wird:

      VB.NET-Quellcode

      1. Private Shared _Colors As New ThresholdDictionary(Of Double, SolidColorBrush)(Brushes.AliceBlue) From { _
      2. {2, Brushes.LightBlue}, {4, Brushes.Turquoise}, {6, Brushes.LightGreen}, {8, Brushes.GreenYellow}, {10, Brushes.Yellow}, {12, Brushes.Gold}, {14, Brushes.Orange}}


      Aber ihr wolltet ja sicherlich den DelegateConverter sehen - wenn das man keine Enttäuschung wird:

      VB.NET-Quellcode

      1. Imports System.Globalization
      2. Namespace System.Windows.Controls
      3. Public Class DelegateConverter : Inherits DependencyObject : Implements IValueConverter
      4. Public Shared ReadOnly ConversionProperty As DependencyProperty = New DepProp(Echo(Of Object).Instance, GetType(DelegateConverter), "Conversion")
      5. Public Property Conversion() As Func(Of Object, Object)
      6. <DebuggerStepThrough()> _
      7. Get
      8. Return DirectCast(GetValue(ConversionProperty), Func(Of Object, Object))
      9. End Get
      10. <DebuggerStepThrough()> _
      11. Set(ByVal NewValue As Func(Of Object, Object))
      12. SetValue(ConversionProperty, NewValue)
      13. End Set
      14. End Property
      15. Public Shared ReadOnly BackConversionProperty As DependencyProperty = New DepProp(Echo(Of Object).Instance, GetType(DelegateConverter), "BackConversion")
      16. Public Property BackConversion() As Func(Of Object, Object)
      17. <DebuggerStepThrough()> _
      18. Get
      19. Return DirectCast(GetValue(BackConversionProperty), Func(Of Object, Object))
      20. End Get
      21. <DebuggerStepThrough()> _
      22. Set(ByVal NewValue As Func(Of Object, Object))
      23. SetValue(BackConversionProperty, NewValue)
      24. End Set
      25. End Property
      26. Public Function Convert(ByVal value As Object, ByVal targetType As Type, ByVal parameter As Object, ByVal culture As CultureInfo) As Object Implements IValueConverter.Convert
      27. Return Conversion(value)
      28. End Function
      29. Public Function ConvertBack(ByVal value As Object, ByVal targetType As Type, ByVal parameter As Object, ByVal culture As CultureInfo) As Object Implements IValueConverter.ConvertBack
      30. Return BackConversion(value)
      31. End Function
      32. End Class
      33. End Namespace
      Ein Dingsbums, welches IValueConverter implementiert, und 2 DependancyProperties hat - sonst nichts. Getan wird hier eiglich nichts, aber das Ding heißt DelegateConverter, eben weils nix selber tut, sondern die Tätigkeiten delegiert.
      Also was es tut ist Konvertieren (mit der Convert()-Methode) und Zurück-Konvertieren (mit der ConvertBack()-Methode - potzblitz!).
      Convert() und ConvertBack() sind Bestandteile der IValueConverter-Schnittstelle - vom Framework so vorgegeben.
      Einem Binding, was Konvertierung unterstützen soll, kann man ein beliebiges Dingsbums als Converter zuweisen - hauptsache, das Dingsbums implementiert IValueConverter - dann kann das Binding nämlich Convert() und ConvertBack() aufrufen, wenn ihm danach ist ;)
      Also nochmal genau DelegateConverter angeguckt: Es gibt die Property Conversion As Func(Of Object, Object), also eine Function, die ein Object entgegennimmt, und ein Object zurückgibt.
      Und es gibt den Schnittstellen-Member Convert(), welcher auch ein Object entgegennimmt (und noch 2 weitere Argumente), und ein Object zurückgibt.
      Und dieses Convert() tut nix anneres (Zeile#32), als die in Conversion gespeicherte Function aufzurufen, mit value als Argument, und davon das Ergebnis returnen.

      Das ViewModel
      Hier das bahnbrechende ViewModel, mit all seinen Properties, weil an was anneres als an Properties kann Xaml leider nicht binden:

      VB.NET-Quellcode

      1. Imports GalaSoft.MvvmLight
      2. Imports System.IO
      3. Imports GalaSoft.MvvmLight.Command
      4. Public Class MainModel : Inherits MainModelBase(Of MainModel)
      5. Private _DataFile As New FileInfo("..\..\Data.Xml")
      6. Public Property Measures As New MeasureDts
      7. Public Property ReLoad As New RelayCommand( _
      8. Sub()
      9. Measures.ReadXml(_DataFile.FullName)
      10. Measures.AcceptChanges()
      11. End Sub, _
      12. Function() Measures.HasChanges)
      13. Public Property Save As New RelayCommand( _
      14. Sub()
      15. Measures.WriteXml(_DataFile.FullName)
      16. Measures.AcceptChanges()
      17. System.Media.SystemSounds.Asterisk.Play()
      18. End Sub, _
      19. Function() Measures.HasChanges)
      20. Public Property GenerateRandom As New RelayCommand( _
      21. Sub()
      22. Dim rnd As New Random
      23. Dim dt = Date.Now
      24. For i = 0 To 15
      25. Measures.Measure.AddMeasureRow(dt.AddSeconds(rnd.Next(3600)), Math.Round(rnd.NextDouble * 15, 2))
      26. Next
      27. End Sub)
      28. Private Shared _Colors As New ThresholdDictionary(Of Double, SolidColorBrush)(Brushes.AliceBlue) From { _
      29. {2, Brushes.LightBlue}, {4, Brushes.Turquoise}, {6, Brushes.LightGreen}, {8, Brushes.GreenYellow}, {10, Brushes.Yellow}, {12, Brushes.Gold}, {14, Brushes.Orange}}
      30. Public Property ColorConversion As Func(Of Object, Object) = Function(value) _Colors(DirectCast(value, Double))
      31. Public Sub New()
      32. If MyBase.IsProvisional Then Return
      33. ReLoad.Execute(Nothing)
      34. End Sub
      35. End Class
      Wir haben hier 5 Public Properties vorliegen (nicht, dass eine übersehen wird ;)).
      1. Public Property Measures As MeasureDts
        ein typisiertes Dataset mit Messwerten
      2. Public Property ReLoad As RelayCommand
        ein Command zum Laden
      3. Public Property Save As RelayCommand
        ein Command zum Speichern (kommt euch das eiglich nicht blöd vor, wenn ich erkläre, dass das Save-Commad zum Speichern ist? ;))
      4. Public Property GenerateRandom As RelayCommand
        ein Command zum Generieren von Zufalls-Werten
      5. Public Property ColorConversion As Func(Of Object, Object)
        eine anonyme Function, die ein Object in ein anneres konvertiert (nämlich einen Double in einen SolidColorBrush).
      Dateien

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

      RelayCommand

      RelayCommand kennt ihr ja evtl. schon aus einem anneren Wpf-Tut: Das ist ein Objekt mit einer Aktion und optional auch einer Funktion, die ansagt, ob die Aktion ühaupt möglich ist.
      Tatsächlich habe ich das DelegateConverter-Konzept von RelayCommand abgeguckt, weil die tun ja auch nix, ausser die eigentlichen Tätigkeiten an Delegaten zu delegieren (und man fragt sich, warum sie RelayCommand heißen, anstatt DelegateCommand).
      Guggemol das Save-Command genau an:

      VB.NET-Quellcode

      1. Public Property Save As New RelayCommand( _
      2. Sub()
      3. Measures.WriteXml(_DataFile.FullName)
      4. Measures.AcceptChanges()
      5. System.Media.SystemSounds.Asterisk.Play()
      6. End Sub, _
      7. Function() Measures.HasChanges)
      Da werden im Konstruktor doch gleich zwei Delegaten übergeben:
      1. eine anonyme Action

        VB.NET-Quellcode

        1. Sub()
        2. Measures.WriteXml(_DataFile.FullName)
        3. Measures.AcceptChanges()
        4. System.Media.SystemSounds.Asterisk.Play()
        5. End Sub

      2. eine anonyme Func(Of Boolean)

        VB.NET-Quellcode

        1. Function() Measures.HasChanges)
      Damit sollte für jeden, der lesen kann, ersichtlich sein, dass dieses Save-Command nur dann ausgeführt werden kann, wenn das Measures-Dataset modifiziert wurde (Measures.HasChanges) - und die Ausführung besteht hier aus den drei Schritten:
      1. Dataset auf Platte schreiben
      2. Dataset-Status auf "unmodifiziert" setzen (Measures.AcceptChanges())
      3. einen "Pling!" - Sound abspielen

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

      Aber eiglich gehts hier ja um den DelegateConverter, und wie man dem eine Conversion beibringt, dasser auch konvertieren kann. Jo, man kann ihn einfach an eine Conversion binden - und wir haben ja auch schon eine Conversion: nämlich die ColorConversion ausm MainModel (Post#1, Listing#3, Zeile#38)!
      Die ColorConversion tut übrigens auch nicht viel - castet den Wert auf Double und ruft mit diesem Key den entsprechenden SolidColorBrush vom ThresholdDictionary ab.

      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:my="clr-namespace:DelegateConverterTester"
      5. xmlns:hlp="clr-namespace:System.Windows.Controls;assembly=Helpers"
      6. Title="MainWindow" Height="350" Width="305"
      7. DataContext="{StaticResource MainModel}">
      8. <Grid>
      9. <Grid.RowDefinitions>
      10. <RowDefinition Height="Auto" MinHeight="8" />
      11. <RowDefinition />
      12. </Grid.RowDefinitions>
      13. <Menu>
      14. <MenuItem Header="ReLoad" Command="{Binding Path=ReLoad}" />
      15. <MenuItem Header="Save" Command="{Binding Path=Save}" />
      16. <MenuItem Header="GenerateRandom" Command="{Binding Path=GenerateRandom}" />
      17. </Menu>
      18. <DataGrid Grid.Row="1" ItemsSource="{Binding Path=Measures.Measure}" AutoGenerateColumns="False" GridLinesVisibility="None" AlternationCount="2147483647" >
      19. <DataGrid.Resources>
      20. <!--hier das Binding funzt zwar, läßt sich aber nicht im PropertyFenster setzen :(-->
      21. <hlp:DelegateConverter x:Key="BackgroundConv" Conversion="{Binding Source={StaticResource MainModel}, Path=ColorConversion}" />
      22. <Style TargetType="{x:Type DataGridCell}">
      23. <Setter Property="VerticalAlignment" Value="Center" />
      24. </Style>
      25. </DataGrid.Resources>
      26. <DataGrid.RowStyle>
      27. <Style TargetType="{x:Type DataGridRow}">
      28. <Setter Property="Header" Value="{Binding RelativeSource={RelativeSource Self}, Path=AlternationIndex}" />
      29. <Setter Property="Background" Value="{Binding Path=Value, Converter={StaticResource BackgroundConv}}" />
      30. </Style>
      31. </DataGrid.RowStyle>
      32. <DataGrid.Columns>
      33. <DataGridTextColumn Binding="{Binding Path=ID}" Header="ID" />
      34. <DataGridTextColumn Binding="{Binding Path=Time}" Header="Time" />
      35. <DataGridTextColumn Binding="{Binding Path=Value, StringFormat={}{0:N2}}" Header="Value" >
      36. <DataGridTextColumn.ElementStyle>
      37. <Style TargetType="{x:Type TextBlock}">
      38. <Setter Property="TextAlignment" Value="Right" />
      39. </Style>
      40. </DataGridTextColumn.ElementStyle>
      41. </DataGridTextColumn>
      42. </DataGrid.Columns>
      43. </DataGrid>
      44. </Grid>
      45. </Window>
      Ich beschränke mich hier auf die Zeilen 21 und #26-#31, oder besser sogar nur auf #21 und #29:
      In #21 wird ein DelegateConverter in den Resourcen angelegt, dessen Conversion an MainModel.ColorConversion gebunden ist
      In #29 wird dieser Converter benutzt, um den Background einer DataGridRow an die (MeasureRow).Value-Property des Datensatzes zu binden. Da Value aber Double ist, und kein SolidColorBrush, wird im Binding eben der Converter eingesetzt - der weiß ja, wie man einen Double in einen SolidColorBrush konvertiert :D

      Auf eine traurige Wpf-Kinderkrankheit ist noch in Zeile#20 hingewiesen:
      "<!--hier das Binding funzt zwar, läßt sich aber nicht im PropertyFenster setzen -->"
      In Binding-Mismatches führe ich aus, dass man sich möglichst immer an den Angeboten des Property-Fensters orientieren soll, weil das wenigstens ein bischen Intellisense bietet.
      Aber es ist eben nicht immer möglichst, und warum hier das PropertyFenster die Binding-Auswahl nicht unterstützt, ist ganz unerklärlich.
      Denn wenn man dieselbe Resource im Application.Xaml anlegt, dann funzt dort das P-Fenster ganz wunderbar!
      Da hat man nun also die Wahl, ohne P-Fenster-Unterstützung unsauber zu coden, oder unsauber zu coden, weil eine Application-weite Sichtbarkeit dieses Converters krass gegen das Kapselungs-Prinzip verstößt.

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