Laden...

MultiDataTrigger-Binding - Null-Value-Exception

Erstellt von GeneVorph vor 3 Jahren Letzter Beitrag vor 3 Jahren 550 Views
G
GeneVorph Themenstarter:in
180 Beiträge seit 2015
vor 3 Jahren
MultiDataTrigger-Binding - Null-Value-Exception

Hallo,
folgendes Problem versuche ich derzeit zu lösen:

Ich gruppiere Daten in einem DataGrid. Dazu verwende ich einen Expander. In meinem ViewModel ist ein enum, der drei Expander-Zustände deklariert:

  • collapseAll
  • expandAll
  • collapseAllButSelected

collapseAll: alle GroupItems werden kollabiert, d.h. die Expander aller GroupItems sind collapsed
expandAll: alle GroupItems sind geöffnet, d.h. die Expander aller GroupItems sind expanded
collapseAllButSelected: alle GroupItems sind kollabiert, es sei denn, ein Item aus der Gruppe wurde ausgewählt.

Ich habe dazu im XAML code bereitgestellt, der immer dann, wenn man auf einen ColumnHeader des DAtaGrids klickt ein Command auslöst.
Das Command initiiert einen Zähler, der je nach Anzahl der Klicks den ExpanderState verändert:
Beim ersten Klick auf den ColumnHeader collapseAll, beim zweiten Klick expandAll, beim dritten Klick collapseAllButSelected.
Diese Funktionalität habe ich bereits bereitgestellt – das funktioniert also.

Was ich nun vorhabe ist folgendes:1. Wenn die Elemente im DataGrid gruppiert werden…
a) frage den ExpanderState ab
b) prüfe, welche Column angeklickt wurde (welcher Name hat der Header)
c) je nachdem welche Bedingung zutrifft wähle ein entsprechendes Expander-Header-Template
Das könnte dann also so aussehen:
Wenn der ExpanderState collapseAll ist (a) UND die Column mit dem Header „Fach“ (b) angeklickt wurde lade ExpanderHeader-Template x (c).
Wenn der ExpanderState collapseAll ist (a) UND die Column mit dem Header „Note“ angeklickt wrude lade ExpanderHeader-Template y (c).
Wenn der ExpanderState expandAll ist (a) UND die Column mit dem Header „Fach“ (b) angeklickt wurde lade ExpanderHeader-Template z (c).
Wenn der ExpanderState expandAll ist (a) UND die Column mit dem Header „Note“ angeklickt wrude lade ExpanderHeader-Template u (c). … usw. usf.

Natürlich wollte ich „einfach“ anfangen und erstmal einen Prüffall durchspielen.
In meinem xaml-code sieht das so aus:


<!-- DataTemplates for the EXPANDER HEADERs -->
        <DataTemplate x:Key="ExpanderHeaderExpanded">
            <StackPanel>
                <TextBlock Text="{Binding Path=DataContext.Name, RelativeSource={RelativeSource AncestorType=Expander}}"
                           FontSize="16" FontWeight="Bold"/>
                <TextBlock Text=""/>
            </StackPanel>
        </DataTemplate>

        <DataTemplate x:Key="ExpanderHeaderCollapsed">
            <StackPanel>
                <TextBlock Text="{Binding Path=DataContext.Name, RelativeSource={RelativeSource AncestorType=Expander}}"
                           FontSize="16" FontWeight="Bold"/>
                <TextBlock>
                    <TextBlock.Style>                        
                        <Style TargetType="TextBlock">
                            <Setter Property="Text">
                                <Setter.Value>
                                    <MultiBinding Mode="OneWay" Converter="{StaticResource AverageValueConverter}" ConverterParameter="Subject">
                                        <Binding  Path="DataContext" RelativeSource="{RelativeSource FindAncestor, AncestorType=Expander, AncestorLevel=1}"/>
                                        <Binding  RelativeSource="{RelativeSource FindAncestor, AncestorType=DataGrid, AncestorLevel=1}"
                                                  Path="DataContext.AllGradesCV.View.Groups"></Binding>
                                    </MultiBinding>
                                </Setter.Value>
                            </Setter>                            
                        </Style>
                    </TextBlock.Style>
                </TextBlock>
            </StackPanel>
        </DataTemplate>


<!-- Style for GroupItem Behaviour of EXPANDER -->
        <Style x:Key="GroupItemStyle" TargetType="Expander">
            <Setter Property="Background" Value="#FF5CB9EE"/>
            <Setter Property="ExpandDirection" Value="Down"/>
            <Style.Triggers>                  
                <Trigger Property="IsExpanded" Value="True">
                    <Setter Property="HeaderTemplate" Value="{StaticResource ExpanderHeaderExpanded}"/>
                </Trigger>
                <!-- Diesen Trigger gegen MultiDataTrigger ersetzen?= -->
                <!--<Trigger Property="IsExpanded" Value="False">
                    <Setter Property="HeaderTemplate" Value="{StaticResource ExpanderHeaderCollapsed}"/>
                </Trigger>-->

                <MultiDataTrigger>
                    <MultiDataTrigger.Conditions>
                        <Condition Property="IsExpanded" Value="False"/>
                        <Condition Binding="{Binding TargetNullValue=true, RelativeSource={RelativeSource AncestorType={x:Type DataGridTemplateColumn}}, Path=Header}" Value="Fach"/>
                    </MultiDataTrigger.Conditions>
                    <MultiDataTrigger.Setters>
                        <Setter Property="HeaderTemplate" Value="{StaticResource ExpanderHeaderCollapsedType}"/>
                    </MultiDataTrigger.Setters>
                </MultiDataTrigger>

Der Part mit dem MultiDataTrigger ist das Problem: wenn ich die App starte wird folgende Exception ausgegeben:> Fehlermeldung:

System.Windows.Markup.XamlParseException: "Nicht-NULL-Wert für "Binding" erforderlich."

Innere Ausnahme:
InvalidOperationException: Nicht-Null-Wert für “Binding” erforderlich.

Ich vermute es fehlt ein Nicht-Null-Wert – was auch immer das bedeuten mag.
Ich bin mir nicht mal sicher, ob ich mit dem MultiDataTriffer für mein Vorhaben da auf dem richtigen Weg bin.
Daher hier noch kurz der Versuch einer Konkretisierung:

  • ich möchte im Style mit dem Target-Type “Expander” auf die Property “IsExpanded” des Expanders binden (prüfen, ob der Value false ist”
  • gleichzeitig auf das Property des DataGrids binden (Column --> Header--> Bezeichnung) und wenn letzterer den Value “Fach aufweist” im Setter das Property “HeaderTemplate” des Expanders setzen.
    Gruß
    Vorph
5.658 Beiträge seit 2006
vor 3 Jahren

Ich vermute es fehlt ein Nicht-Null-Wert – was auch immer das bedeuten mag.

Ein "Nicht-Null-Wert" ist ein Wert, der nicht null ist. Du kannst dem Template nicht null zuweisen, aber wo genau das auftritt, sollte dir die Fehlermeldung sagen.

Ansonsten scheint es in deinem Code noch mehrere andere Merkwürdigkeiten zu geben. Wo defininierst du z.B. das Template für "ExpanderHeaderCollapsedType", und warum gibst du als TargetNullValue für einen String-Wert ein "true" an?

Weeks of programming can save you hours of planning

G
GeneVorph Themenstarter:in
180 Beiträge seit 2015
vor 3 Jahren

Du kannst dem Template nicht null zuweisen, aber wo genau das auftritt, sollte dir die Fehlermeldung sagen.

Die komplette Fehlermeldung ist ja mit angegeben (s. Post 1) - expliziter wird es leider nicht 🙁

Ansonsten scheint es in deinem Code noch mehrere andere Merkwürdigkeiten zu geben. Wo defininierst du z.B. das Template für "ExpanderHeaderCollapsedType"

In UserControl.Resources (mir schleierhaft warum ich das vergessen hatte - sorry)


<DataTemplate x:Key="ExpanderHeaderCollapsedType">
            <StackPanel>
                <TextBlock Text="{Binding Path=DataContext.Name, RelativeSource={RelativeSource AncestorType=Expander}}"
                           FontSize="16" FontWeight="Bold"/>
                <TextBlock>
                    <TextBlock.Style>
                        <Style TargetType="TextBlock">
                            <Setter Property="Text">
                                <Setter.Value>
                                    <MultiBinding Mode="OneWay" Converter="{StaticResource AverageValueConverter}" ConverterParameter="Type">
                                        <Binding  Path="DataContext" RelativeSource="{RelativeSource FindAncestor, AncestorType=Expander, AncestorLevel=1}"/>
                                        <Binding  RelativeSource="{RelativeSource FindAncestor, AncestorType=DataGrid, AncestorLevel=1}"
                                                  Path="DataContext.AllGradesCV.View.Groups"></Binding>
                                    </MultiBinding>
                                </Setter.Value>
                            </Setter>
                        </Style>
                    </TextBlock.Style>
                </TextBlock>
            </StackPanel>
        </DataTemplate>

... und warum gibst du als TargetNullValue für einen String-Wert ein "true" an?

Gute Frage - weil ich schlicht keine Ahnung hatte, was ich mit dem Fehler anfangen soll, und beim googeln dann auf diese Monstrosität gestoßen bin. Im übrigen ist es egal ob ich true oder PaulePanter eingebe - das ändert schlicht nüscht.

Dürfte ich meine Eingangsfrage vlt. umformulieren? Ich denke mit diesem "Null-Type-Value" verrenne ich mich in eine ganz falsche Richtung. Nachdem ich es jetzt tagelang probiert habe, scheint mir, die eigentliche Frage müsste lauten:
wie erreiche ich es, dass beim Gruppieren der jeweilige Expander abhängig von der angeklickten DataGridColumn UND dem Zustand von ExpandedState eine bestimmte Funktion über einen ValueConverter auslöst?

Hier ein funktionierendes Beispiel (eine andere View in meinem Projekt, die aber fast dieselbe Funktionalität bereitstellt):
Hier wird im ValueConverter lediglich festgestellt, ob ein bestimmtes Objekt (Eintrag im DataGrid) gerade das aktuelle Objekt ist. Wenn ja, werden alle GroupItems collapsed, bis auf dasjenige, zu dem das akutelle Objekt gehört (konkret: du hast Schüler verschiedener Klassen. Aus der Klasse A1 ist Paule Panter angeklickt. Beim Gruppieren werden nun alle Klassen gruppiert und in einem kollabierten Expander zusammengefasst, außer die Klasse, in der Paule Panter ist):


<UserControl.Resources>
       <!-- Hier wird der ValueConverter deklariert. Dieser ist eine .cs-Datei in meinem Projekt (s. unten) -->
        <conv:ExpandConverter x:Key="ExpanderStateConverter"/>

<!-- Ein enum (ExpandedState) entscheidet darüber, wie der Expander sich verhalten soll (collapsed, expanded, alle collapsed, bis auf das GroupItem, das den aktuell im DataGrid ausgewählten Eintrag erhält. Da im ExpanderHeader unterschiedliche Infos je nach Zustand angezeigt werden, gibt es zwei verschiedene Templates-->

        <DataTemplate x:Key="ExpanderHeaderExpanded">
            <StackPanel>
                <TextBlock Text="{Binding Path=DataContext.Name, RelativeSource={RelativeSource AncestorType=Expander}}"
                           FontSize="16" FontWeight="Bold"/>
                <TextBlock Text="{Binding Path=DataContext.ItemCount, StringFormat=Schüler: {0}, RelativeSource={RelativeSource AncestorType=Expander}}"/>
            </StackPanel>
        </DataTemplate>

        <DataTemplate x:Key="ExpanderHeaderCollapsed">
            <StackPanel>
                <TextBlock Text="{Binding Path=DataContext.Name, RelativeSource={RelativeSource AncestorType=Expander}}"
                           FontSize="16" FontWeight="Bold"/>
                <TextBlock Text="{Binding Path=DataContext.ItemCount, StringFormat=Schüler: {0}, RelativeSource={RelativeSource AncestorType=Expander}}"
                           FontSize="12" FontWeight="Bold"/>
            </StackPanel>
        </DataTemplate>
       
<!-- GroupItem-Style für den Expander. Je nachdem, ob das ExpanderHeader expanded oder collapsed ist wird hier das Header-Template geladen-->
        <Style x:Key="GroupItemStyle" TargetType="Expander">           

            <Setter Property="Background" Value="#FF5CB9EE"/>
            <Setter Property="ExpandDirection" Value="Down"/>
            <Style.Triggers>
                <Trigger Property="IsExpanded" Value="False">
                    <Setter Property="HeaderTemplate" Value="{StaticResource ExpanderHeaderCollapsed}"/>
                </Trigger>
                <Trigger Property="IsExpanded" Value="True">
                    <Setter Property="HeaderTemplate" Value="{StaticResource ExpanderHeaderExpanded}"/>
                </Trigger>

<!-- GroupItem-DAtaTrigger: Hier wird an den enum ExpandedState im ViewModel gebunden... -->
                <DataTrigger  Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UserControl}, Path=DataContext.ExpandedState}" Value="expandAll">
<!-- ...und wenn dessen Value "IsExpanded ist, dieser Code ausgeführt: -->
                    <Setter Property="IsExpanded">
                        <Setter.Value>
                            <MultiBinding Mode="OneWay" Converter="{StaticResource ExpanderStateConverter}" ConverterParameter="expandAll">
                                <Binding  Path="DataContext" RelativeSource="{RelativeSource Self}"/>
                                <Binding  RelativeSource="{RelativeSource FindAncestor, AncestorType=DataGrid, AncestorLevel=1}"
                Path="DataContext.SelectedStudent"></Binding>
                            </MultiBinding>
                        </Setter.Value>
                    </Setter>
                </DataTrigger>

<!-- ...wenn der Wert "collapseAll" ist, wird dieser Code ausgeführt: -->
                <DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UserControl}, Path=DataContext.ExpandedState}" Value="collapseAll">
                    <Setter Property="IsExpanded">
                        <Setter.Value>
                            <MultiBinding Mode="OneWay" Converter="{StaticResource ExpanderStateConverter}" ConverterParameter="collapseAll">
                                <Binding  Path="DataContext" RelativeSource="{RelativeSource Self}"/>
                                <Binding
                RelativeSource="{RelativeSource FindAncestor, AncestorType=DataGrid, AncestorLevel=1}"
                Path="DataContext.SelectedStudent"></Binding>
                            </MultiBinding>
                        </Setter.Value>
                    </Setter>
                </DataTrigger>

<!-- ...und wenn der Wert "collapseAllButSelected" ist, wird dieser Code ausgeführt: -->
                <DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UserControl}, Path=DataContext.ExpandedState}" Value="collapseAllButSelected">
                    <Setter Property="IsExpanded">
                        <Setter.Value>
                            <MultiBinding Mode="OneWay" Converter="{StaticResource ExpanderStateConverter}" ConverterParameter="collapseAllButSelected">
                                <Binding  Path="DataContext" RelativeSource="{RelativeSource Self}"/>
                                <Binding
                RelativeSource="{RelativeSource FindAncestor, AncestorType=DataGrid, AncestorLevel=1}"
                Path="DataContext.SelectedStudent"></Binding>
                            </MultiBinding>
                        </Setter.Value>
                    </Setter>
                </DataTrigger>

            </Style.Triggers>
            
        </Style>
        
    </UserControl.Resources>

Im DataTrigger findet ja so eine Art if/then/else Abfrage statt (wenn ExpandedState == collapseAll -> then...).
Alles was ich jetzt mit diesem Post erreichen wollte ist eigentlich: wie mache ich das, wenn nun noch eine Condition hinzukommt? Also:
if (ExpandedState == collapseAllButSelected && DataGrid.ColumnName == "xyu")?
Dieser Gedankengang hat mich überhaupt erst auf MultiDataTrigger gebracht.

Der Vollständigkeit halber hier mal noch der ValueConverter:


public class ExpandConverter : IMultiValueConverter
    {
        private bool ItemInFilterGroup(object[] value)
        {
            CollectionViewGroup oc;
            Student x = new Student();

            //CollectionViewGroup 
            //1. Evalute type on [0]
            if (value[0] is CollectionViewGroup)
            {
                oc = value[0] as CollectionViewGroup;
            }
            else
            {
                oc = null;
            }

            if (value[1] is Student)
            {
                x = value[1] as Student;
            }

            if (oc != null && x != null)
            {
                if (oc.Items.Contains(x))
                {
                    return true;
                }
                else
                {
                    return false;
                }
            }

            return false;
            
        }

        public object Convert(object[] value, Type targetType, object parameter, CultureInfo culture)
        {
            var x = parameter as string;

            if (x == "expandAll")
            {                
                return true;
            }
            else if (x== "collapseAll")
            {
                return false;
            }
            else if (x == "collapseAllButSelected")
            {
                return ItemInFilterGroup(value);
            }

            return false;
        }       

        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }