none
Datagrid Cell Style abhängig eines Wertes einer Anderen Spalte RRS feed

  • Frage

  • Hallo zusammen

    Ich Grübel über folgende Umsetzung. Ich habe 2 Spalten in einem DataGrid welches an eine Sicht gebunden ist. Die beiden Spalten sind von mir folgendermaßen definiert.

    <DataGridTextColumn Header="Soll- stellung" Width="100" Binding="{Binding abgabeschuld, StringFormat={}{0:C}}" HeaderStyle="{StaticResource CenterAlignmentColumnHeaderStyle}" >
     <DataGridTextColumn.ElementStyle>
      <Style TargetType="TextBlock">
       <Setter Property="HorizontalAlignment" Value="Right"/>
      </Style>
     </DataGridTextColumn.ElementStyle>
    </DataGridTextColumn>
    <DataGridTextColumn Header="Bezahlt" Width="100" Binding="{Binding gezahlt, StringFormat={}{0:C}}" HeaderStyle="{StaticResource CenterAlignmentColumnHeaderStyle}" >
     <DataGridTextColumn.ElementStyle>
      <Style TargetType="TextBlock">
       <Setter Property="HorizontalAlignment" Value="Right"/>
      </Style>
     </DataGridTextColumn.ElementStyle>
    </DataGridTextColumn>

    Nun würde ich gerne eine Ampelschaltung auf die Spalte "Bezahlt" implementieren. 
    Bezahlt = 0 (Rot)
    Bezahlt <> Spalte Sollstellung (Gelb)
    Bezahlt = Spalte Sollstellung (Grün)

     Kann mit da jemand helfen?

    LG Thomas

    Mittwoch, 25. Juli 2018 12:56

Antworten

  • Hi Thomas,
    hier mal eine kleine Demo.

    XAML:

    <Window x:Class="Window37"
            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:WpfApp1"
            mc:Ignorable="d"
            Title="Window37" Height="450" Width="800">
      <Window.Resources>
        <local:Window37VM x:Key="vm"/>
        <local:Window37Conv x:Key="conv"/>
      </Window.Resources>
      <Grid DataContext="{StaticResource vm}">
        <DataGrid ItemsSource="{Binding View}" AutoGenerateColumns="False">
          <DataGrid.Columns>
            <DataGridTextColumn Header="Soll- stellung" Width="100" Binding="{Binding Abgabeschuld, StringFormat={}{0:C}}" >
              <DataGridTextColumn.ElementStyle>
                <Style TargetType="TextBlock">
                  <Setter Property="HorizontalAlignment" Value="Right"/>
                </Style>
              </DataGridTextColumn.ElementStyle>
            </DataGridTextColumn>
            <DataGridTextColumn Header="Bezahlt" Width="100" Binding="{Binding Gezahlt, StringFormat={}{0:C}}" >
              <DataGridTextColumn.ElementStyle>
                <Style TargetType="TextBlock">
                  <Setter Property="HorizontalAlignment" Value="Right"/>
                  <Setter Property="Background">
                    <Setter.Value>
                      <MultiBinding Converter="{StaticResource conv}">
                        <Binding Path="Abgabeschuld"/>
                        <Binding Path="Gezahlt"/>
                      </MultiBinding>
                    </Setter.Value>
                  </Setter>
                </Style>
              </DataGridTextColumn.ElementStyle>
            </DataGridTextColumn>
          </DataGrid.Columns>
        </DataGrid>
      </Grid>
    </Window>

    Und der ViewModel und Konverter:

    Imports System.Collections.ObjectModel
    Imports System.ComponentModel
    Imports System.Globalization
    
    Public Class Window37VM
    
      Private cvs As New CollectionViewSource
    
      Public ReadOnly Property View As ICollectionView
        Get
          If cvs.Source Is Nothing Then cvs.Source = GetData()
          Return cvs.View
        End Get
      End Property
    
      Private Function GetData() As ObservableCollection(Of Data)
        Dim l As New ObservableCollection(Of Data)
        l.Add(New Data With {.Abgabeschuld = 55.5D, .Gezahlt = 55.5D})
        l.Add(New Data With {.Abgabeschuld = 55.5D, .Gezahlt = 0D})
        l.Add(New Data With {.Abgabeschuld = 55.5D, .Gezahlt = 33.5D})
        Return l
      End Function
    
      Public Class Data
        Public Property Abgabeschuld As Decimal
        Public Property Gezahlt As Decimal
      End Class
    End Class
    
    Public Class Window37Conv
      Implements IMultiValueConverter
    
      Public Function Convert(values() As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IMultiValueConverter.Convert
        If values.Count < 2 OrElse String.IsNullOrEmpty(values(0).ToString) OrElse String.IsNullOrEmpty(values(1).ToString) Then
          Return New SolidColorBrush(Colors.White)
        End If
        Dim wert1 As Decimal = 0
        Decimal.TryParse(values(0).ToString, wert1)
        Dim wert2 As Decimal = 0
        Decimal.TryParse(values(1).ToString, wert2)
        If wert2 = 0 Then Return New SolidColorBrush(Colors.Red)
        If wert1 <> wert2 Then Return New SolidColorBrush(Colors.Yellow)
        Return New SolidColorBrush(Colors.Green)
      End Function
    
      Public Function ConvertBack(value As Object, targetTypes() As Type, parameter As Object, culture As CultureInfo) As Object() Implements IMultiValueConverter.ConvertBack
        Throw New NotImplementedException()
      End Function
    End Class


    --
    Viele Grüsse
    Peter Fleischer (ehem. MVP)
    Meine Homepage mit Tipps und Tricks

    • Als Antwort vorgeschlagen Peter Fleischer Mittwoch, 25. Juli 2018 14:37
    • Als Antwort markiert tommytom73 Freitag, 27. Juli 2018 10:25
    Mittwoch, 25. Juli 2018 14:37
  • Hi Thomas,
    solch eine Aufgabenstellung wird üblicherweise mit einem ValueConverter gelöst. Die Hintergrundfarbe wird auch an "bezahlt" gebunden. Zusätzlich wird da aber ein ValueConverter zwischengeschaltet, der den Wert von bezahlt auswertet und dann einen Farbverlauf (Brush) liefert. Da Du im ValueConverter nicht mit einem Festwert vergleichen willst, sondern mit dem Wert einer anderen Eigenschaft des Datenobjektes, musst Du einen MultiValueConverter nutzen. Da werden beide Eigenschaften (bezahlt und Sollstellung) gebunden und dann im Converter ausgewertet, um dann den Farbverlauf (Brush) zu liefern.

    Wenn Du noch mitteilst, in welcher Programmiersprache Du arbeitest, kann ich Dir auch ein Beispiel posten.


    --
    Viele Grüsse
    Peter Fleischer (ehem. MVP)
    Meine Homepage mit Tipps und Tricks

    Mittwoch, 25. Juli 2018 13:49

Alle Antworten

  • Hi Thomas,
    solch eine Aufgabenstellung wird üblicherweise mit einem ValueConverter gelöst. Die Hintergrundfarbe wird auch an "bezahlt" gebunden. Zusätzlich wird da aber ein ValueConverter zwischengeschaltet, der den Wert von bezahlt auswertet und dann einen Farbverlauf (Brush) liefert. Da Du im ValueConverter nicht mit einem Festwert vergleichen willst, sondern mit dem Wert einer anderen Eigenschaft des Datenobjektes, musst Du einen MultiValueConverter nutzen. Da werden beide Eigenschaften (bezahlt und Sollstellung) gebunden und dann im Converter ausgewertet, um dann den Farbverlauf (Brush) zu liefern.

    Wenn Du noch mitteilst, in welcher Programmiersprache Du arbeitest, kann ich Dir auch ein Beispiel posten.


    --
    Viele Grüsse
    Peter Fleischer (ehem. MVP)
    Meine Homepage mit Tipps und Tricks

    Mittwoch, 25. Juli 2018 13:49
  • Hi Peter

    Danke erstmal für Deine schnelle Antwort. Ich bin immer noch ein alter Verfechter von vb.NET  ;-)

    LG Thomas

    Mittwoch, 25. Juli 2018 14:01
  • Hi Thomas,
    hier mal eine kleine Demo.

    XAML:

    <Window x:Class="Window37"
            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:WpfApp1"
            mc:Ignorable="d"
            Title="Window37" Height="450" Width="800">
      <Window.Resources>
        <local:Window37VM x:Key="vm"/>
        <local:Window37Conv x:Key="conv"/>
      </Window.Resources>
      <Grid DataContext="{StaticResource vm}">
        <DataGrid ItemsSource="{Binding View}" AutoGenerateColumns="False">
          <DataGrid.Columns>
            <DataGridTextColumn Header="Soll- stellung" Width="100" Binding="{Binding Abgabeschuld, StringFormat={}{0:C}}" >
              <DataGridTextColumn.ElementStyle>
                <Style TargetType="TextBlock">
                  <Setter Property="HorizontalAlignment" Value="Right"/>
                </Style>
              </DataGridTextColumn.ElementStyle>
            </DataGridTextColumn>
            <DataGridTextColumn Header="Bezahlt" Width="100" Binding="{Binding Gezahlt, StringFormat={}{0:C}}" >
              <DataGridTextColumn.ElementStyle>
                <Style TargetType="TextBlock">
                  <Setter Property="HorizontalAlignment" Value="Right"/>
                  <Setter Property="Background">
                    <Setter.Value>
                      <MultiBinding Converter="{StaticResource conv}">
                        <Binding Path="Abgabeschuld"/>
                        <Binding Path="Gezahlt"/>
                      </MultiBinding>
                    </Setter.Value>
                  </Setter>
                </Style>
              </DataGridTextColumn.ElementStyle>
            </DataGridTextColumn>
          </DataGrid.Columns>
        </DataGrid>
      </Grid>
    </Window>

    Und der ViewModel und Konverter:

    Imports System.Collections.ObjectModel
    Imports System.ComponentModel
    Imports System.Globalization
    
    Public Class Window37VM
    
      Private cvs As New CollectionViewSource
    
      Public ReadOnly Property View As ICollectionView
        Get
          If cvs.Source Is Nothing Then cvs.Source = GetData()
          Return cvs.View
        End Get
      End Property
    
      Private Function GetData() As ObservableCollection(Of Data)
        Dim l As New ObservableCollection(Of Data)
        l.Add(New Data With {.Abgabeschuld = 55.5D, .Gezahlt = 55.5D})
        l.Add(New Data With {.Abgabeschuld = 55.5D, .Gezahlt = 0D})
        l.Add(New Data With {.Abgabeschuld = 55.5D, .Gezahlt = 33.5D})
        Return l
      End Function
    
      Public Class Data
        Public Property Abgabeschuld As Decimal
        Public Property Gezahlt As Decimal
      End Class
    End Class
    
    Public Class Window37Conv
      Implements IMultiValueConverter
    
      Public Function Convert(values() As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IMultiValueConverter.Convert
        If values.Count < 2 OrElse String.IsNullOrEmpty(values(0).ToString) OrElse String.IsNullOrEmpty(values(1).ToString) Then
          Return New SolidColorBrush(Colors.White)
        End If
        Dim wert1 As Decimal = 0
        Decimal.TryParse(values(0).ToString, wert1)
        Dim wert2 As Decimal = 0
        Decimal.TryParse(values(1).ToString, wert2)
        If wert2 = 0 Then Return New SolidColorBrush(Colors.Red)
        If wert1 <> wert2 Then Return New SolidColorBrush(Colors.Yellow)
        Return New SolidColorBrush(Colors.Green)
      End Function
    
      Public Function ConvertBack(value As Object, targetTypes() As Type, parameter As Object, culture As CultureInfo) As Object() Implements IMultiValueConverter.ConvertBack
        Throw New NotImplementedException()
      End Function
    End Class


    --
    Viele Grüsse
    Peter Fleischer (ehem. MVP)
    Meine Homepage mit Tipps und Tricks

    • Als Antwort vorgeschlagen Peter Fleischer Mittwoch, 25. Juli 2018 14:37
    • Als Antwort markiert tommytom73 Freitag, 27. Juli 2018 10:25
    Mittwoch, 25. Juli 2018 14:37
  • Hallo Peter,

    ganz lieben Dank für dieses Beispiel muss ich mir mal genau ansehen. Beim Überfliegen habe ich nur noch nicht die Idee an welcher Stelle ich die DataView anbinde. 

    LG Thomas

    Donnerstag, 26. Juli 2018 07:17
  • Hi Thomas,
    Du nutzt in Deinem Codeauszug ein WPF-DataGrid für die Anzeige von Datenobjekten einer Liste. Damit das DataGrid den Inhalt der Liste anzeigt, muss diese oder eine Sicht darauf der ItemsSource-Eigenschaft zugewiesen werden. Das kann man im CodeBehind machen oder wie in meinem Beispiel über Datenbindung im XAML. Mein Beispiel ist entsprechend dem Entwurfsmusters MVVM erstellt. Die ViewModel-Klasse wird als Ressource dem Ressourcen-Wörterbuch des Windows hinzugefügt. Bei der ersten Nutzung als DataContext im äußeren Grid wird eine (statische) Instanz des ViewModels erzeugt. Im DataGrid wird dann aus dieser Instanz die Eigenschaft "View" an die ItemsSource gebunden. Wenn das DataGrid etwas anzeigen will/muss, dann fragt es die gebundene Eigenschaft (View) nach der Liste bzw. nach der Sicht auf die Liste. Als Liste wird im Beispiel eine ObservableCollection mit Datenobjekten vom Typ "Data" genutzt. Die Sicht wird über eine CollectionViewSource bereitgestellt.

    Ich hoffe, dass mit der Erklärung das Beispiel etwas verständlicher ist.


    --
    Viele Grüsse
    Peter Fleischer (ehem. MVP)
    Meine Homepage mit Tipps und Tricks

    Donnerstag, 26. Juli 2018 07:55
  • Hallo Peter,

    ah jetzt wird es klarer.
    Im XAML ist bei mir die Bindung auf ItemSource="{Binding}" und im CodeBehind ist dann bei mir eingestellt auf Me.dgvDokumentation.DataContext = objDb.GetBescheide (DefaultView der Tabelle)

    Ich danke Dir ganz herzlich

    LG Thomas

    PS: Ich markiere es Morgen als Antwort falls ich noch fragen habe :-) 

    Donnerstag, 26. Juli 2018 08:34