Laden...

WPF - DataGrid bindet nicht an IEnumerable

Erstellt von CSharpFreak vor 8 Jahren Letzter Beitrag vor 8 Jahren 2.701 Views
C
CSharpFreak Themenstarter:in
42 Beiträge seit 2014
vor 8 Jahren
WPF - DataGrid bindet nicht an IEnumerable

Hallo -
ich habe ein Problem mit dem Binding. Und zwar möchte ich das Property OBJ an dem DataGrid binden. Nur leider funktioniert es so nicht.

Klassen:


    public class ObjectItem
    {
        public String Name { get; set; }
    }

    public class BindingTest : List<ObjectItem>
    {
        public IEnumerable<ObjectItem> OBJ { get { return this.Where(CItem => CItem.Name.Length < 5); } }
        public BindingTest()
        {
            Add(new ObjectItem() { Name = "A" });
            Add(new ObjectItem() { Name = "B" });
            Add(new ObjectItem() { Name = "C" });
            Add(new ObjectItem() { Name = "DONTLIST" });
        }
    }

    public class MainObject
    {
        public BindingTest BIN { get; set; } = new BindingTest();
    }

XAML:

<Window x:Class="ExampleBinding.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:ExampleBinding"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <Grid Loaded="Grid_Loaded">
        <DataGrid x:Name="WORKS" 
                  ItemsSource="{Binding ABC2.OBJ}"
                  AutoGenerateColumns="False"
                  HorizontalAlignment="Left" VerticalAlignment="Top" Height="126" Width="163" Margin="10,79,0,0">
            <DataGrid.Columns>
                <DataGridTextColumn Binding="{Binding Name}"/>
            </DataGrid.Columns>
        </DataGrid>
        <DataGrid x:Name="DONTWORK" 
        	ItemsSource="{Binding ABC.BIN.OBJ}"
        	AutoGenerateColumns="False"
        	HorizontalAlignment="Left" VerticalAlignment="Top" Height="126" Width="163" Margin="340,79,0,0" DataContext="">
            <DataGrid.Columns>
                <DataGridTextColumn Binding="{Binding Name}"/>
            </DataGrid.Columns>
        </DataGrid>
    </Grid>
</Window>

    public partial class MainWindow : Window
    {
        public MainObject ABC { get; set; } = new MainObject();
        public BindingTest ABC2 { get; set; } = new BindingTest();
        public MainWindow()
        {
            InitializeComponent();
            DataContext = this;
        }

        private void Grid_Loaded(object sender, RoutedEventArgs e)
        {

        }
    }

Mit freundlichen Grüßen

212 Beiträge seit 2008
vor 8 Jahren

Hallo!

Deine Klasse ObjectItem sollte INotifyPropertyChanged implementieren.

Für die Liste musst Du eine ObservableCollection<ObjectItem> nehmen, ansonsten aktualisiert sich die View nicht.

Die Abfrage im Getter funktioniert so auch nicht, da müsstest Du eine gesonderte Liste bereitstellen, damit das CollectionChanged gefeuert wird, nur so bleibt die View aktuell.

Die Zuweisung vom DataContext solltest Du auch noch mal überprüfen.

Ich denke das sollte ehr so aussehen:


public BindingTest ABC2 { get; set; } = new BindingTest();
public MainWindow()
{
    InitializeComponent();
    DataContext = ABC2;
}

Bitte auch noch dein XAML überprüfen, DataContext="" geht auch nicht 😃

Gruß
Christoph

5.299 Beiträge seit 2008
vor 8 Jahren

Unabhängig von den sonstigen Schwächen des Ansatzes fund Ich folgendes Detail in deim geposteten Xaml-Code:

 DataContext="">

Das dürfte das beschriebene Fehlverhalten aufklären.

Edit: mist - zu spät

Der frühe Apfel fängt den Wurm.

C
CSharpFreak Themenstarter:in
42 Beiträge seit 2014
vor 8 Jahren

Vielen Dank! Ihr habt beide recht, ich habe das View nicht richtig gesetzt :S
Und soweit ich gelesen habe muss man nicht zwanghaft manuell die "INotifyPropertyChanged" implementieren da dies automatisch geschieht, wenn man {get; set; } deklariert?
Die List<> habe ich in ObservableCollection<> geändert.

Allerdings stelle ich mit noch die Frage wie das mit IEnumerable ist, wie kann ich ein Solches Attribut binden, sodass es sich immer aktualisiert?

212 Beiträge seit 2008
vor 8 Jahren

Und soweit ich gelesen habe muss man nicht zwanghaft manuell die "INotifyPropertyChanged" implementieren da dies automatisch geschieht, wenn man {get; set; } deklariert?

Wo hast du das gelesen? Da muss man immer nacharbeiten, zu mindest bis heute.

Allerdings stelle ich mit noch die Frage wie das mit IEnumerable ist, wie kann ich ein Solches Attribut binden, sodass es sich immer aktualisiert?

Du musst die Liste(ObservableCollection) immer updaten, so dass das CollectionChanged Event gefeuert wird. Also, aus der Basis-Liste abfragen und dann an die gefilterte Liste übergeben. So würde ich das zumindest machen.

Gruß
Christoph

5.299 Beiträge seit 2008
vor 8 Jahren

nö, muss man nicht.
Wenn eine Property nur ein einziges Mal gelesen wird beim Binden, und sich im weiteren nicht mehr ändert, dann braucht man natürlich auch keinen Benachrichtigungs-Mechanismus über Änderungen (notify property-changed)

Um das mit der ObservableCollection verständlich zu machen: Die gebundene Property muss vom Typ ObservableCollection<T> sein, dann kriegt das Gui auch mit, wenn Datensätze zukommen/gelöscht werden.

Allerdings solltest du, CSharpFreak, dir was zum MVVM-Pattern anlesen, denn ein Window an sich selbst zu binden - damit bist du wieder auf dem Niveau von Windows.Forms, bzw. noch darunter.

Insbesondere das DataContext-Setzen im Codebehind disabled ja auch die Intellisense-Unterstützung beim Setzen von Bindings (wie gesagt: da war olle WinForms noch besser 😉 )

Der frühe Apfel fängt den Wurm.

212 Beiträge seit 2008
vor 8 Jahren

@ErfinderDesRades

worauf bezieht sich das "nö, muss man nicht" ?

Gruß
Christoph

5.299 Beiträge seit 2008
vor 8 Jahren

Na, darauf:

Und soweit ich gelesen habe muss man nicht zwanghaft manuell die "INotifyPropertyChanged" implementieren da dies automatisch geschieht, wenn man {get; set; } deklariert?

Wo hast du das gelesen? Da muss man immer nacharbeiten, zu mindest bis heute. Weil wie gesagt - wenn die Props sich nicht ändern, brauchst du auch kein INotify dazu.

Also ich gebe CsharpFreak insonfern recht, dass mans nicht zwangsläufig braucht, aber nicht, weil das automatisch ginge, sondern weil man's garnet braucht, wenn die Properties sich nicht ändern.

Oder anders: Nur das Aktualisierung der Bindings ist auf INotifyPropertyChanged angewiesen - initiales Binding funzt auch ohne das tadellos.

Der frühe Apfel fängt den Wurm.

212 Beiträge seit 2008
vor 8 Jahren

Aso, da stimme ich zu. Ich hatte das so verstanden, dass INotifyPropertyChanged automatisch implementiert ist, wenn es einen Getter und Setter gibt.

[EDIT]

"INotifyPropertyChanged" implementieren da dies automatisch geschieht, wenn man {get; set; } deklariert

So habe ich das verstanden.

Gruß
Christoph

C
CSharpFreak Themenstarter:in
42 Beiträge seit 2014
vor 8 Jahren

Vielen Dank erst noch einmal für eure Hilfe. Ich habe mir das MVVM-Pattern nochmal angeschaut und nun auch hoffentlich etwas verinnerlicht.
Und dabei noch etwas interessantes entdeckt, dass seit dem .Net 4.5 eine Vereinfachung bezüglich des PropertyChanged existiert =)

Ich habe das Beispiel daraufhin etwas abgeändert. Allerdings stehe ich nun vor dem Problem mit dem CollectionChanged Event. Das Event sollte ja von der ObservableCollection implementiert werden.


    public class ObjectItem : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        protected void SetProperty<T>(ref T field, T value, [CallerMemberName] String name = "")
        {
            if (!EqualityComparer<T>.Default.Equals(field, value))
            {
                field = value;
                if (PropertyChanged != null)
                {
                    PropertyChanged(this, new PropertyChangedEventArgs(name));
                }
            }
        }

        private String _Name;
        public String Name
        {
            get { return _Name; }
            set { SetProperty(ref _Name, value); }
        }
    }

    public class BindingTest : ObservableCollection<ObjectItem>
    {
        public IEnumerable<ObjectItem> OBJLessTree { get { return this.Where(CItem => CItem.Name.Length < 3); } }
        public IEnumerable<ObjectItem> OBJGreaterTree { get { return this.Where(CItem => CItem.Name.Length > 3); } }
        public BindingTest()
        {
            Add(new ObjectItem() { Name = "A"});
            Add(new ObjectItem() { Name = "B" });
            Add(new ObjectItem() { Name = "C" });
            Add(new ObjectItem() { Name = "FIRST" });
            Add(new ObjectItem() { Name = "SECOND" });
            Add(new ObjectItem() { Name = "THIRD" });
        }
    }

<Window x:Class="ExampleBinding.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:ExampleBinding"
        mc:Ignorable="d"
        Title="MainWindow" Height="250" Width="525">
    <Grid>
        <DataGrid x:Name="LessDataGrid" 
                  ItemsSource="{Binding OBJLessTree}"
                  AutoGenerateColumns="False"
                  IsReadOnly="True" VerticalAlignment="Top" Height="126" Margin="10,10,300,0">
            <DataGrid.Columns>
                <DataGridTextColumn Binding="{Binding Name}"/>
            </DataGrid.Columns>
        </DataGrid>
        <DataGrid x:Name="GreaterDataGrid" 
        	ItemsSource="{Binding OBJGreaterTree}"
        	AutoGenerateColumns="False"
            IsReadOnly="True" VerticalAlignment="Top" Height="126" Margin="300,10,10,0">
            <DataGrid.Columns>
                <DataGridTextColumn Binding="{Binding Name}"/>
            </DataGrid.Columns>
        </DataGrid>
        <Button x:Name="button" Content="Change!!!" Margin="10,147,10,0" VerticalAlignment="Top" Height="65" Click="button_Click"/>
    </Grid>
</Window>

    public partial class MainWindow : Window
    {
        public BindingTest ABC { get; set; } = new BindingTest();
        public MainWindow()
        {
            InitializeComponent();
            LessDataGrid.DataContext = ABC;
            GreaterDataGrid.DataContext = ABC;
        }

        private void button_Click(object sender, RoutedEventArgs e)
        {
            ABC.First().Name = "ABCDEFGH";
        }
    }

3.003 Beiträge seit 2006
vor 8 Jahren

Vielen Dank erst noch einmal für eure Hilfe. Ich habe mir das MVVM-Pattern nochmal angeschaut und nun auch hoffentlich etwas verinnerlicht.
Und dabei noch etwas interessantes entdeckt, dass seit dem .Net 4.5 eine Vereinfachung bezüglich des PropertyChanged existiert =)

Ich habe das Beispiel daraufhin etwas abgeändert. Allerdings stehe ich nun vor dem Problem mit dem CollectionChanged Event. Das Event sollte ja von der ObservableCollection implementiert werden.

Wird es auch. Aber die Eigenschaften, die du dort bindest, sind IEnumerable<ObjectItem> und nicht ObservableCollection.

Btw, Button_Click weist schon darauf hin, dass du MVVM nicht wirklich verinnerlicht hast, genau wie das Setzen des DataContext im Code-Behind. Man sollte schon wirklich, wirklich gut begründen, wenn man bei MVVM Code-Behind nutzt (die Faelle gibt es, sind aber wirklich selten).

LaTino
Edit (laengliche Erlaeuterung): jedes Objekt, das INotifyPropertyChanged implementiert, kann so gebunden werden, dass die veränderten Eigenschaften im View abgebildet werden. Nun implementiert IEnumerable<> nicht INotifyPropertyChanged, weshalb Änderungen an der Liste selbst nur abgebildet werden können, wenn man einen Wrapper um eine Liste schreibt, der INotifyPropertyChanged implementiert und bei jeder Änderung am Inhalt der Liste, nicht der beinhalteten Objekte, feuert. Und genau das ist ObservableCollection. Wenn du ABC nicht direkt bindest, muss es auch nicht ObservableCollection sein. Dein ViewModel soll ein Modell des Views sein! Wenn du also zwei DataGrids mit zwei Datenquellen hast, dann musst du das im VM nachbilden.

"Furlow, is it always about money?"
"Is there anything else? I mean, how much sex can you have?"
"Don't know. I haven't maxed out yet."
(Furlow & Crichton, Farscape)

5.299 Beiträge seit 2008
vor 8 Jahren

Was auch wichtig: Es ist glaub BrainF...k, von ObservableCollection<T> zu erben. Ich sagte glaub in meim 1.Post: Die ItemSource der DGs sollte an eine Property vom Typ ObservableCollection<T> binden.

Von ObservableCollection<T> zu beerben habich nix gesagt.
Klar kann man das machen, aber und wenn du solch tust, solltest du einen guten Grund dafür haben, ansonsten es lassen.

Der frühe Apfel fängt den Wurm.

W
872 Beiträge seit 2005
vor 8 Jahren

Ich benutze gerne PropertyChanged.Fody, damit ich nicht manuell INotifyPropertyChanged implementieren muss.

Dann wird für jedes Public Property mit getter und setter INotifyPropertyChanged direkt beim Compilieren generiert.

C
CSharpFreak Themenstarter:in
42 Beiträge seit 2014
vor 8 Jahren

Btw, Button_Click weist schon darauf hin, dass du MVVM nicht wirklich verinnerlicht hast, genau wie das Setzen des DataContext im Code-Behind. Man sollte schon wirklich, wirklich gut begründen, wenn man bei MVVM Code-Behind nutzt (die Faelle gibt es, sind aber wirklich selten).

Das verstehe ich nicht... wie wäre es denn richtig?

Was auch wichtig: Es ist glaub BrainF...k, von ObservableCollection<T> zu erben. Ich sagte glaub in meim 1.Post: Die ItemSource der DGs sollte an eine Property vom Typ ObservableCollection<T> binden.

Ich fand es für mich sinnig davon zu erben, da es mir einfach nur einen Member ersparen sollte bzw. es am ende eine "Liste" sein soll mit Membern, in der vorgefertigte filter sind.

Ich habe eine weitere tolle Sache entdeckt "ObservableCollectionView", diese sollte mir die Collection filtern, wenn ich das richtig verstanden habe. Ich bin wie im Beispiel vorgegangen jedoch werden immer noch weiterhin nur die Properties geändert.


    public class ObjectItem : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        protected void SetProperty<T>(ref T field, T value, [CallerMemberName] String name = "")
        {
            if (!EqualityComparer<T>.Default.Equals(field, value))
            {
                field = value;
                PropertyChangedEventHandler handler = PropertyChanged;
                if (handler != null)
                {
                    handler(this, new PropertyChangedEventArgs(name));
                }
            }
        }

        private String _Name;
        public String Name
        {
            get { return _Name; }
            set { SetProperty(ref _Name, value); }
        }
    }

    public class BindingTest : MtObservableCollection<ObjectItem>
    {
        public ObservableCollectionView<ObjectItem> OBJLessTree { get; set; }
        public ObservableCollectionView<ObjectItem> OBJGreaterTree { get; set; }
        public BindingTest()
        {
            OBJLessTree = new ObservableCollectionView<ObjectItem>(this);
            OBJGreaterTree = new ObservableCollectionView<ObjectItem>(this);

            OBJLessTree.Filter = p => p.Name.Length < 3;
            OBJGreaterTree.Filter = p => p.Name.Length > 3;

            this.Add(new ObjectItem() { Name = "A"});
            this.Add(new ObjectItem() { Name = "B" });
            this.Add(new ObjectItem() { Name = "C" });
            this.Add(new ObjectItem() { Name = "FIRST" });
            this.Add(new ObjectItem() { Name = "SECOND" });
            this.Add(new ObjectItem() { Name = "THIRD" });
        }
    }

<Window x:Class="ExampleBinding.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:ExampleBinding"
        mc:Ignorable="d"
        Title="MainWindow" Height="250" Width="525">
    <Grid>
        <DataGrid x:Name="LessDataGrid" 
                  ItemsSource="{Binding OBJLessTree, UpdateSourceTrigger=PropertyChanged}"
                  AutoGenerateColumns="False"
                  IsReadOnly="True" VerticalAlignment="Top" Height="126" Margin="10,10,300,0">
            <DataGrid.Columns>
                <DataGridTextColumn Binding="{Binding Path=Name}"/>
            </DataGrid.Columns>
        </DataGrid>
        <DataGrid x:Name="GreaterDataGrid" 
        	ItemsSource="{Binding OBJGreaterTree, UpdateSourceTrigger=PropertyChanged}"
        	AutoGenerateColumns="False"
            IsReadOnly="True" VerticalAlignment="Top" Height="126" Margin="300,10,10,0">
            <DataGrid.Columns>
                <DataGridTextColumn Binding="{Binding Path=Name}"/>
            </DataGrid.Columns>
        </DataGrid>
        <Button x:Name="button" Content="Change!!!" Margin="10,147,10,0" VerticalAlignment="Top" Height="65" Click="button_Click"/>
    </Grid>
</Window>

    public partial class MainWindow : Window
    {
        public BindingTest ABC { get; set; } = new BindingTest();
        public MainWindow()
        {
            InitializeComponent();
            DataContext = ABC;
        }

        private void button_Click(object sender, RoutedEventArgs e)
        {
            ABC.First().Name = "ABCDEFGH";
        }
    }

Ich benutze gerne
>
, damit ich nicht manuell INotifyPropertyChanged implementieren muss.

Dann wird für jedes Public Property mit getter und setter INotifyPropertyChanged direkt beim Compilieren generiert.

Werde ich später noch ausprobieren, da ich vorerst das Vorgehen verstehen möchte.
Danke für den Hinweis für das NuGet.

5.299 Beiträge seit 2008
vor 8 Jahren

Das verstehe ich nicht... wie wäre es denn richtig? Das ist nicht in einem Post kurz mal erklärt.
Wurde schon (mehrfach) gesagt: Recherchiere dir das Stichwort "MVVM" - Pattern.

Einige Beispiele findest du hier: https://www.vb-paradise.de/index.php/Board/959-WPF-XAML/ einiges davon ist sogar 2-sprachig (vb+c#) ausgeführt, grade die grundsätzlicheren Tuts.
Ansonsten ist vb auch nicht soo schwer zu verstehen, und das Xaml ist ja dasselbe.
Aber nicht nur Samples gucken, sondern auch die Theorie dazu nachlesen, dass du verstehst, warum die Samples so aufgebaut sind und nicht anders.

Jo, und zu BindingTest, MtObservableCollection<ObjectItem>, ObservableCollectionView und whatever:
Mach es einfach. Suche immer die einfachste Lösung, die es gibt. Von 2 Möglichkeiten, die dasselbe Prob lösen - die einfachere wählen - mach dir das zum Prinzip (es heißt übrigens "KISS").

Wenn du nurso aus Daffke iwelche neuen Klassen erstellst, von iwas geerbt, und hier noch ne Bibliothek eingebunden, da noch ein Interface implementert, und und und... - hier so zum Spielen mag das angehen, aber wenn du mal was programmierst, und dir das als Stil angewöhnt hast - niemand wird deine Proggis verstehen können, wegen dem ganzen Kram, der da rumfährt.
Und den wesentlichen Punkten kommst du so auch keinen Schritt näher.

Sinnloser Code ist noch sogar schwieriger zu handhaben als fehlerhafter - man untersucht son Zeug Stunden und Stunden, um dahinter zu kommen, was es soll.
Und man muss sich regelrecht ein Herz fassen, um zu sagen: "Na, da ist ja garnichts hinter", weil man erwartet sowas nicht, und ausserdem: wenn man sich irrt, und räumt es weg, und war doch was hinter, dann ist evtl. ziemlich viel kaputt.

Also wie gesagt: Halte dich an KISS - Programme werden schon von selbst kompliziert genug.

Der frühe Apfel fängt den Wurm.